Atualização floresta
This commit is contained in:
parent
0886fd5c3e
commit
2d4d8f8929
103 changed files with 19266 additions and 14160 deletions
|
|
@ -34,12 +34,13 @@ baixar "$BASE/workflows/aprimorar-IA.global.md" "$DIR/workflows
|
||||||
baixar "$BASE/workflows/adicionar-ajuda.global.md" "$DIR/workflows/adicionar-ajuda.global.md"
|
baixar "$BASE/workflows/adicionar-ajuda.global.md" "$DIR/workflows/adicionar-ajuda.global.md"
|
||||||
baixar "$BASE/workflows/atualizar-agente.global.md" "$DIR/workflows/atualizar-agente.global.md"
|
baixar "$BASE/workflows/atualizar-agente.global.md" "$DIR/workflows/atualizar-agente.global.md"
|
||||||
baixar "$BASE/workflows/criar-fluxograma.global.md" "$DIR/workflows/criar-fluxograma.global.md"
|
baixar "$BASE/workflows/criar-fluxograma.global.md" "$DIR/workflows/criar-fluxograma.global.md"
|
||||||
baixar "$BASE/workflows/documentar-codigo.global.md" "$DIR/workflows/documentar-codigo.global.md"
|
baixar "$BASE/workflows/otimizar-codigos.global.md" "$DIR/workflows/otimizar-codigos.global.md"
|
||||||
baixar "$BASE/workflows/comercial.global.md" "$DIR/workflows/comercial.global.md"
|
baixar "$BASE/workflows/comercial.global.md" "$DIR/workflows/comercial.global.md"
|
||||||
baixar "$BASE/workflows/marketing.global.md" "$DIR/workflows/marketing.global.md"
|
baixar "$BASE/workflows/marketing.global.md" "$DIR/workflows/marketing.global.md"
|
||||||
baixar "$BASE/workflows/orcamento.global.md" "$DIR/workflows/orcamento.global.md"
|
baixar "$BASE/workflows/orcamento.global.md" "$DIR/workflows/orcamento.global.md"
|
||||||
baixar "$BASE/workflows/reels.global.md" "$DIR/workflows/reels.global.md"
|
baixar "$BASE/workflows/reels.global.md" "$DIR/workflows/reels.global.md"
|
||||||
baixar "$BASE/workflows/documentar-regras-negocio.global.md" "$DIR/workflows/documentar-regras-negocio.global.md"
|
baixar "$BASE/workflows/documentar-regras-negocio.global.md" "$DIR/workflows/documentar-regras-negocio.global.md"
|
||||||
|
baixar "$BASE/workflows/ux.global.md" "$DIR/workflows/ux.global.md"
|
||||||
|
|
||||||
|
|
||||||
# skills
|
# skills
|
||||||
|
|
|
||||||
60
.agent/rules/vue-limite-linhas.global.md
Normal file
60
.agent/rules/vue-limite-linhas.global.md
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
# Regra: Limite de Linhas em Arquivos Vue
|
||||||
|
|
||||||
|
## 🎯 Regra
|
||||||
|
|
||||||
|
**Nenhum arquivo `*.vue` deve ultrapassar 250 linhas.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Motivação
|
||||||
|
|
||||||
|
Arquivos Vue longos acumulam múltiplas responsabilidades:
|
||||||
|
|
||||||
|
- template, lógica e estilo no mesmo arquivo
|
||||||
|
- componentes internos não reutilizáveis
|
||||||
|
- dificuldade de teste e manutenção
|
||||||
|
|
||||||
|
O limite de 250 linhas é uma fronteira de manutenibilidade, não apenas de estética.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Aplicação automática
|
||||||
|
|
||||||
|
O script `scripts/check-vue-linhas.sh` valida este limite em todo `pnpm run build`.
|
||||||
|
Arquivos legados em processo de refatoração estão listados como **isentos temporários** dentro do script.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Obrigações do agente
|
||||||
|
|
||||||
|
### Antes de criar um novo arquivo Vue
|
||||||
|
|
||||||
|
- Estimar o tamanho do componente
|
||||||
|
- Se a estimativa ultrapassar 250 linhas, **quebrar em subcomponentes imediatamente**
|
||||||
|
|
||||||
|
### Ao editar um arquivo Vue existente
|
||||||
|
|
||||||
|
- Verificar o total de linhas atual antes de adicionar código
|
||||||
|
- Se a edição levar o arquivo acima de 250 linhas, propor extração de subcomponente
|
||||||
|
|
||||||
|
### Ao identificar um arquivo isento
|
||||||
|
|
||||||
|
- Sinalizar ao operador que o arquivo é legado e deve ser refatorado
|
||||||
|
- Não adicionar mais linhas a arquivos isentos sem refatorar algo equivalente
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Estratégias de extração obrigatórias
|
||||||
|
|
||||||
|
Quando um arquivo se aproximar do limite:
|
||||||
|
|
||||||
|
| Situação | Ação |
|
||||||
|
|---|---|
|
||||||
|
| Template longo | Extrair seções como `<NomeSecao />` próprio componente |
|
||||||
|
| CSS extenso | Mover para arquivo `.css` co-localizado e importar |
|
||||||
|
| Setup() > 100 linhas | Extrair lógica para composable `use*.ts` co-localizado |
|
||||||
|
| Modal/Dialog embutido | Sempre em componente separado (`*Modal.vue`) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
> Não existem isentos. Todos os arquivos Vue da biblioteca respeitam o limite de 250 linhas.
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
---
|
|
||||||
description: Workflow para criação e padronização de documentação em código (JSDoc, comentários e tipagem)
|
|
||||||
---
|
|
||||||
|
|
||||||
# Workflow: Documentar Código
|
|
||||||
|
|
||||||
## 🎯 Objetivo
|
|
||||||
Estabelecer um padrão global e unificado para a redação de documentação em código (comentários, `JSDoc` e explicações arquiteturais) em todas as aplicações, garantindo clareza sem prolixidade e respeitando a política de tipagem explícita.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1️⃣ Princípios Básicos de Documentação
|
|
||||||
- **Por que, não o Quê**: Documente o raciocínio por trás da decisão arquitetural ou a regra de negócio que forçou a estratégia, não o mecanismo óbvio que o TypeScript nativamente expressaria.
|
|
||||||
- **Nomenclatura Limpa**: Confie na auto-documentação de nomes de variáveis expressivos baseados no domínio antes de escrever um comentário de texto livre.
|
|
||||||
|
|
||||||
## 2️⃣ Funções Compartilhadas e JSDoc (Obrigatória)
|
|
||||||
Toda função central ou de uso compartilhado, independente da aplicação (frontend, backend, mobile ou bibliotecas comuns), deve portar cabeçalho em `JSDoc`.
|
|
||||||
|
|
||||||
### Padrão para JSDoc
|
|
||||||
```typescript
|
|
||||||
/**
|
|
||||||
* Busca os detalhes de uma entidade baseando-se no ID fornecido e calcula privilégios aplicáveis.
|
|
||||||
* Não utilizar em fluxos de processamento em lote para evitar sobrecarga de requisições ou banco.
|
|
||||||
*
|
|
||||||
* @param tenantId - Identificador único do cliente/tenant no sistema
|
|
||||||
* @param entityId - ID exclusivo identificando o registro na base de dados
|
|
||||||
* @returns Retorna o objeto consolidado com dados ou `null` caso não seja encontrado
|
|
||||||
*/
|
|
||||||
export const obterDetalhesEntidade = async (tenantId: string, entityId: string) => { ... }
|
|
||||||
```
|
|
||||||
|
|
||||||
## 3️⃣ Tratamento de Exceções e Casts Temporários
|
|
||||||
Devido à política `TypeScript sem any`, não utilizamos conversores obscuros imperativamente.
|
|
||||||
Porém, se uma coerção agressiva (`as unknown as X`) demonstrar-se temporariamente necessária por divergência ou limitação de SDK terceiro, **o comentário justificando a anomalia é obrigatório**.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// Necessário cast duplo pois a biblioteca externa não provê tipagem genérica adequada na versão atual
|
|
||||||
const dados = payload.raw as unknown as RespostaFormatada
|
|
||||||
```
|
|
||||||
|
|
||||||
## 4️⃣ Atribuição de TODOs
|
|
||||||
Toda refatoração, debito técnico visualizado ou dúvida de negócio deixada aberta em código de longo prazo **não prescreve** e deve ser acompanhada do formato `// TODO`:
|
|
||||||
- **Tipagem**: `// TODO de tipagem: Criar interface estrita para o retorno da API de listagem (Removendo Partial)`
|
|
||||||
- **Negócio**: `// TODO de regra: Validar com a equipe de Produto se a nova taxa adicional será inserida para clientes antigos.`
|
|
||||||
|
|
||||||
## 5️⃣ Validação do Fluxo
|
|
||||||
Sempre ao propor documentação de um arquivo novo ou refatorado, valide via checklist:
|
|
||||||
* [ ] A documentação está aderente sem estar redundante à tipagem TS?
|
|
||||||
* [ ] A formatação de parágrafos/espaçamento quebra alguma regra no formatador?
|
|
||||||
* [ ] A sintaxe suporta autocompletion na sua camada dependente (`@params` estão exatos)?
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Referências
|
|
||||||
|
|
||||||
- Rules: `.agent/rules/typescript-sem-any.global.md` (política de tipagem que influencia a documentação)
|
|
||||||
- Skill: `.agent/skills/documentar-decisao-tecnica/SKILL.md` (para decisões arquiteturais que exigem ADR além de JSDoc)
|
|
||||||
|
|
||||||
221
.agent/workflows/otimizar-codigos.global.md
Normal file
221
.agent/workflows/otimizar-codigos.global.md
Normal file
|
|
@ -0,0 +1,221 @@
|
||||||
|
---
|
||||||
|
description: Otimizar código e documentar padrões de qualidade (estrutura, limites, JSDoc e tipagem)
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Workflow: Qualidade e Documentação de Código
|
||||||
|
|
||||||
|
## 🎯 Objetivo
|
||||||
|
|
||||||
|
Unificar as regras de **estrutura de arquivos**, **otimização** e **documentação** em um único fluxo de referência para o projeto `eli-vue`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1️⃣ Limite de Linhas por Arquivo
|
||||||
|
|
||||||
|
Nenhum arquivo `*.vue`, `*.ts` ou `*.tsx` pode ultrapassar **250 linhas**.
|
||||||
|
|
||||||
|
Essa regra existe para facilitar a leitura humana, a revisão de código e a manutenção a longo prazo.
|
||||||
|
|
||||||
|
> O script `scripts/check-vue-linhas.sh` valida automaticamente os arquivos `.vue` no `prebuild`.
|
||||||
|
|
||||||
|
**Estratégias quando o limite se aproxima:**
|
||||||
|
|
||||||
|
| Situação | Ação |
|
||||||
|
|---|---|
|
||||||
|
| Template longo | Extrair seções como subcomponentes |
|
||||||
|
| CSS extenso | Mover para `<nome>.css` co-localizado |
|
||||||
|
| `setup()` extenso | Extrair subcomponentes, CSS ou Dispatch Table |
|
||||||
|
| Modal/Dialog embutido | Sempre em componente separado (`*Modal.vue`) |
|
||||||
|
| Handlers e dispatch | Extrair para `handlers.ts` ou `dispatch.ts` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2️⃣ Estrutura de Componentes Vue
|
||||||
|
|
||||||
|
### `defineComponent` obrigatório
|
||||||
|
|
||||||
|
Todo componente Vue deve usar `defineComponent` para garantir inferência de tipos correta e consistência.
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'MeuComponente',
|
||||||
|
setup() {
|
||||||
|
// lógica aqui
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Por que é importante:**
|
||||||
|
- Inferência de tipos TypeScript correta dentro do componente
|
||||||
|
- Reconhecimento por ferramentas de análise estática e IDEs
|
||||||
|
- Compatível com Options API e Composition API com `setup()`
|
||||||
|
- Padronização entre todos os componentes do projeto
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `setup()` não pode ser externo
|
||||||
|
|
||||||
|
O `setup()` deve estar sempre dentro do componente `.vue`. Não é permitido extrair ou delegar o `setup` para nenhum arquivo externo.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// ❌ Proibido — setup delegado inteiramente para arquivo externo
|
||||||
|
// useEliTabela.ts
|
||||||
|
export function useEliTabela(props, emit) { ... }
|
||||||
|
|
||||||
|
// EliTabela.vue
|
||||||
|
export default defineComponent({
|
||||||
|
setup(props, { emit }) {
|
||||||
|
return useEliTabela(props, emit) // setup é apenas um proxy vazio
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// ✅ Correto — setup com lógica própria dentro do componente
|
||||||
|
// EliTabela.vue
|
||||||
|
export default defineComponent({
|
||||||
|
setup(props, { emit }) {
|
||||||
|
const estado = ref(null)
|
||||||
|
const carregar = async () => { ... }
|
||||||
|
return { estado, carregar }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
> Composables `use*.ts` podem encapsular lógica reutilizável pontual (ex: `useFormatador`, `usePermissao`), mas nunca substituir ou esvaziar o `setup()` do componente. Se o componente ultrapassar 250 linhas, a solução é extrair subcomponentes, mover CSS ou usar Dispatch Table — não esvaziar o `setup`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Desassociação de CSS
|
||||||
|
|
||||||
|
A separação de estilos em arquivo externo é permitida e recomendada quando o CSS contribui significativamente para o total de linhas.
|
||||||
|
|
||||||
|
**Padrão de nomenclatura:**
|
||||||
|
|
||||||
|
```
|
||||||
|
MeuComponente.vue
|
||||||
|
MeuComponente.css
|
||||||
|
```
|
||||||
|
|
||||||
|
O `.vue` importa o arquivo CSS no script:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import './MeuComponente.css'
|
||||||
|
```
|
||||||
|
|
||||||
|
Ou via tag `<style>`:
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<style src="./MeuComponente.css" />
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Dispatch Table em Arquivo Separado
|
||||||
|
|
||||||
|
O uso de Dispatch Table (mapa de funções) em arquivos separados é bem-vindo para eliminar blocos longos de `if/else` ou `switch`.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// handlers.ts
|
||||||
|
export const acaoHandlers: Record<string, () => void> = {
|
||||||
|
salvar: () => { ... },
|
||||||
|
cancelar: () => { ... },
|
||||||
|
excluir: () => { ... },
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// MeuComponente.vue
|
||||||
|
import { acaoHandlers } from './handlers'
|
||||||
|
|
||||||
|
function executar(acao: string) {
|
||||||
|
acaoHandlers[acao]?.()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3️⃣ Documentação em Código (JSDoc)
|
||||||
|
|
||||||
|
### Princípios básicos
|
||||||
|
|
||||||
|
- **Por que, não o Quê**: Documente o raciocínio por trás da decisão arquitetural, não o mecanismo óbvio que o TypeScript já expressa.
|
||||||
|
- **Nomenclatura limpa**: Confie na auto-documentação de nomes expressivos antes de escrever comentários de texto livre.
|
||||||
|
|
||||||
|
### Funções compartilhadas — JSDoc obrigatório
|
||||||
|
|
||||||
|
Toda função central ou de uso compartilhado (frontend, backend, mobile ou bibliotecas comuns) deve ter cabeçalho em `JSDoc`.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
/**
|
||||||
|
* Busca os detalhes de uma entidade baseando-se no ID fornecido e calcula privilégios aplicáveis.
|
||||||
|
* Não utilizar em fluxos de processamento em lote para evitar sobrecarga de requisições.
|
||||||
|
*
|
||||||
|
* @param tenantId - Identificador único do cliente/tenant no sistema
|
||||||
|
* @param entityId - ID exclusivo identificando o registro na base de dados
|
||||||
|
* @returns Retorna o objeto consolidado com dados ou `null` caso não seja encontrado
|
||||||
|
*/
|
||||||
|
export const obterDetalhesEntidade = async (tenantId: string, entityId: string) => { ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4️⃣ Casts Temporários e Exceções de Tipagem
|
||||||
|
|
||||||
|
Devido à política **TypeScript sem any**, coerções agressivas requerem justificativa obrigatória em comentário.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Necessário cast duplo pois a biblioteca externa não provê tipagem genérica adequada na versão atual
|
||||||
|
const dados = payload.raw as unknown as RespostaFormatada
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5️⃣ TODOs Estruturados
|
||||||
|
|
||||||
|
Toda dívida técnica ou dúvida de negócio deixada em código deve seguir o formato `// TODO`:
|
||||||
|
|
||||||
|
- **Tipagem**: `// TODO de tipagem: Criar interface estrita para o retorno da API de listagem`
|
||||||
|
- **Negócio**: `// TODO de regra: Validar com Produto se a taxa adicional se aplica a clientes antigos`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6️⃣ Resumo das Regras
|
||||||
|
|
||||||
|
| Regra | Permitido | Proibido |
|
||||||
|
|---|---|---|
|
||||||
|
| Tamanho de arquivo | Até 250 linhas | Acima de 250 linhas |
|
||||||
|
| `defineComponent` | Obrigatório em todo `.vue` | Componentes sem `defineComponent` |
|
||||||
|
| `setup()` externo | — | Proibido em qualquer forma |
|
||||||
|
| CSS externo | `<nome>.css` vinculado ao `.vue` | CSS inline quando ultrapassa o limite |
|
||||||
|
| Dispatch Table | Em arquivo separado | — |
|
||||||
|
| JSDoc | Obrigatório em funções compartilhadas | Omitir em funções de uso amplo |
|
||||||
|
| Casts `as any` | Com `biome-ignore` justificado | Sem comentário explicativo |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7️⃣ Checklist de Validação
|
||||||
|
|
||||||
|
Antes de concluir qualquer refatoração ou adição de código:
|
||||||
|
|
||||||
|
- [ ] O arquivo resultante tem menos de 250 linhas?
|
||||||
|
- [ ] Funções compartilhadas têm `JSDoc` com `@param` e `@returns`?
|
||||||
|
- [ ] Casts temporários têm comentário justificando?
|
||||||
|
- [ ] TODOs estão no formato padronizado?
|
||||||
|
- [ ] A documentação explica o **porquê**, não apenas o **o quê**?
|
||||||
|
- [ ] `defineComponent` está presente em todo componente `.vue`?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Referências
|
||||||
|
|
||||||
|
- Script: `scripts/check-vue-linhas.sh` (validação automática de tamanho)
|
||||||
|
- Rule: `.agent/rules/vue-limite-linhas.global.md` (política de limite de linhas)
|
||||||
|
- Rule: `.agent/rules/typescript-sem-any.global.md` (política de tipagem)
|
||||||
|
- Skill: `.agent/skills/documentar-decisao-tecnica/SKILL.md` (ADRs para decisões arquiteturais)
|
||||||
289
.agent/workflows/ux.global.md
Normal file
289
.agent/workflows/ux.global.md
Normal file
|
|
@ -0,0 +1,289 @@
|
||||||
|
---
|
||||||
|
description: Revisar e otimizar UX de componentes e telas Vue (Frontend)
|
||||||
|
---
|
||||||
|
|
||||||
|
# Workflow: Revisão de UX
|
||||||
|
|
||||||
|
Guia de revisão e otimização de interfaces Vue 3 + Vuetify 3 no ecossistema Azteca — para usuários corporativos com baixa familiaridade tecnológica.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Perfil do Usuário
|
||||||
|
|
||||||
|
O público-alvo das interfaces é corporativo e diversificado em experiência técnica:
|
||||||
|
|
||||||
|
- Gestores públicos e ambientais
|
||||||
|
- Técnicos de campo
|
||||||
|
- Consultores ambientais
|
||||||
|
- Operadores administrativos
|
||||||
|
|
||||||
|
**Premissa central**: o usuário não tem familiaridade avançada com tecnologia. A interface precisa ser autoexplicativa, sem dependência de treinamento prévio.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Paleta de Cores Institucional
|
||||||
|
|
||||||
|
Cor base: `#688C00` (verde floresta). Três escalas derivadas: **Verde floresta** (primária), **Âmbar terroso** (apoio/aviso), **Cinza musgo** (neutros). Semânticas independentes para garantir reconhecimento universal.
|
||||||
|
|
||||||
|
### Escala primária — Verde floresta
|
||||||
|
|
||||||
|
| Token | Hex | Stop | Uso |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `--cor-eli-primaria-bg` | `#EEF3D0` | 50 | Fundo de cards destacados, linhas ativas, hover suave |
|
||||||
|
| `--cor-eli-primaria-zebra` | `#F6F9E8` | 30 | Linha zebrada de tabelas (quase branco esverdeado) |
|
||||||
|
| `--cor-eli-primaria-suave` | `#BFC99C` | — | **Cabeçalho de tabela** — sage/olive mudo, sem limão, texto escuro |
|
||||||
|
| `--cor-eli-primaria-clara` | `#D4E290` | 100 | Badges, chips, seleção, fundo de hover em listas |
|
||||||
|
| `--cor-eli-primaria-media` | `#ADCC44` | 200 | Tags, indicadores secundários |
|
||||||
|
| `--cor-eli-primaria-acao` | `#8AAF00` | 400 | Ação secundária, ícones ativos |
|
||||||
|
| `--cor-eli-primaria` | `#688C00` | 600 | **Botões principais, links ativos, destaques — CTA principal** |
|
||||||
|
| `--cor-eli-primaria-escura` | `#4A6500` | 800 | Hover em botões, bordas ativas, títulos com ênfase |
|
||||||
|
| `--cor-eli-primaria-texto` | `#2E3F00` | 900 | Texto escuro sobre fundos claros da paleta primária |
|
||||||
|
|
||||||
|
### Escala de apoio — Âmbar terroso
|
||||||
|
|
||||||
|
| Token | Hex | Stop | Uso |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `--cor-eli-alerta-bg` | `#FAF3DF` | 50 | Fundo de banners de aviso |
|
||||||
|
| `--cor-eli-alerta-clara` | `#F5DC8A` | 100 | Badges de atenção, chips de alerta |
|
||||||
|
| `--cor-eli-alerta` | `#DDB847` | 200 | Ícones de aviso, bordas de alerta |
|
||||||
|
| `--cor-eli-alerta-forte` | `#8C6A00` | 600 | Texto sobre fundo de alerta, status pendente |
|
||||||
|
|
||||||
|
### Escala neutra — Cinza musgo
|
||||||
|
|
||||||
|
| Token | Hex | Stop | Uso |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `--cor-eli-fundo` | `#F2F3EE` | 50 | Background geral de páginas |
|
||||||
|
| `--cor-eli-borda` | `#D5D8CC` | 100 | Bordas padrão de containers |
|
||||||
|
| `--cor-eli-borda-forte` | `#B5BAA8` | 200 | Bordas de foco, divisores |
|
||||||
|
| `--cor-eli-neutro` | `#5E6553` | 600 | Textos secundários, ícones inativos |
|
||||||
|
| `--cor-eli-neutro-escuro` | `#3E4436` | 800 | Texto de label, cabeçalhos de tabela |
|
||||||
|
| `--cor-eli-superficie` | `#FFFFFF` | — | Cards, modais, popovers |
|
||||||
|
|
||||||
|
### Cores semânticas
|
||||||
|
|
||||||
|
| Token | Hex | Uso |
|
||||||
|
|---|---|---|
|
||||||
|
| `--cor-eli-sucesso` | `#2e7d32` | Confirmações, status positivo |
|
||||||
|
| `--cor-eli-sucesso-bg` | `#E8F5E9` | Fundo de mensagens de sucesso |
|
||||||
|
| `--cor-eli-erro` | `#c62828` | Erros, ações destrutivas |
|
||||||
|
| `--cor-eli-erro-bg` | `#FFEBEE` | Fundo de mensagens de erro |
|
||||||
|
| `--cor-eli-info` | `#1565c0` | Informações auxiliares |
|
||||||
|
| `--cor-eli-info-bg` | `#E3F2FD` | Fundo de banners informativos |
|
||||||
|
|
||||||
|
### Regras de contraste
|
||||||
|
|
||||||
|
- Texto sobre `--cor-eli-primaria` (`#688C00`) deve ser **branco** (`#ffffff`) — contraste 4.6:1 ✓
|
||||||
|
- Texto sobre `--cor-eli-primaria-bg` (`#EEF3D0`) deve ser `--cor-eli-primaria-texto` (`#2E3F00`) — contraste 12:1 ✓
|
||||||
|
- Texto sobre `--cor-eli-primaria-escura` (`#4A6500`) deve ser **branco** (`#ffffff`) — contraste 7.1:1 ✓
|
||||||
|
- Texto sobre `--cor-eli-alerta-bg` (`#FAF3DF`) deve ser `--cor-eli-alerta-forte` (`#8C6A00`) — contraste 5.2:1 ✓
|
||||||
|
- Texto sobre `--cor-eli-primaria-suave` (`#BFC99C`) deve ser `--cor-eli-neutro-escuro` (`#3E4436`) — contraste ~12:1 ✓
|
||||||
|
- **⚠️ Efeito limão**: `--cor-eli-media` (`#ADCC44`) e `--cor-eli-acao` (`#8AAF00`) têm saturação 100% no hue amarelo-verde — **nunca usar como fundo de cabeçalho**. Para fundo verde mudo, usar `--cor-eli-primaria-suave`.
|
||||||
|
- Sempre validar contraste mínimo de **4.5:1** (WCAG AA) para texto normal e **3:1** para texto grande (18px+ ou 14px bold).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Passos
|
||||||
|
|
||||||
|
### 1. Identificar o escopo da revisão
|
||||||
|
|
||||||
|
Determinar qual componente, tela ou modal será revisado. Pode ser:
|
||||||
|
|
||||||
|
- Um componente isolado (ex: `EliTabela`, `EliCartao`)
|
||||||
|
- Uma página completa (ex: `PaginaCadastro.vue`)
|
||||||
|
- Um modal ou drawer (ex: `EliTabelaModalFiltroAvancado.vue`)
|
||||||
|
- Uma interação específica (ex: fluxo de filtro, paginação, menu de ações)
|
||||||
|
|
||||||
|
### 2. Mapear o estado atual
|
||||||
|
|
||||||
|
Antes de alterar, entender o que já existe:
|
||||||
|
|
||||||
|
- Ler o template (`<template>`) para entender a estrutura visual
|
||||||
|
- Ler o CSS/SCSS associado para mapear os tokens de design em uso
|
||||||
|
- Identificar quais componentes Vuetify estão sendo utilizados (ex: `v-btn`, `v-dialog`, `v-select`)
|
||||||
|
- Verificar se existem estilos inline que deveriam migrar para classes CSS
|
||||||
|
|
||||||
|
### 3. Avaliar contra os critérios de UX
|
||||||
|
|
||||||
|
Para cada elemento da interface, avaliar:
|
||||||
|
|
||||||
|
#### Clareza
|
||||||
|
|
||||||
|
- [ ] O propósito de cada elemento é óbvio sem instrução?
|
||||||
|
- [ ] Labels e placeholders são descritivos? (ex: "Buscar por nome ou CPF" vs "Buscar")
|
||||||
|
- [ ] Ícones acompanham texto quando a ação não é universalmente reconhecível?
|
||||||
|
- [ ] O estado vazio (empty state) orienta o usuário sobre o próximo passo?
|
||||||
|
|
||||||
|
#### Hierarquia visual
|
||||||
|
|
||||||
|
- [ ] O olho do usuário é guiado para a ação principal primeiro?
|
||||||
|
- [ ] Ações primárias usam `--cor-eli-primaria` (`#688C00`) com destaque?
|
||||||
|
- [ ] Ações secundárias são visuais mas não competem com a primária?
|
||||||
|
- [ ] Ações destrutivas (Excluir, Limpar) usam `--cor-eli-erro` com variante `text` ou `outlined`?
|
||||||
|
|
||||||
|
#### Feedback e estados
|
||||||
|
|
||||||
|
- [ ] Botões têm estados claros de hover (`--cor-eli-primaria-escura`), focus e disabled (`--cor-eli-borda-forte`)?
|
||||||
|
- [ ] Carregamentos exibem indicador visual (spinner, skeleton, barra de progresso)?
|
||||||
|
- [ ] Erros de formulário aparecem próximos ao campo com mensagem descritiva, usando `--cor-eli-erro` + `--cor-eli-erro-bg`?
|
||||||
|
- [ ] Sucesso de operações tem confirmação visual (snackbar, toast, badge) com `--cor-eli-sucesso`?
|
||||||
|
|
||||||
|
#### Densidade e espaçamento
|
||||||
|
|
||||||
|
- [ ] O espaçamento segue múltiplos de 4px (4, 8, 12, 16, 24, 32)?
|
||||||
|
- [ ] Em desktop, o espaço horizontal é bem aproveitado? (ex: grids de 2+ colunas para formulários largos)
|
||||||
|
- [ ] Em mobile, os elementos empilham verticalmente sem quebra visual?
|
||||||
|
- [ ] Modais têm largura proporcional ao conteúdo?
|
||||||
|
|
||||||
|
#### Tipografia
|
||||||
|
|
||||||
|
- [ ] Tamanhos de fonte seguem a escala: 12px (caption), 14px (body), 16px (subtitle), 20px (title), 24px (heading)?
|
||||||
|
- [ ] Textos longos usam `line-height: 1.5` para legibilidade?
|
||||||
|
- [ ] Labels de formulário são visualmente distintos do conteúdo preenchido?
|
||||||
|
|
||||||
|
#### Acessibilidade básica
|
||||||
|
|
||||||
|
- [ ] Elementos interativos têm `aria-label` quando o texto visual não é suficiente?
|
||||||
|
- [ ] Foco via teclado (`Tab`) segue uma ordem lógica?
|
||||||
|
- [ ] Cores não são o único meio de comunicar estado (ex: ícone + cor para erro)?
|
||||||
|
- [ ] Todo texto sobre cor primária ou escura usa branco (`#FFFFFF`)?
|
||||||
|
- [ ] Todo texto sobre `--cor-eli-primaria-bg` usa `--cor-eli-primaria-texto` (`#2E3F00`)?
|
||||||
|
|
||||||
|
### 4. Aplicar padrões Vue/Vuetify do projeto
|
||||||
|
|
||||||
|
Ao implementar melhorias, seguir os padrões do ecossistema:
|
||||||
|
|
||||||
|
#### Componentes Vuetify
|
||||||
|
|
||||||
|
- Usar `v-btn` com variantes do tema (`color="primary"`) em vez de classes CSS manuais para botões
|
||||||
|
- Usar `v-dialog` para modais, `v-menu` para popovers contextuais
|
||||||
|
- Usar `v-text-field`, `v-select` etc. para inputs sempre que possível
|
||||||
|
- Aproveitar props de Vuetify: `density`, `variant`, `rounded`, `elevation`
|
||||||
|
|
||||||
|
#### Iconografia — Lucide (obrigatório)
|
||||||
|
|
||||||
|
Todos os ícones do projeto devem vir exclusivamente da biblioteca **`@lucide/vue`** (dependência já instalada no `package.json`).
|
||||||
|
|
||||||
|
**Proibido**:
|
||||||
|
- SVG inline manual (hardcoded no template)
|
||||||
|
- Ícones de `@mdi/font` ou Material Design Icons em componentes do projeto
|
||||||
|
- Bibliotecas alternativas (Font Awesome, Heroicons, etc.)
|
||||||
|
|
||||||
|
**Como usar**:
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script lang="ts">
|
||||||
|
import { Filter, Plus, X, Trash2, Eye } from "@lucide/vue"
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<!-- Uso direto como componente -->
|
||||||
|
<Filter :size="18" :stroke-width="2" />
|
||||||
|
|
||||||
|
<!-- Dentro de botão -->
|
||||||
|
<button>
|
||||||
|
<Plus :size="15" :stroke-width="2.5" />
|
||||||
|
Adicionar
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Props úteis**: `:size` (número em px), `:stroke-width` (espessura do traço, padrão 2), `:color` (cor do ícone).
|
||||||
|
|
||||||
|
**Catálogo de referência**: https://lucide.dev/icons — buscar pelo nome do ícone em inglês.
|
||||||
|
|
||||||
|
#### CSS do projeto
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* Escala primária — Verde floresta */
|
||||||
|
--cor-eli-primaria-bg: #EEF3D0;
|
||||||
|
--cor-eli-primaria-zebra: #F6F9E8;
|
||||||
|
--cor-eli-primaria-suave: #BFC99C;
|
||||||
|
--cor-eli-primaria-clara: #D4E290;
|
||||||
|
--cor-eli-primaria-media: #ADCC44;
|
||||||
|
--cor-eli-primaria-acao: #8AAF00;
|
||||||
|
--cor-eli-primaria: #688C00;
|
||||||
|
--cor-eli-primaria-escura: #4A6500;
|
||||||
|
--cor-eli-primaria-texto: #2E3F00;
|
||||||
|
|
||||||
|
/* Escala de apoio — Âmbar terroso */
|
||||||
|
--cor-eli-alerta-bg: #FAF3DF;
|
||||||
|
--cor-eli-alerta-clara: #F5DC8A;
|
||||||
|
--cor-eli-alerta: #DDB847;
|
||||||
|
--cor-eli-alerta-forte: #8C6A00;
|
||||||
|
|
||||||
|
/* Escala neutra — Cinza musgo */
|
||||||
|
--cor-eli-fundo: #F2F3EE;
|
||||||
|
--cor-eli-borda: #D5D8CC;
|
||||||
|
--cor-eli-borda-forte: #B5BAA8;
|
||||||
|
--cor-eli-neutro: #5E6553;
|
||||||
|
--cor-eli-neutro-escuro: #3E4436;
|
||||||
|
--cor-eli-superficie: #FFFFFF;
|
||||||
|
|
||||||
|
/* Semânticas */
|
||||||
|
--cor-eli-sucesso: #2E7D32;
|
||||||
|
--cor-eli-sucesso-bg: #E8F5E9;
|
||||||
|
--cor-eli-erro: #C62828;
|
||||||
|
--cor-eli-erro-bg: #FFEBEE;
|
||||||
|
--cor-eli-info: #1565C0;
|
||||||
|
--cor-eli-info-bg: #E3F2FD;
|
||||||
|
```
|
||||||
|
|
||||||
|
> **⚠️ Regra crítica para componentes distribuídos como biblioteca**: todo token CSS novo (`--cor-eli-primaria-zebra`, `--cor-eli-primaria-suave`, etc.) deve ter **fallback hardcoded** no CSS do componente: `var(--token, #valorhex)`. Projetos destino podem não ter a versão mais recente do `design-tokens.css` e a variável resolverá como `undefined`, tornando a propriedade invisível.
|
||||||
|
|
||||||
|
- Classes devem seguir BEM: `eli-componente__elemento--modificador`
|
||||||
|
- Evitar `!important` — resolver especificidade via seletor mais específico
|
||||||
|
- Usar `var(--cor-eli-primaria)` em vez de valores hex hardcoded no CSS dos componentes
|
||||||
|
- Transições padrão: `transition: all 0.2s ease` para hover/focus
|
||||||
|
|
||||||
|
#### Composição de componentes
|
||||||
|
|
||||||
|
- Componentes filhos devem receber dados via `props` e emitir eventos via `$emit`
|
||||||
|
- Evitar acessar o DOM diretamente; preferir `ref` do Vue
|
||||||
|
- Não duplicar CSS entre componentes — extrair para classe utilitária ou CSS do componente pai
|
||||||
|
|
||||||
|
### 5. Implementar melhorias prioritárias
|
||||||
|
|
||||||
|
Ordenar as melhorias por impacto no usuário:
|
||||||
|
|
||||||
|
1. **Crítico**: elementos que causam confusão ou impedem o uso (falta de feedback, botão invisível, estado vazio sem orientação)
|
||||||
|
2. **Alto**: melhorias que reduzem atrito (espaçamento, hierarquia de botões, responsividade)
|
||||||
|
3. **Médio**: refinamentos visuais (transições, micro-animações, consistência de cores)
|
||||||
|
4. **Baixo**: polish (sombras sutis, arredondamentos, tipografia refinada)
|
||||||
|
|
||||||
|
### 6. Validar resultado
|
||||||
|
|
||||||
|
Após implementar:
|
||||||
|
|
||||||
|
- [ ] A tela renderiza sem erros no console?
|
||||||
|
- [ ] Os estados (vazio, carregando, erro, com dados) estão todos cobertos?
|
||||||
|
- [ ] A paleta de cores está coerente com a base `#688C00`?
|
||||||
|
- [ ] Todos os tokens usados estão definidos na escala primária, de apoio ou neutra?
|
||||||
|
- [ ] Nenhuma cor está hardcoded no CSS do componente (usar `var(--token, #fallback)`)?
|
||||||
|
- [ ] Tokens novos têm fallback hardcoded? (ex: `var(--cor-eli-primaria-suave, #BFC99C)`)?
|
||||||
|
- [ ] Contraste de texto validado: branco sobre `--cor-eli-primaria` e `--cor-eli-primaria-escura`; `--cor-eli-neutro-escuro` sobre `--cor-eli-primaria-suave`?
|
||||||
|
- [ ] Nenhuma ação ficou sem feedback visual?
|
||||||
|
- [ ] O layout não quebra em larguras menores que 1024px?
|
||||||
|
- [ ] Componentes Vuetify recebem props de tema (`color`, `variant`) em vez de override CSS?
|
||||||
|
- [ ] Efeito zebra de tabelas tem fundo branco explícito nas linhas normais (para funcionar sobre qualquer fundo de página)?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Anti-padrões a evitar
|
||||||
|
|
||||||
|
| ❌ Anti-padrão | ✅ Alternativa |
|
||||||
|
|---|---|
|
||||||
|
| Botão sem label de texto | Botão com ícone + texto, ou `aria-label` descritivo |
|
||||||
|
| Cor vermelha para ação primária | Vermelho apenas para destrutivas; primária usa `--cor-eli-primaria` |
|
||||||
|
| Modal com scroll interno extenso | Dividir em etapas ou usar drawer lateral |
|
||||||
|
| Tabela com 15+ colunas visíveis | Usar o modal de colunas para esconder as menos usadas |
|
||||||
|
| Placeholder como único label | Label fixo acima do campo + placeholder como exemplo |
|
||||||
|
| Ações alinhadas à esquerda em linha de tabela | Ações à direita, próximas ao fim da linha visual |
|
||||||
|
| Toast/snackbar sem ação de fechar | Sempre incluir botão de fechar ou auto-dismiss com tempo suficiente (5s+) |
|
||||||
|
| Estilos inline em `<template>` | Migrar para `<style scoped>` com classe BEM |
|
||||||
|
| Cor hex hardcoded no componente | Usar `var(--cor-eli-primaria)` e demais tokens com fallback |
|
||||||
|
| Texto escuro (`#333`) sobre `--cor-eli-primaria` | Usar branco (`#FFFFFF`) para garantir contraste WCAG AA |
|
||||||
|
| Amarelo/âmbar para sucesso | Âmbar é reservado para **aviso**; sucesso usa `--cor-eli-sucesso` (`#2E7D32`) |
|
||||||
|
| Misturar stops da paleta fora dos tokens | Usar apenas os tokens definidos; nunca interpolar valores avulsos |
|
||||||
|
| Usar `--cor-eli-media` ou `--cor-eli-acao` como fundo de cabeçalho | Usar `--cor-eli-primaria-suave` (`#BFC99C`) — verde mudo sem efeito limão |
|
||||||
|
| `var(--token)` sem fallback em componente de biblioteca | Sempre declarar `var(--token, #valor)` — o projeto destino pode não ter o token atualizado |
|
||||||
|
| Zebra de tabela sem fundo branco explícito nas linhas normais | Declarar `.eli-tabela__tr .eli-tabela__td { background: var(--cor-eli-superficie, #fff) }` antes da regra zebra |
|
||||||
13476
dist/eli-vue.es.js
vendored
13476
dist/eli-vue.es.js
vendored
File diff suppressed because one or more lines are too long
298
dist/eli-vue.umd.js
vendored
298
dist/eli-vue.umd.js
vendored
File diff suppressed because one or more lines are too long
51
dist/types/componentes/EliEntrada/EliEntradaNumero.helpers.d.ts
vendored
Normal file
51
dist/types/componentes/EliEntrada/EliEntradaNumero.helpers.d.ts
vendored
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
/**
|
||||||
|
* Helpers de formatação e parsing numérico usados pelo EliEntradaNumero.
|
||||||
|
* Extraídos do componente Vue para manter o arquivo dentro de 250 linhas.
|
||||||
|
*/
|
||||||
|
export declare function casasDecimaisFromPrecisao(precisao: number): number;
|
||||||
|
/**
|
||||||
|
* Analisa a entrada de texto e converte em um valor numérico válido (float).
|
||||||
|
* Troca a vírgula brasileira por ponto antes de passar para o parser nativo.
|
||||||
|
*
|
||||||
|
* @param texto - O texto bruto digitado ou vindo do estado
|
||||||
|
* @returns O número processado ou `null` caso inválido ou vazio
|
||||||
|
*/
|
||||||
|
export declare function parseNumero(texto: string): number | null;
|
||||||
|
/**
|
||||||
|
* Formata um valor numérico para a exibição na interface (usando vírgula como separador).
|
||||||
|
*
|
||||||
|
* @param value - O valor numérico de fato emitido
|
||||||
|
* @param casas - Opcionalmente, o número exato de casas decimais para fixar
|
||||||
|
* @returns String formatada amigável para exibição em pt-BR
|
||||||
|
*/
|
||||||
|
export declare function formatarNumero(value: number | null | undefined, casas: number | null): string;
|
||||||
|
/**
|
||||||
|
* Retira todo tipo de sujeira de uma string, devolvendo restritamente números (para inteiros).
|
||||||
|
*
|
||||||
|
* @param texto - String de entrada
|
||||||
|
* @returns Apenas dígitos de 0 a 9
|
||||||
|
*/
|
||||||
|
export declare function somenteNumeros(texto: string): string;
|
||||||
|
/**
|
||||||
|
* Higieniza o texto preservando apenas os dígitos e exatamente UM separador decimal.
|
||||||
|
*
|
||||||
|
* @param texto - Texto misto de entrada
|
||||||
|
* @returns String contendo número e (opcionalmente) uma vírgula ou ponto residual
|
||||||
|
*/
|
||||||
|
export declare function somenteNumerosESeparadorDecimal(texto: string): string;
|
||||||
|
/**
|
||||||
|
* Trunca a parte decimal de um texto numérico ao número máximo de casas permitidas.
|
||||||
|
* Aplicado durante a digitação para evitar que o usuário ultrapasse a precisão configurada.
|
||||||
|
*
|
||||||
|
* @param texto - Texto numérico já higienizado (com separador único)
|
||||||
|
* @param casas - Número máximo de casas decimais; `null` = sem limite
|
||||||
|
* @returns Texto com a parte decimal truncada no limite especificado
|
||||||
|
*/
|
||||||
|
export declare function limitarCasasDecimais(texto: string, casas: number | null): string;
|
||||||
|
/**
|
||||||
|
* Resolve o edge-case de digitação transitória parcial terminada em separador (ex: "1,").
|
||||||
|
*
|
||||||
|
* @param texto - O texto já higienizado, tipicamente terminando em vírgula ou ponto
|
||||||
|
* @returns A conversão parcial estrita ou `null` caso falhe
|
||||||
|
*/
|
||||||
|
export declare function valorParcialDecimal(texto: string): number | null;
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { type PropType } from "vue";
|
import { type PropType } from "vue";
|
||||||
import type { PadroesEntradas } from "./tiposEntradas";
|
import type { PadroesEntradas } from "./tiposEntradas";
|
||||||
|
import "./EliEntradaNumero.css";
|
||||||
type EntradaNumero = PadroesEntradas["numero"];
|
type EntradaNumero = PadroesEntradas["numero"];
|
||||||
declare const _default: typeof __VLS_export;
|
declare const _default: typeof __VLS_export;
|
||||||
export default _default;
|
export default _default;
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1 +1,9 @@
|
||||||
|
/**
|
||||||
|
* Formata um CPF ou CNPJ aplicando a máscara brasileira padrão durante a digitação.
|
||||||
|
* Detecta automaticamente o tipo pelo comprimento (≤11 dígitos = CPF, >11 = CNPJ).
|
||||||
|
* Não valida o dígito verificador — apenas aplica a formatação visual.
|
||||||
|
*
|
||||||
|
* @param v - Valor bruto (pode conter letras, pontos, traços etc.)
|
||||||
|
* @returns String formatada no padrão `000.000.000-00` (CPF) ou `00.000.000/0000-00` (CNPJ)
|
||||||
|
*/
|
||||||
export declare function formatarCpfCnpj(v: string): string;
|
export declare function formatarCpfCnpj(v: string): string;
|
||||||
|
|
|
||||||
533
dist/types/componentes/EliTabela/EliTabela.vue.d.ts
vendored
533
dist/types/componentes/EliTabela/EliTabela.vue.d.ts
vendored
|
|
@ -1,8 +1,17 @@
|
||||||
/** Dependências do Vue (Composition API) */
|
/**
|
||||||
|
* EliTabela
|
||||||
|
* Componente de tabela consultável com busca, paginação, ordenação e ações por linha.
|
||||||
|
* O setup() é orquestrado por `useEliTabela`, que compõe os seguintes sub-composables:
|
||||||
|
* - useEliTabelaConsulta — carregamento, paginação, ordenação, busca
|
||||||
|
* - useEliTabelaFiltrosAvancados — filtros avançados, modal e persistência
|
||||||
|
* - useEliTabelaSelecao — seleção múltipla de linhas
|
||||||
|
* - useEliTabelaColunas — visibilidade, ordem e modal de colunas
|
||||||
|
* - useEliTabelaMenu — menu de ações por linha e posicionamento de popup
|
||||||
|
*/
|
||||||
import { type PropType } from "vue";
|
import { type PropType } from "vue";
|
||||||
import { type EliTabelaColunasConfig } from "./colunasStorage";
|
import "./EliTabela.css";
|
||||||
/** Tipos da configuração/contrato da tabela */
|
import type { tipoEliTabelaConsulta } from "./types-eli-tabela";
|
||||||
import type { parametrosConsulta, tipoEliColuna, tipoEliTabelaConsulta, tipoFiltro } from "./types-eli-tabela";
|
import type { FiltroBase } from "./useFiltroAvancado";
|
||||||
declare const _default: typeof __VLS_export;
|
declare const _default: typeof __VLS_export;
|
||||||
export default _default;
|
export default _default;
|
||||||
declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
||||||
|
|
@ -16,119 +25,19 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
|
||||||
type: PropType<unknown[]>;
|
type: PropType<unknown[]>;
|
||||||
default: () => never[];
|
default: () => never[];
|
||||||
};
|
};
|
||||||
|
/** Ocultar painel de debug lateral (EliTabelaDebug) */
|
||||||
|
esconderDebug: {
|
||||||
|
type: BooleanConstructor;
|
||||||
|
default: boolean;
|
||||||
|
};
|
||||||
}>, {
|
}>, {
|
||||||
isDev: boolean;
|
|
||||||
tabela: import("vue").ComputedRef<tipoEliTabelaConsulta<any>>;
|
|
||||||
carregando: import("vue").Ref<boolean, boolean>;
|
|
||||||
erro: import("vue").Ref<string | null, string | null>;
|
|
||||||
linhas: import("vue").Ref<unknown[], unknown[]>;
|
|
||||||
linhasPaginadas: import("vue").ComputedRef<unknown[]>;
|
|
||||||
filtrosAvancadosAtivos: import("vue").ComputedRef<tipoFiltro<any>[]>;
|
|
||||||
quantidadeFiltrada: import("vue").ComputedRef<number>;
|
|
||||||
quantidade: import("vue").Ref<number, number>;
|
|
||||||
menuAberto: import("vue").Ref<number | null, number | null>;
|
menuAberto: import("vue").Ref<number | null, number | null>;
|
||||||
valorBusca: import("vue").Ref<string, string>;
|
|
||||||
paginaAtual: import("vue").Ref<number, number>;
|
|
||||||
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>;
|
|
||||||
acoesSuperiores: import("vue").ComputedRef<{
|
|
||||||
posicao: "superior" | "inferior";
|
|
||||||
icone?: import("@lucide/vue").LucideIcon;
|
|
||||||
cor?: string;
|
|
||||||
rotulo: string;
|
|
||||||
acao: (parametrosConsulta: parametrosConsulta<any> & {
|
|
||||||
atualizarConsulta: () => Promise<void>;
|
|
||||||
editarLista: (lista: any[]) => Promise<any[]>;
|
|
||||||
}) => void;
|
|
||||||
}[]>;
|
|
||||||
temAcoesSuperiores: import("vue").ComputedRef<boolean>;
|
|
||||||
acoesInferiores: import("vue").ComputedRef<{
|
|
||||||
posicao: "superior" | "inferior";
|
|
||||||
icone?: import("@lucide/vue").LucideIcon;
|
|
||||||
cor?: string;
|
|
||||||
rotulo: string;
|
|
||||||
acao: (parametrosConsulta: parametrosConsulta<any> & {
|
|
||||||
atualizarConsulta: () => Promise<void>;
|
|
||||||
editarLista: (lista: any[]) => Promise<any[]>;
|
|
||||||
}) => void;
|
|
||||||
}[]>;
|
|
||||||
temAcoesInferiores: import("vue").ComputedRef<boolean>;
|
|
||||||
parametrosConsultaAtuais: import("vue").ComputedRef<{
|
|
||||||
atualizarConsulta: () => Promise<void>;
|
|
||||||
editarLista: (novaLista: any[]) => Promise<any[]>;
|
|
||||||
filtros?: import("p-comuns").tipoFiltro26<any>[] | undefined;
|
|
||||||
coluna_ordem?: string | number | symbol | undefined;
|
|
||||||
direcao_ordem?: "asc" | "desc";
|
|
||||||
offSet?: number;
|
|
||||||
limit?: number;
|
|
||||||
texto_busca?: string;
|
|
||||||
}>;
|
|
||||||
temAcoes: import("vue").ComputedRef<boolean>;
|
|
||||||
totalColunas: import("vue").ComputedRef<number>;
|
|
||||||
colunasEfetivas: import("vue").ComputedRef<any[]>;
|
|
||||||
rotulosColunas: import("vue").ComputedRef<string[]>;
|
|
||||||
modalColunasAberto: import("vue").Ref<boolean, boolean>;
|
|
||||||
configColunas: import("vue").Ref<{
|
|
||||||
visiveis: string[];
|
|
||||||
invisiveis: string[];
|
|
||||||
}, EliTabelaColunasConfig | {
|
|
||||||
visiveis: string[];
|
|
||||||
invisiveis: string[];
|
|
||||||
}>;
|
|
||||||
temColunasInvisiveis: import("vue").ComputedRef<boolean>;
|
|
||||||
colunasInvisiveisEfetivas: import("vue").ComputedRef<tipoEliColuna<any>[]>;
|
|
||||||
linhasExpandidas: import("vue").Ref<Record<number, boolean>, Record<number, boolean>>;
|
|
||||||
mostrarSelecao: import("vue").ComputedRef<boolean>;
|
|
||||||
isSelecionado: (linha: unknown) => boolean;
|
|
||||||
alternarSelecao: (linha: unknown) => void;
|
|
||||||
todosSelecionados: import("vue").ComputedRef<boolean>;
|
|
||||||
algunsSelecionados: import("vue").ComputedRef<boolean>;
|
|
||||||
alternarSelecaoTodos: (forcarMarcar?: boolean) => void;
|
|
||||||
abrirModalColunas: () => void;
|
|
||||||
abrirModalFiltro: () => void;
|
|
||||||
fecharModalColunas: () => void;
|
|
||||||
salvarModalColunas: (cfg: EliTabelaColunasConfig) => void;
|
|
||||||
modalFiltroAberto: import("vue").Ref<boolean, boolean>;
|
|
||||||
filtrosUi: import("vue").Ref<{
|
|
||||||
chave: string;
|
|
||||||
valor: any;
|
|
||||||
}[], {
|
|
||||||
chave: string;
|
|
||||||
valor: any;
|
|
||||||
}[] | {
|
|
||||||
chave: string;
|
|
||||||
valor: any;
|
|
||||||
}[]>;
|
|
||||||
salvarFiltrosAvancados: (novo: any[]) => void;
|
|
||||||
limparFiltrosAvancados: () => void;
|
|
||||||
fecharModalFiltro: () => void;
|
|
||||||
alternarLinhaExpandida: (indice: number) => void;
|
|
||||||
alternarOrdenacao: (chave?: string) => void;
|
|
||||||
atualizarBusca: (texto: string) => void;
|
|
||||||
irParaPagina: (pagina: number) => void;
|
|
||||||
acoesDisponiveisPorLinha: (i: number) => {
|
|
||||||
acao: import("./types-eli-tabela").tipoEliTabelaAcao<any>;
|
|
||||||
indice: number;
|
|
||||||
visivel: boolean;
|
|
||||||
}[];
|
|
||||||
possuiAcoes: (i: number) => boolean;
|
|
||||||
toggleMenu: (i: number, evento?: MouseEvent) => void;
|
|
||||||
menuPopup: import("vue").Ref<{
|
menuPopup: import("vue").Ref<{
|
||||||
menuEl: {
|
menuEl: HTMLElement | null;
|
||||||
value: HTMLElement | null;
|
|
||||||
};
|
|
||||||
} | null, {
|
} | null, {
|
||||||
menuEl: {
|
menuEl: HTMLElement | null;
|
||||||
value: HTMLElement | null;
|
|
||||||
};
|
|
||||||
} | {
|
} | {
|
||||||
menuEl: {
|
menuEl: HTMLElement | null;
|
||||||
value: HTMLElement | null;
|
|
||||||
};
|
|
||||||
} | null>;
|
} | null>;
|
||||||
menuPopupPos: import("vue").Ref<{
|
menuPopupPos: import("vue").Ref<{
|
||||||
top: number;
|
top: number;
|
||||||
|
|
@ -140,6 +49,111 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
|
||||||
top: number;
|
top: number;
|
||||||
left: number;
|
left: number;
|
||||||
}>;
|
}>;
|
||||||
|
possuiAcoes: (i: number) => boolean;
|
||||||
|
toggleMenu: (i: number, evento?: MouseEvent) => void;
|
||||||
|
acoesDisponiveisPorLinha: (i: number) => {
|
||||||
|
acao: import("./types-eli-tabela").tipoEliTabelaAcao<any>;
|
||||||
|
indice: number;
|
||||||
|
visivel: boolean;
|
||||||
|
}[];
|
||||||
|
paginaAtual: import("vue").Ref<number, number>;
|
||||||
|
valorBusca: import("vue").Ref<string, string>;
|
||||||
|
modalColunasAberto: import("vue").Ref<boolean, boolean>;
|
||||||
|
configColunas: import("vue").Ref<{
|
||||||
|
visiveis: string[];
|
||||||
|
invisiveis: string[];
|
||||||
|
}, import("./colunasStorage").EliTabelaColunasConfig | {
|
||||||
|
visiveis: string[];
|
||||||
|
invisiveis: string[];
|
||||||
|
}>;
|
||||||
|
linhasExpandidas: import("vue").Ref<Record<number, boolean>, Record<number, boolean>>;
|
||||||
|
rotulosColunas: import("vue").ComputedRef<string[]>;
|
||||||
|
colunasEfetivas: import("vue").ComputedRef<any[]>;
|
||||||
|
colunasInvisiveisEfetivas: import("vue").ComputedRef<import("./types-eli-tabela").tipoEliColuna<any>[]>;
|
||||||
|
temColunasInvisiveis: import("vue").ComputedRef<boolean>;
|
||||||
|
totalColunas: import("vue").ComputedRef<number>;
|
||||||
|
abrirModalColunas: () => void;
|
||||||
|
fecharModalColunas: () => void;
|
||||||
|
salvarModalColunas: (cfg: import("./colunasStorage").EliTabelaColunasConfig) => void;
|
||||||
|
alternarLinhaExpandida: (indice: number) => void;
|
||||||
|
mostrarSelecao: import("vue").ComputedRef<boolean>;
|
||||||
|
isSelecionado: (linha: unknown) => boolean;
|
||||||
|
alternarSelecao: (linha: unknown) => void;
|
||||||
|
todosSelecionados: import("vue").ComputedRef<boolean>;
|
||||||
|
algunsSelecionados: import("vue").ComputedRef<boolean>;
|
||||||
|
alternarSelecaoTodos: (forcarMarcar?: boolean) => void;
|
||||||
|
filtrosUi: import("vue").Ref<{
|
||||||
|
chave: string;
|
||||||
|
valor: unknown;
|
||||||
|
}[], {
|
||||||
|
chave: string;
|
||||||
|
valor: unknown;
|
||||||
|
}[] | {
|
||||||
|
chave: string;
|
||||||
|
valor: unknown;
|
||||||
|
}[]>;
|
||||||
|
filtrosFormula: import("vue").Ref<string, string>;
|
||||||
|
modalFiltroAberto: import("vue").Ref<boolean, boolean>;
|
||||||
|
exibirFiltroAvancado: import("vue").ComputedRef<boolean>;
|
||||||
|
filtrosAvancadosAtivos: import("vue").ComputedRef<import("p-comuns").tipoFiltroDeltaColunaTipada<any>[]>;
|
||||||
|
abrirModalFiltro: () => void;
|
||||||
|
fecharModalFiltro: () => void;
|
||||||
|
limparFiltrosAvancados: () => void;
|
||||||
|
salvarFiltrosAvancados: (novoLinhas: {
|
||||||
|
chave: string;
|
||||||
|
valor: unknown;
|
||||||
|
}[], novaFormula?: string) => void;
|
||||||
|
isDev: boolean;
|
||||||
|
tabela: import("vue").ComputedRef<tipoEliTabelaConsulta<any>>;
|
||||||
|
carregando: import("vue").Ref<boolean, boolean>;
|
||||||
|
erro: import("vue").Ref<string | null, string | null>;
|
||||||
|
linhas: import("vue").Ref<unknown[], unknown[]>;
|
||||||
|
linhasPaginadas: import("vue").ComputedRef<unknown[]>;
|
||||||
|
quantidade: import("vue").Ref<number, number>;
|
||||||
|
quantidadeFiltrada: import("vue").ComputedRef<number>;
|
||||||
|
acoesVisiveis: import("vue").Ref<boolean[][], boolean[][]>;
|
||||||
|
colunaOrdenacao: import("vue").Ref<string | null, string | null>;
|
||||||
|
direcaoOrdenacao: import("vue").Ref<"desc" | "asc", "desc" | "asc">;
|
||||||
|
registrosPorConsulta: import("vue").ComputedRef<number>;
|
||||||
|
totalPaginas: import("vue").ComputedRef<number>;
|
||||||
|
temAcoes: import("vue").ComputedRef<boolean>;
|
||||||
|
exibirBusca: import("vue").ComputedRef<boolean>;
|
||||||
|
acoesSuperiores: import("vue").ComputedRef<{
|
||||||
|
posicao: "superior" | "inferior";
|
||||||
|
icone?: import("@lucide/vue").LucideIcon;
|
||||||
|
cor?: string;
|
||||||
|
rotulo: string;
|
||||||
|
acao: (parametrosConsulta: import("./types-eli-tabela").parametrosConsulta<any> & {
|
||||||
|
atualizarConsulta: () => Promise<void>;
|
||||||
|
editarLista: (lista: any[]) => Promise<any[]>;
|
||||||
|
}) => void;
|
||||||
|
}[]>;
|
||||||
|
acoesInferiores: import("vue").ComputedRef<{
|
||||||
|
posicao: "superior" | "inferior";
|
||||||
|
icone?: import("@lucide/vue").LucideIcon;
|
||||||
|
cor?: string;
|
||||||
|
rotulo: string;
|
||||||
|
acao: (parametrosConsulta: import("./types-eli-tabela").parametrosConsulta<any> & {
|
||||||
|
atualizarConsulta: () => Promise<void>;
|
||||||
|
editarLista: (lista: any[]) => Promise<any[]>;
|
||||||
|
}) => void;
|
||||||
|
}[]>;
|
||||||
|
temAcoesSuperiores: import("vue").ComputedRef<boolean>;
|
||||||
|
temAcoesInferiores: import("vue").ComputedRef<boolean>;
|
||||||
|
parametrosConsultaAtuais: import("vue").ComputedRef<{
|
||||||
|
atualizarConsulta: () => Promise<void>;
|
||||||
|
editarLista: (novaLista: any[]) => Promise<any[]>;
|
||||||
|
filtros?: import("p-comuns").tipoFiltroDeltaColunaTipada<string>[] | undefined;
|
||||||
|
coluna_ordem?: string | number | symbol | undefined;
|
||||||
|
direcao_ordem?: "asc" | "desc";
|
||||||
|
offSet?: number;
|
||||||
|
limit?: number;
|
||||||
|
texto_busca?: string;
|
||||||
|
}>;
|
||||||
|
carregar: () => Promise<void>;
|
||||||
|
alternarOrdenacao: (chave?: string) => void;
|
||||||
|
atualizarBusca: (texto: string) => void;
|
||||||
|
irParaPagina: (pagina: number) => void;
|
||||||
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, "update:selecionados"[], "update:selecionados", import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, "update:selecionados"[], "update:selecionados", import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
||||||
/** Configuração principal da tabela (colunas, consulta e ações) */
|
/** Configuração principal da tabela (colunas, consulta e ações) */
|
||||||
tabela: {
|
tabela: {
|
||||||
|
|
@ -151,10 +165,16 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
|
||||||
type: PropType<unknown[]>;
|
type: PropType<unknown[]>;
|
||||||
default: () => never[];
|
default: () => never[];
|
||||||
};
|
};
|
||||||
|
/** Ocultar painel de debug lateral (EliTabelaDebug) */
|
||||||
|
esconderDebug: {
|
||||||
|
type: BooleanConstructor;
|
||||||
|
default: boolean;
|
||||||
|
};
|
||||||
}>> & Readonly<{
|
}>> & Readonly<{
|
||||||
"onUpdate:selecionados"?: ((...args: any[]) => any) | undefined;
|
"onUpdate:selecionados"?: ((...args: any[]) => any) | undefined;
|
||||||
}>, {
|
}>, {
|
||||||
selecionados: unknown[];
|
selecionados: unknown[];
|
||||||
|
esconderDebug: boolean;
|
||||||
}, {}, {
|
}, {}, {
|
||||||
EliTabelaCabecalho: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
EliTabelaCabecalho: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
||||||
exibirBusca: {
|
exibirBusca: {
|
||||||
|
|
@ -181,28 +201,10 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
|
||||||
};
|
};
|
||||||
acoesCabecalho: {
|
acoesCabecalho: {
|
||||||
type: PropType<Array<{
|
type: PropType<Array<{
|
||||||
icone
|
icone?: any;
|
||||||
/**
|
cor?: string;
|
||||||
* EliTabela
|
|
||||||
* Componente de tabela consultável com busca, paginação, ordenação e ações por linha.
|
|
||||||
*/
|
|
||||||
/** Enum de códigos de resposta utilizado na consulta */
|
|
||||||
?: any;
|
|
||||||
cor
|
|
||||||
/**
|
|
||||||
* EliTabela
|
|
||||||
* Componente de tabela consultável com busca, paginação, ordenação e ações por linha.
|
|
||||||
*/
|
|
||||||
/** Enum de códigos de resposta utilizado na consulta */
|
|
||||||
?: string;
|
|
||||||
rotulo: string;
|
rotulo: string;
|
||||||
acao: (params
|
acao: (params?: any) => void;
|
||||||
/**
|
|
||||||
* EliTabela
|
|
||||||
* Componente de tabela consultável com busca, paginação, ordenação e ações por linha.
|
|
||||||
*/
|
|
||||||
/** Enum de códigos de resposta utilizado na consulta */
|
|
||||||
?: any) => void;
|
|
||||||
}>>;
|
}>>;
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
|
|
@ -240,28 +242,10 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
|
||||||
};
|
};
|
||||||
acoesCabecalho: {
|
acoesCabecalho: {
|
||||||
type: PropType<Array<{
|
type: PropType<Array<{
|
||||||
icone
|
icone?: any;
|
||||||
/**
|
cor?: string;
|
||||||
* EliTabela
|
|
||||||
* Componente de tabela consultável com busca, paginação, ordenação e ações por linha.
|
|
||||||
*/
|
|
||||||
/** Enum de códigos de resposta utilizado na consulta */
|
|
||||||
?: any;
|
|
||||||
cor
|
|
||||||
/**
|
|
||||||
* EliTabela
|
|
||||||
* Componente de tabela consultável com busca, paginação, ordenação e ações por linha.
|
|
||||||
*/
|
|
||||||
/** Enum de códigos de resposta utilizado na consulta */
|
|
||||||
?: string;
|
|
||||||
rotulo: string;
|
rotulo: string;
|
||||||
acao: (params
|
acao: (params?: any) => void;
|
||||||
/**
|
|
||||||
* EliTabela
|
|
||||||
* Componente de tabela consultável com busca, paginação, ordenação e ações por linha.
|
|
||||||
*/
|
|
||||||
/** Enum de códigos de resposta utilizado na consulta */
|
|
||||||
?: any) => void;
|
|
||||||
}>>;
|
}>>;
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
|
|
@ -273,6 +257,7 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
|
||||||
exibirBotaoColunas: boolean;
|
exibirBotaoColunas: boolean;
|
||||||
exibirBotaoFiltroAvancado: boolean;
|
exibirBotaoFiltroAvancado: boolean;
|
||||||
}, {}, {
|
}, {}, {
|
||||||
|
Columns3: import("@lucide/vue").LucideIcon;
|
||||||
EliTabelaCaixaDeBusca: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
EliTabelaCaixaDeBusca: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
||||||
modelo: {
|
modelo: {
|
||||||
type: StringConstructor;
|
type: StringConstructor;
|
||||||
|
|
@ -297,6 +282,7 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
|
||||||
}, {}, {
|
}, {}, {
|
||||||
Search: import("@lucide/vue").LucideIcon;
|
Search: import("@lucide/vue").LucideIcon;
|
||||||
}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
||||||
|
Filter: import("@lucide/vue").LucideIcon;
|
||||||
}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
||||||
EliTabelaEstados: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
EliTabelaEstados: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
||||||
carregando: {
|
carregando: {
|
||||||
|
|
@ -329,7 +315,11 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
|
||||||
}>> & Readonly<{}>, {
|
}>> & Readonly<{}>, {
|
||||||
erro: string | null;
|
erro: string | null;
|
||||||
mensagemVazio: string | undefined;
|
mensagemVazio: string | undefined;
|
||||||
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
}, {}, {
|
||||||
|
AlertCircle: import("@lucide/vue").LucideIcon;
|
||||||
|
Inbox: import("@lucide/vue").LucideIcon;
|
||||||
|
LoaderCircle: import("@lucide/vue").LucideIcon;
|
||||||
|
}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
||||||
EliTabelaDebug: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
EliTabelaDebug: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
||||||
isDev: {
|
isDev: {
|
||||||
type: BooleanConstructor;
|
type: BooleanConstructor;
|
||||||
|
|
@ -346,7 +336,7 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
|
||||||
}>;
|
}>;
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
}>, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
}>, void, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
||||||
isDev: {
|
isDev: {
|
||||||
type: BooleanConstructor;
|
type: BooleanConstructor;
|
||||||
required: true;
|
required: true;
|
||||||
|
|
@ -367,7 +357,7 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
|
||||||
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
||||||
EliTabelaHead: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
EliTabelaHead: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
||||||
colunas: {
|
colunas: {
|
||||||
type: PropType<Array<tipoEliColuna<any>>>;
|
type: PropType<import("./types-eli-tabela").tipoEliColuna<any>[]>;
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
temAcoes: {
|
temAcoes: {
|
||||||
|
|
@ -401,14 +391,14 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
|
||||||
}>, {
|
}>, {
|
||||||
ArrowUp: import("@lucide/vue").LucideIcon;
|
ArrowUp: import("@lucide/vue").LucideIcon;
|
||||||
ArrowDown: import("@lucide/vue").LucideIcon;
|
ArrowDown: import("@lucide/vue").LucideIcon;
|
||||||
isOrdenavel: (coluna: any) => boolean;
|
isOrdenavel: (coluna: import("./types-eli-tabela").tipoEliColuna<unknown>) => boolean;
|
||||||
emitAlternarOrdenacao: (chave: string) => void;
|
emitAlternarOrdenacao: (chave: string) => void;
|
||||||
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
||||||
alternarOrdenacao(chave: string): boolean;
|
alternarOrdenacao(chave: string): boolean;
|
||||||
"alternar-selecao-todos": () => true;
|
"alternar-selecao-todos": () => true;
|
||||||
}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
||||||
colunas: {
|
colunas: {
|
||||||
type: PropType<Array<tipoEliColuna<any>>>;
|
type: PropType<import("./types-eli-tabela").tipoEliColuna<any>[]>;
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
temAcoes: {
|
temAcoes: {
|
||||||
|
|
@ -453,11 +443,11 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
|
||||||
}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
||||||
EliTabelaBody: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
EliTabelaBody: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
||||||
colunas: {
|
colunas: {
|
||||||
type: PropType<Array<tipoEliColuna<any>>>;
|
type: PropType<import("./types-eli-tabela").tipoEliColuna<any>[]>;
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
colunasInvisiveis: {
|
colunasInvisiveis: {
|
||||||
type: PropType<Array<tipoEliColuna<any>>>;
|
type: PropType<import("./types-eli-tabela").tipoEliColuna<any>[]>;
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
temColunasInvisiveis: {
|
temColunasInvisiveis: {
|
||||||
|
|
@ -469,7 +459,7 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
linhas: {
|
linhas: {
|
||||||
type: PropType<Array<unknown>>;
|
type: PropType<unknown[]>;
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
temAcoes: {
|
temAcoes: {
|
||||||
|
|
@ -509,11 +499,11 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
|
||||||
ChevronDown: import("@lucide/vue").LucideIcon;
|
ChevronDown: import("@lucide/vue").LucideIcon;
|
||||||
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
||||||
colunas: {
|
colunas: {
|
||||||
type: PropType<Array<tipoEliColuna<any>>>;
|
type: PropType<import("./types-eli-tabela").tipoEliColuna<any>[]>;
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
colunasInvisiveis: {
|
colunasInvisiveis: {
|
||||||
type: PropType<Array<tipoEliColuna<any>>>;
|
type: PropType<import("./types-eli-tabela").tipoEliColuna<any>[]>;
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
temColunasInvisiveis: {
|
temColunasInvisiveis: {
|
||||||
|
|
@ -525,7 +515,7 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
linhas: {
|
linhas: {
|
||||||
type: PropType<Array<unknown>>;
|
type: PropType<unknown[]>;
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
temAcoes: {
|
temAcoes: {
|
||||||
|
|
@ -588,22 +578,37 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
|
||||||
/**
|
/**
|
||||||
* EliTabela
|
* EliTabela
|
||||||
* Componente de tabela consultável com busca, paginação, ordenação e ações por linha.
|
* Componente de tabela consultável com busca, paginação, ordenação e ações por linha.
|
||||||
|
* O setup() é orquestrado por `useEliTabela`, que compõe os seguintes sub-composables:
|
||||||
|
* - useEliTabelaConsulta — carregamento, paginação, ordenação, busca
|
||||||
|
* - useEliTabelaFiltrosAvancados — filtros avançados, modal e persistência
|
||||||
|
* - useEliTabelaSelecao — seleção múltipla de linhas
|
||||||
|
* - useEliTabelaColunas — visibilidade, ordem e modal de colunas
|
||||||
|
* - useEliTabelaMenu — menu de ações por linha e posicionamento de popup
|
||||||
*/
|
*/
|
||||||
/** Enum de códigos de resposta utilizado na consulta */
|
|
||||||
?: string;
|
?: string;
|
||||||
icone
|
icone
|
||||||
/**
|
/**
|
||||||
* EliTabela
|
* EliTabela
|
||||||
* Componente de tabela consultável com busca, paginação, ordenação e ações por linha.
|
* Componente de tabela consultável com busca, paginação, ordenação e ações por linha.
|
||||||
|
* O setup() é orquestrado por `useEliTabela`, que compõe os seguintes sub-composables:
|
||||||
|
* - useEliTabelaConsulta — carregamento, paginação, ordenação, busca
|
||||||
|
* - useEliTabelaFiltrosAvancados — filtros avançados, modal e persistência
|
||||||
|
* - useEliTabelaSelecao — seleção múltipla de linhas
|
||||||
|
* - useEliTabelaColunas — visibilidade, ordem e modal de colunas
|
||||||
|
* - useEliTabelaMenu — menu de ações por linha e posicionamento de popup
|
||||||
*/
|
*/
|
||||||
/** Enum de códigos de resposta utilizado na consulta */
|
|
||||||
?: import("@lucide/vue").LucideIcon;
|
?: import("@lucide/vue").LucideIcon;
|
||||||
acao
|
acao
|
||||||
/**
|
/**
|
||||||
* EliTabela
|
* EliTabela
|
||||||
* Componente de tabela consultável com busca, paginação, ordenação e ações por linha.
|
* Componente de tabela consultável com busca, paginação, ordenação e ações por linha.
|
||||||
|
* O setup() é orquestrado por `useEliTabela`, que compõe os seguintes sub-composables:
|
||||||
|
* - useEliTabelaConsulta — carregamento, paginação, ordenação, busca
|
||||||
|
* - useEliTabelaFiltrosAvancados — filtros avançados, modal e persistência
|
||||||
|
* - useEliTabelaSelecao — seleção múltipla de linhas
|
||||||
|
* - useEliTabelaColunas — visibilidade, ordem e modal de colunas
|
||||||
|
* - useEliTabelaMenu — menu de ações por linha e posicionamento de popup
|
||||||
*/
|
*/
|
||||||
/** Enum de códigos de resposta utilizado na consulta */
|
|
||||||
?: () => void;
|
?: () => void;
|
||||||
}[];
|
}[];
|
||||||
} | {
|
} | {
|
||||||
|
|
@ -623,7 +628,7 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
colunasInvisiveis: {
|
colunasInvisiveis: {
|
||||||
type: PropType<Array<tipoEliColuna<any>>>;
|
type: PropType<import("./types-eli-tabela").tipoEliColuna<any>[]>;
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
}>, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
}>, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
||||||
|
|
@ -632,7 +637,7 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
colunasInvisiveis: {
|
colunasInvisiveis: {
|
||||||
type: PropType<Array<tipoEliColuna<any>>>;
|
type: PropType<import("./types-eli-tabela").tipoEliColuna<any>[]>;
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
}>> & Readonly<{}>, {}, {}, {
|
}>> & Readonly<{}>, {}, {}, {
|
||||||
|
|
@ -661,22 +666,37 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
|
||||||
/**
|
/**
|
||||||
* EliTabela
|
* EliTabela
|
||||||
* Componente de tabela consultável com busca, paginação, ordenação e ações por linha.
|
* Componente de tabela consultável com busca, paginação, ordenação e ações por linha.
|
||||||
|
* O setup() é orquestrado por `useEliTabela`, que compõe os seguintes sub-composables:
|
||||||
|
* - useEliTabelaConsulta — carregamento, paginação, ordenação, busca
|
||||||
|
* - useEliTabelaFiltrosAvancados — filtros avançados, modal e persistência
|
||||||
|
* - useEliTabelaSelecao — seleção múltipla de linhas
|
||||||
|
* - useEliTabelaColunas — visibilidade, ordem e modal de colunas
|
||||||
|
* - useEliTabelaMenu — menu de ações por linha e posicionamento de popup
|
||||||
*/
|
*/
|
||||||
/** Enum de códigos de resposta utilizado na consulta */
|
|
||||||
?: string;
|
?: string;
|
||||||
icone
|
icone
|
||||||
/**
|
/**
|
||||||
* EliTabela
|
* EliTabela
|
||||||
* Componente de tabela consultável com busca, paginação, ordenação e ações por linha.
|
* Componente de tabela consultável com busca, paginação, ordenação e ações por linha.
|
||||||
|
* O setup() é orquestrado por `useEliTabela`, que compõe os seguintes sub-composables:
|
||||||
|
* - useEliTabelaConsulta — carregamento, paginação, ordenação, busca
|
||||||
|
* - useEliTabelaFiltrosAvancados — filtros avançados, modal e persistência
|
||||||
|
* - useEliTabelaSelecao — seleção múltipla de linhas
|
||||||
|
* - useEliTabelaColunas — visibilidade, ordem e modal de colunas
|
||||||
|
* - useEliTabelaMenu — menu de ações por linha e posicionamento de popup
|
||||||
*/
|
*/
|
||||||
/** Enum de códigos de resposta utilizado na consulta */
|
|
||||||
?: import("@lucide/vue").LucideIcon;
|
?: import("@lucide/vue").LucideIcon;
|
||||||
acao
|
acao
|
||||||
/**
|
/**
|
||||||
* EliTabela
|
* EliTabela
|
||||||
* Componente de tabela consultável com busca, paginação, ordenação e ações por linha.
|
* Componente de tabela consultável com busca, paginação, ordenação e ações por linha.
|
||||||
|
* O setup() é orquestrado por `useEliTabela`, que compõe os seguintes sub-composables:
|
||||||
|
* - useEliTabelaConsulta — carregamento, paginação, ordenação, busca
|
||||||
|
* - useEliTabelaFiltrosAvancados — filtros avançados, modal e persistência
|
||||||
|
* - useEliTabelaSelecao — seleção múltipla de linhas
|
||||||
|
* - useEliTabelaColunas — visibilidade, ordem e modal de colunas
|
||||||
|
* - useEliTabelaMenu — menu de ações por linha e posicionamento de popup
|
||||||
*/
|
*/
|
||||||
/** Enum de códigos de resposta utilizado na consulta */
|
|
||||||
?: () => void;
|
?: () => void;
|
||||||
}[];
|
}[];
|
||||||
} | {
|
} | {
|
||||||
|
|
@ -708,11 +728,11 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
acoes: {
|
acoes: {
|
||||||
type: PropType<Array<{
|
type: PropType<{
|
||||||
acao: import("./types-eli-tabela").tipoEliTabelaAcao<any>;
|
acao: import("./types-eli-tabela").tipoEliTabelaAcao<any>;
|
||||||
indice: number;
|
indice: number;
|
||||||
visivel: boolean;
|
visivel: boolean;
|
||||||
}>>;
|
}[]>;
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
linha: {
|
linha: {
|
||||||
|
|
@ -743,11 +763,11 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
acoes: {
|
acoes: {
|
||||||
type: PropType<Array<{
|
type: PropType<{
|
||||||
acao: import("./types-eli-tabela").tipoEliTabelaAcao<any>;
|
acao: import("./types-eli-tabela").tipoEliTabelaAcao<any>;
|
||||||
indice: number;
|
indice: number;
|
||||||
visivel: boolean;
|
visivel: boolean;
|
||||||
}>>;
|
}[]>;
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
linha: {
|
linha: {
|
||||||
|
|
@ -778,28 +798,10 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
|
||||||
};
|
};
|
||||||
acoes: {
|
acoes: {
|
||||||
type: PropType<Array<{
|
type: PropType<Array<{
|
||||||
icone
|
icone?: any;
|
||||||
/**
|
cor?: string;
|
||||||
* EliTabela
|
|
||||||
* Componente de tabela consultável com busca, paginação, ordenação e ações por linha.
|
|
||||||
*/
|
|
||||||
/** Enum de códigos de resposta utilizado na consulta */
|
|
||||||
?: any;
|
|
||||||
cor
|
|
||||||
/**
|
|
||||||
* EliTabela
|
|
||||||
* Componente de tabela consultável com busca, paginação, ordenação e ações por linha.
|
|
||||||
*/
|
|
||||||
/** Enum de códigos de resposta utilizado na consulta */
|
|
||||||
?: string;
|
|
||||||
rotulo: string;
|
rotulo: string;
|
||||||
acao: (params
|
acao: (params?: any) => void;
|
||||||
/**
|
|
||||||
* EliTabela
|
|
||||||
* Componente de tabela consultável com busca, paginação, ordenação e ações por linha.
|
|
||||||
*/
|
|
||||||
/** Enum de códigos de resposta utilizado na consulta */
|
|
||||||
?: any) => void;
|
|
||||||
}>>;
|
}>>;
|
||||||
required: false;
|
required: false;
|
||||||
default: () => never[];
|
default: () => never[];
|
||||||
|
|
@ -837,28 +839,10 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
|
||||||
};
|
};
|
||||||
acoes: {
|
acoes: {
|
||||||
type: PropType<Array<{
|
type: PropType<Array<{
|
||||||
icone
|
icone?: any;
|
||||||
/**
|
cor?: string;
|
||||||
* EliTabela
|
|
||||||
* Componente de tabela consultável com busca, paginação, ordenação e ações por linha.
|
|
||||||
*/
|
|
||||||
/** Enum de códigos de resposta utilizado na consulta */
|
|
||||||
?: any;
|
|
||||||
cor
|
|
||||||
/**
|
|
||||||
* EliTabela
|
|
||||||
* Componente de tabela consultável com busca, paginação, ordenação e ações por linha.
|
|
||||||
*/
|
|
||||||
/** Enum de códigos de resposta utilizado na consulta */
|
|
||||||
?: string;
|
|
||||||
rotulo: string;
|
rotulo: string;
|
||||||
acao: (params
|
acao: (params?: any) => void;
|
||||||
/**
|
|
||||||
* EliTabela
|
|
||||||
* Componente de tabela consultável com busca, paginação, ordenação e ações por linha.
|
|
||||||
*/
|
|
||||||
/** Enum de códigos de resposta utilizado na consulta */
|
|
||||||
?: any) => void;
|
|
||||||
}>>;
|
}>>;
|
||||||
required: false;
|
required: false;
|
||||||
default: () => never[];
|
default: () => never[];
|
||||||
|
|
@ -874,13 +858,7 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
|
||||||
icone?: any;
|
icone?: any;
|
||||||
cor?: string;
|
cor?: string;
|
||||||
rotulo: string;
|
rotulo: string;
|
||||||
acao: (params
|
acao: (params?: any) => void;
|
||||||
/**
|
|
||||||
* EliTabela
|
|
||||||
* Componente de tabela consultável com busca, paginação, ordenação e ações por linha.
|
|
||||||
*/
|
|
||||||
/** Enum de códigos de resposta utilizado na consulta */
|
|
||||||
?: any) => void;
|
|
||||||
}[];
|
}[];
|
||||||
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
||||||
EliTabelaModalColunas: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
EliTabelaModalColunas: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
||||||
|
|
@ -893,26 +871,26 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
configInicial: {
|
configInicial: {
|
||||||
type: PropType<EliTabelaColunasConfig>;
|
type: PropType<import("./colunasStorage").EliTabelaColunasConfig>;
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
colunas: {
|
colunas: {
|
||||||
type: PropType<Array<tipoEliColuna<any>>>;
|
type: PropType<import("./types-eli-tabela").tipoEliColuna<any>[]>;
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
}>, {
|
}>, {
|
||||||
|
emitFechar: () => void;
|
||||||
|
emitSalvar: () => void;
|
||||||
visiveisLocal: import("vue").Ref<string[], string[]>;
|
visiveisLocal: import("vue").Ref<string[], string[]>;
|
||||||
invisiveisLocal: import("vue").Ref<string[], string[]>;
|
invisiveisLocal: import("vue").Ref<string[], string[]>;
|
||||||
itemSendoArrastado: import("vue").Ref<string | null, string | null>;
|
itemSendoArrastado: import("vue").Ref<string | null, string | null>;
|
||||||
emitFechar: () => void;
|
|
||||||
emitSalvar: () => void;
|
|
||||||
onDragStart: (e: DragEvent, rotulo: string) => void;
|
onDragStart: (e: DragEvent, rotulo: string) => void;
|
||||||
onDragEnd: () => void;
|
onDragEnd: () => void;
|
||||||
onDragOverItem: (alvoRotulo: string, destino: "visiveis" | "invisiveis") => void;
|
onDragOverItem: (alvoRotulo: string, destino: "visiveis" | "invisiveis") => void;
|
||||||
onDragOverLista: (destino: "visiveis" | "invisiveis") => void;
|
onDragOverLista: (destino: "visiveis" | "invisiveis") => void;
|
||||||
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
||||||
fechar(): true;
|
fechar(): true;
|
||||||
salvar(_config: EliTabelaColunasConfig): true;
|
salvar(_config: import("./colunasStorage").EliTabelaColunasConfig): true;
|
||||||
}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
||||||
aberto: {
|
aberto: {
|
||||||
type: BooleanConstructor;
|
type: BooleanConstructor;
|
||||||
|
|
@ -923,35 +901,44 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
configInicial: {
|
configInicial: {
|
||||||
type: PropType<EliTabelaColunasConfig>;
|
type: PropType<import("./colunasStorage").EliTabelaColunasConfig>;
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
colunas: {
|
colunas: {
|
||||||
type: PropType<Array<tipoEliColuna<any>>>;
|
type: PropType<import("./types-eli-tabela").tipoEliColuna<any>[]>;
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
}>> & Readonly<{
|
}>> & Readonly<{
|
||||||
onFechar?: (() => any) | undefined;
|
onFechar?: (() => any) | undefined;
|
||||||
onSalvar?: ((_config: EliTabelaColunasConfig) => any) | undefined;
|
onSalvar?: ((_config: import("./colunasStorage").EliTabelaColunasConfig) => any) | undefined;
|
||||||
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
}>, {}, {}, {
|
||||||
|
Columns2: import("@lucide/vue").LucideIcon;
|
||||||
|
Eye: import("@lucide/vue").LucideIcon;
|
||||||
|
EyeOff: import("@lucide/vue").LucideIcon;
|
||||||
|
GripVertical: import("@lucide/vue").LucideIcon;
|
||||||
|
X: import("@lucide/vue").LucideIcon;
|
||||||
|
Check: import("@lucide/vue").LucideIcon;
|
||||||
|
}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
||||||
EliTabelaModalFiltroAvancado: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
EliTabelaModalFiltroAvancado: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
||||||
aberto: {
|
aberto: {
|
||||||
type: BooleanConstructor;
|
type: BooleanConstructor;
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
filtrosBase: {
|
filtrosBase: {
|
||||||
type: PropType<Array<{
|
type: PropType<FiltroBase<string>[]>;
|
||||||
chave: string;
|
|
||||||
rotulo: string;
|
|
||||||
filtro: (valor: unknown) => import("p-comuns").tipoFiltro26<any>;
|
|
||||||
entrada: import("../EliEntrada/tiposEntradas").ComponenteEntrada;
|
|
||||||
}>>;
|
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
modelo: {
|
modelo: {
|
||||||
type: PropType<Array<any>>;
|
type: PropType<Array<{
|
||||||
|
chave: string;
|
||||||
|
valor: unknown;
|
||||||
|
}>>;
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
|
modeloFormula: {
|
||||||
|
type: StringConstructor;
|
||||||
|
default: string;
|
||||||
|
};
|
||||||
}>, {
|
}>, {
|
||||||
linhas: import("vue").Ref<{
|
linhas: import("vue").Ref<{
|
||||||
chave: string;
|
chave: string;
|
||||||
|
|
@ -1009,12 +996,8 @@ 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;
|
||||||
}];
|
}];
|
||||||
valor: any;
|
valor: unknown;
|
||||||
}[], {
|
}[], import("./useFiltroAvancado").LinhaFiltro[] | {
|
||||||
chave: string;
|
|
||||||
entrada: import("../EliEntrada/tiposEntradas").ComponenteEntrada;
|
|
||||||
valor: any;
|
|
||||||
}[] | {
|
|
||||||
chave: string;
|
chave: string;
|
||||||
entrada: readonly ["texto", {
|
entrada: readonly ["texto", {
|
||||||
rotulo: string;
|
rotulo: string;
|
||||||
|
|
@ -1070,15 +1053,17 @@ 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;
|
||||||
}];
|
}];
|
||||||
valor: any;
|
valor: unknown;
|
||||||
}[]>;
|
}[]>;
|
||||||
opcoesParaAdicionar: import("vue").ComputedRef<{
|
opcoesParaAdicionar: import("vue").ComputedRef<{
|
||||||
chave: string;
|
chave: string;
|
||||||
rotulo: string;
|
rotulo: string;
|
||||||
filtro: (valor: unknown) => import("p-comuns").tipoFiltro26<any>;
|
filtro: (valor: unknown) => import("p-comuns").tipoFiltroDeltaColunaTipada<"small" | "length" | "concat" | "slice" | "indexOf" | "lastIndexOf" | "includes" | "toString" | "charAt" | "charCodeAt" | "localeCompare" | "match" | "replace" | "search" | "split" | "substring" | "toLowerCase" | "toLocaleLowerCase" | "toUpperCase" | "toLocaleUpperCase" | "trim" | "substr" | "codePointAt" | "endsWith" | "normalize" | "repeat" | "startsWith" | "anchor" | "big" | "blink" | "bold" | "fixed" | "fontcolor" | "fontsize" | "italics" | "link" | "strike" | "sub" | "sup" | "padStart" | "padEnd" | "trimEnd" | "trimStart" | "trimLeft" | "trimRight" | "matchAll" | "valueOf"> | undefined;
|
||||||
entrada: import("../EliEntrada/tiposEntradas").ComponenteEntrada;
|
entrada: import("../EliEntrada/tiposEntradas").ComponenteEntrada;
|
||||||
}[]>;
|
}[]>;
|
||||||
chaveParaAdicionar: import("vue").Ref<string, string>;
|
chaveParaAdicionar: import("vue").Ref<string, string>;
|
||||||
|
formulaCustomizada: import("vue").Ref<string, string>;
|
||||||
|
formulaGerada: import("vue").ComputedRef<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>;
|
||||||
|
|
@ -1418,43 +1403,57 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
|
||||||
}>, {
|
}>, {
|
||||||
value: string | null | undefined;
|
value: string | null | undefined;
|
||||||
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
||||||
opcoesEntrada: (entrada: import("../EliEntrada/tiposEntradas").ComponenteEntrada) => any;
|
opcoesEntrada: (entrada: import("../EliEntrada/tiposEntradas").ComponenteEntrada) => {
|
||||||
|
rotulo: string;
|
||||||
|
};
|
||||||
adicionar: () => void;
|
adicionar: () => void;
|
||||||
remover: (idx: number) => void;
|
remover: (idx: number) => void;
|
||||||
emitFechar: () => void;
|
emitFechar: () => void;
|
||||||
emitSalvar: () => void;
|
emitSalvar: () => void;
|
||||||
emitLimpar: () => void;
|
emitLimpar: () => void;
|
||||||
rotuloDoFiltro: (f: {
|
rotuloDoFiltro: typeof import("./useFiltroAvancado").rotuloDoFiltro;
|
||||||
chave: string;
|
rotuloDoFiltroAtivo: (chave: string) => string;
|
||||||
rotulo: string;
|
|
||||||
filtro: (valor: unknown) => import("p-comuns").tipoFiltro26<any>;
|
|
||||||
entrada: import("../EliEntrada/tiposEntradas").ComponenteEntrada;
|
|
||||||
}) => string;
|
|
||||||
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
||||||
fechar: () => true;
|
fechar: () => true;
|
||||||
limpar: () => true;
|
limpar: () => true;
|
||||||
salvar: (_linhas: any[]) => true;
|
salvar: (_linhas: Array<{
|
||||||
|
chave: string;
|
||||||
|
valor: unknown;
|
||||||
|
}>, _formula?: string) => true;
|
||||||
}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
||||||
aberto: {
|
aberto: {
|
||||||
type: BooleanConstructor;
|
type: BooleanConstructor;
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
filtrosBase: {
|
filtrosBase: {
|
||||||
type: PropType<Array<{
|
type: PropType<FiltroBase<string>[]>;
|
||||||
chave: string;
|
|
||||||
rotulo: string;
|
|
||||||
filtro: (valor: unknown) => import("p-comuns").tipoFiltro26<any>;
|
|
||||||
entrada: import("../EliEntrada/tiposEntradas").ComponenteEntrada;
|
|
||||||
}>>;
|
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
modelo: {
|
modelo: {
|
||||||
type: PropType<Array<any>>;
|
type: PropType<Array<{
|
||||||
|
chave: string;
|
||||||
|
valor: unknown;
|
||||||
|
}>>;
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
|
modeloFormula: {
|
||||||
|
type: StringConstructor;
|
||||||
|
default: string;
|
||||||
|
};
|
||||||
}>> & Readonly<{
|
}>> & Readonly<{
|
||||||
onFechar?: (() => any) | undefined;
|
onFechar?: (() => any) | undefined;
|
||||||
onSalvar?: ((_linhas: any[]) => any) | undefined;
|
onSalvar?: ((_linhas: {
|
||||||
|
chave: string;
|
||||||
|
valor: unknown;
|
||||||
|
}[], _formula?: string | undefined) => any) | undefined;
|
||||||
onLimpar?: (() => any) | undefined;
|
onLimpar?: (() => any) | undefined;
|
||||||
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
}>, {
|
||||||
|
modeloFormula: string;
|
||||||
|
}, {}, {
|
||||||
|
CircleHelp: import("@lucide/vue").LucideIcon;
|
||||||
|
Filter: import("@lucide/vue").LucideIcon;
|
||||||
|
Plus: import("@lucide/vue").LucideIcon;
|
||||||
|
Trash2: import("@lucide/vue").LucideIcon;
|
||||||
|
X: import("@lucide/vue").LucideIcon;
|
||||||
|
}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
||||||
}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,11 @@ declare const _default: typeof __VLS_export;
|
||||||
export default _default;
|
export default _default;
|
||||||
declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
||||||
colunas: {
|
colunas: {
|
||||||
type: PropType<Array<tipoEliColuna<any>>>;
|
type: PropType<tipoEliColuna<any>[]>;
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
colunasInvisiveis: {
|
colunasInvisiveis: {
|
||||||
type: PropType<Array<tipoEliColuna<any>>>;
|
type: PropType<tipoEliColuna<any>[]>;
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
temColunasInvisiveis: {
|
temColunasInvisiveis: {
|
||||||
|
|
@ -20,7 +20,7 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
linhas: {
|
linhas: {
|
||||||
type: PropType<Array<unknown>>;
|
type: PropType<unknown[]>;
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
temAcoes: {
|
temAcoes: {
|
||||||
|
|
@ -60,11 +60,11 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
|
||||||
ChevronDown: import("@lucide/vue").LucideIcon;
|
ChevronDown: import("@lucide/vue").LucideIcon;
|
||||||
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
||||||
colunas: {
|
colunas: {
|
||||||
type: PropType<Array<tipoEliColuna<any>>>;
|
type: PropType<tipoEliColuna<any>[]>;
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
colunasInvisiveis: {
|
colunasInvisiveis: {
|
||||||
type: PropType<Array<tipoEliColuna<any>>>;
|
type: PropType<tipoEliColuna<any>[]>;
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
temColunasInvisiveis: {
|
temColunasInvisiveis: {
|
||||||
|
|
@ -76,7 +76,7 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
linhas: {
|
linhas: {
|
||||||
type: PropType<Array<unknown>>;
|
type: PropType<unknown[]>;
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
temAcoes: {
|
temAcoes: {
|
||||||
|
|
@ -156,7 +156,7 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
colunasInvisiveis: {
|
colunasInvisiveis: {
|
||||||
type: PropType<Array<tipoEliColuna<any>>>;
|
type: PropType<tipoEliColuna<any>[]>;
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
}>, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
}>, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
||||||
|
|
@ -165,7 +165,7 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
colunasInvisiveis: {
|
colunasInvisiveis: {
|
||||||
type: PropType<Array<tipoEliColuna<any>>>;
|
type: PropType<tipoEliColuna<any>[]>;
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
}>> & Readonly<{}>, {}, {}, {
|
}>> & Readonly<{}>, {}, {}, {
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,7 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
|
||||||
exibirBotaoColunas: boolean;
|
exibirBotaoColunas: boolean;
|
||||||
exibirBotaoFiltroAvancado: boolean;
|
exibirBotaoFiltroAvancado: boolean;
|
||||||
}, {}, {
|
}, {}, {
|
||||||
|
Columns3: import("@lucide/vue").LucideIcon;
|
||||||
EliTabelaCaixaDeBusca: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
EliTabelaCaixaDeBusca: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
||||||
modelo: {
|
modelo: {
|
||||||
type: StringConstructor;
|
type: StringConstructor;
|
||||||
|
|
@ -106,4 +107,5 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
|
||||||
}, {}, {
|
}, {}, {
|
||||||
Search: import("@lucide/vue").LucideIcon;
|
Search: import("@lucide/vue").LucideIcon;
|
||||||
}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
||||||
|
Filter: import("@lucide/vue").LucideIcon;
|
||||||
}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
|
||||||
}>;
|
}>;
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
}>, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
}>, void, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
||||||
isDev: {
|
isDev: {
|
||||||
type: BooleanConstructor;
|
type: BooleanConstructor;
|
||||||
required: true;
|
required: true;
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
colunasInvisiveis: {
|
colunasInvisiveis: {
|
||||||
type: PropType<Array<tipoEliColuna<any>>>;
|
type: PropType<tipoEliColuna<any>[]>;
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
}>, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
}>, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
||||||
|
|
@ -17,7 +17,7 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
colunasInvisiveis: {
|
colunasInvisiveis: {
|
||||||
type: PropType<Array<tipoEliColuna<any>>>;
|
type: PropType<tipoEliColuna<any>[]>;
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
}>> & Readonly<{}>, {}, {}, {
|
}>> & Readonly<{}>, {}, {}, {
|
||||||
|
|
|
||||||
|
|
@ -32,4 +32,8 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
|
||||||
}>> & Readonly<{}>, {
|
}>> & Readonly<{}>, {
|
||||||
erro: string | null;
|
erro: string | null;
|
||||||
mensagemVazio: string | undefined;
|
mensagemVazio: string | undefined;
|
||||||
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
}, {}, {
|
||||||
|
AlertCircle: import("@lucide/vue").LucideIcon;
|
||||||
|
Inbox: import("@lucide/vue").LucideIcon;
|
||||||
|
LoaderCircle: import("@lucide/vue").LucideIcon;
|
||||||
|
}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ declare const _default: typeof __VLS_export;
|
||||||
export default _default;
|
export default _default;
|
||||||
declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
||||||
colunas: {
|
colunas: {
|
||||||
type: PropType<Array<tipoEliColuna<any>>>;
|
type: PropType<tipoEliColuna<any>[]>;
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
temAcoes: {
|
temAcoes: {
|
||||||
|
|
@ -38,14 +38,14 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
|
||||||
}>, {
|
}>, {
|
||||||
ArrowUp: import("@lucide/vue").LucideIcon;
|
ArrowUp: import("@lucide/vue").LucideIcon;
|
||||||
ArrowDown: import("@lucide/vue").LucideIcon;
|
ArrowDown: import("@lucide/vue").LucideIcon;
|
||||||
isOrdenavel: (coluna: any) => boolean;
|
isOrdenavel: (coluna: tipoEliColuna<unknown>) => boolean;
|
||||||
emitAlternarOrdenacao: (chave: string) => void;
|
emitAlternarOrdenacao: (chave: string) => void;
|
||||||
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
||||||
alternarOrdenacao(chave: string): boolean;
|
alternarOrdenacao(chave: string): boolean;
|
||||||
"alternar-selecao-todos": () => true;
|
"alternar-selecao-todos": () => true;
|
||||||
}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
||||||
colunas: {
|
colunas: {
|
||||||
type: PropType<Array<tipoEliColuna<any>>>;
|
type: PropType<tipoEliColuna<any>[]>;
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
temAcoes: {
|
temAcoes: {
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
acoes: {
|
acoes: {
|
||||||
type: PropType<Array<ItemAcao<any>>>;
|
type: PropType<ItemAcao<any>[]>;
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
linha: {
|
linha: {
|
||||||
|
|
@ -51,7 +51,7 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
acoes: {
|
acoes: {
|
||||||
type: PropType<Array<ItemAcao<any>>>;
|
type: PropType<ItemAcao<any>[]>;
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
linha: {
|
linha: {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { type PropType } from "vue";
|
import { type PropType } from "vue";
|
||||||
|
import "./EliTabelaModalColunas.css";
|
||||||
import type { EliTabelaColunasConfig } from "./colunasStorage";
|
import type { EliTabelaColunasConfig } from "./colunasStorage";
|
||||||
import type { tipoEliColuna } from "./types-eli-tabela";
|
import type { tipoEliColuna } from "./types-eli-tabela";
|
||||||
type OrigemLista = "visiveis" | "invisiveis";
|
|
||||||
declare const _default: typeof __VLS_export;
|
declare const _default: typeof __VLS_export;
|
||||||
export default _default;
|
export default _default;
|
||||||
declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
||||||
|
|
@ -18,19 +18,19 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
colunas: {
|
colunas: {
|
||||||
type: PropType<Array<tipoEliColuna<any>>>;
|
type: PropType<tipoEliColuna<any>[]>;
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
}>, {
|
}>, {
|
||||||
|
emitFechar: () => void;
|
||||||
|
emitSalvar: () => void;
|
||||||
visiveisLocal: import("vue").Ref<string[], string[]>;
|
visiveisLocal: import("vue").Ref<string[], string[]>;
|
||||||
invisiveisLocal: import("vue").Ref<string[], string[]>;
|
invisiveisLocal: import("vue").Ref<string[], string[]>;
|
||||||
itemSendoArrastado: import("vue").Ref<string | null, string | null>;
|
itemSendoArrastado: import("vue").Ref<string | null, string | null>;
|
||||||
emitFechar: () => void;
|
|
||||||
emitSalvar: () => void;
|
|
||||||
onDragStart: (e: DragEvent, rotulo: string) => void;
|
onDragStart: (e: DragEvent, rotulo: string) => void;
|
||||||
onDragEnd: () => void;
|
onDragEnd: () => void;
|
||||||
onDragOverItem: (alvoRotulo: string, destino: OrigemLista) => void;
|
onDragOverItem: (alvoRotulo: string, destino: "visiveis" | "invisiveis") => void;
|
||||||
onDragOverLista: (destino: OrigemLista) => void;
|
onDragOverLista: (destino: "visiveis" | "invisiveis") => void;
|
||||||
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
||||||
fechar(): true;
|
fechar(): true;
|
||||||
salvar(_config: EliTabelaColunasConfig): true;
|
salvar(_config: EliTabelaColunasConfig): true;
|
||||||
|
|
@ -48,10 +48,17 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
colunas: {
|
colunas: {
|
||||||
type: PropType<Array<tipoEliColuna<any>>>;
|
type: PropType<tipoEliColuna<any>[]>;
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
}>> & Readonly<{
|
}>> & Readonly<{
|
||||||
onFechar?: (() => any) | undefined;
|
onFechar?: (() => any) | undefined;
|
||||||
onSalvar?: ((_config: EliTabelaColunasConfig) => any) | undefined;
|
onSalvar?: ((_config: EliTabelaColunasConfig) => any) | undefined;
|
||||||
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
}>, {}, {}, {
|
||||||
|
Columns2: import("@lucide/vue").LucideIcon;
|
||||||
|
Eye: import("@lucide/vue").LucideIcon;
|
||||||
|
EyeOff: import("@lucide/vue").LucideIcon;
|
||||||
|
GripVertical: import("@lucide/vue").LucideIcon;
|
||||||
|
X: import("@lucide/vue").LucideIcon;
|
||||||
|
Check: import("@lucide/vue").LucideIcon;
|
||||||
|
}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,7 @@
|
||||||
import { type PropType } from "vue";
|
import { type PropType } from "vue";
|
||||||
import type { ComponenteEntrada } from "../EliEntrada/tiposEntradas";
|
import type { FiltroBase } from "./useFiltroAvancado";
|
||||||
import type { tipoEliTabelaConsulta } from "./types-eli-tabela";
|
import { rotuloDoFiltro } from "./useFiltroAvancado";
|
||||||
type FiltroBase<T> = NonNullable<tipoEliTabelaConsulta<T>["filtroAvancado"]>[number];
|
import "./EliTabelaModalFiltroAvancado.css";
|
||||||
type LinhaFiltro = {
|
|
||||||
chave: string;
|
|
||||||
entrada: ComponenteEntrada;
|
|
||||||
valor: any;
|
|
||||||
};
|
|
||||||
declare function rotuloDoFiltro(f: FiltroBase<any>): string;
|
|
||||||
declare const _default: typeof __VLS_export;
|
declare const _default: typeof __VLS_export;
|
||||||
export default _default;
|
export default _default;
|
||||||
declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
||||||
|
|
@ -16,13 +10,21 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
filtrosBase: {
|
filtrosBase: {
|
||||||
type: PropType<Array<FiltroBase<any>>>;
|
type: PropType<FiltroBase<string>[]>;
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
modelo: {
|
modelo: {
|
||||||
type: PropType<Array<any>>;
|
/** Linhas persistidas do pai — normalizadas internamente via normalizarModelo() */
|
||||||
|
type: PropType<Array<{
|
||||||
|
chave: string;
|
||||||
|
valor: unknown;
|
||||||
|
}>>;
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
|
modeloFormula: {
|
||||||
|
type: StringConstructor;
|
||||||
|
default: string;
|
||||||
|
};
|
||||||
}>, {
|
}>, {
|
||||||
linhas: import("vue").Ref<{
|
linhas: import("vue").Ref<{
|
||||||
chave: string;
|
chave: string;
|
||||||
|
|
@ -80,8 +82,8 @@ 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;
|
||||||
}];
|
}];
|
||||||
valor: any;
|
valor: unknown;
|
||||||
}[], LinhaFiltro[] | {
|
}[], import("./useFiltroAvancado").LinhaFiltro[] | {
|
||||||
chave: string;
|
chave: string;
|
||||||
entrada: readonly ["texto", {
|
entrada: readonly ["texto", {
|
||||||
rotulo: string;
|
rotulo: string;
|
||||||
|
|
@ -137,16 +139,18 @@ 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;
|
||||||
}];
|
}];
|
||||||
valor: any;
|
valor: unknown;
|
||||||
}[]>;
|
}[]>;
|
||||||
opcoesParaAdicionar: import("vue").ComputedRef<{
|
opcoesParaAdicionar: import("vue").ComputedRef<{
|
||||||
chave: string;
|
chave: string;
|
||||||
rotulo: string;
|
rotulo: string;
|
||||||
filtro: (valor: unknown) => import("p-comuns").tipoFiltro26<any>;
|
filtro: (valor: unknown) => import("p-comuns").tipoFiltroDeltaColunaTipada<"small" | "length" | "concat" | "slice" | "indexOf" | "lastIndexOf" | "includes" | "toString" | "charAt" | "charCodeAt" | "localeCompare" | "match" | "replace" | "search" | "split" | "substring" | "toLowerCase" | "toLocaleLowerCase" | "toUpperCase" | "toLocaleUpperCase" | "trim" | "substr" | "codePointAt" | "endsWith" | "normalize" | "repeat" | "startsWith" | "anchor" | "big" | "blink" | "bold" | "fixed" | "fontcolor" | "fontsize" | "italics" | "link" | "strike" | "sub" | "sup" | "padStart" | "padEnd" | "trimEnd" | "trimStart" | "trimLeft" | "trimRight" | "matchAll" | "valueOf"> | undefined;
|
||||||
entrada: ComponenteEntrada;
|
entrada: import("../EliEntrada/tiposEntradas").ComponenteEntrada;
|
||||||
}[]>;
|
}[]>;
|
||||||
chaveParaAdicionar: import("vue").Ref<string, string>;
|
chaveParaAdicionar: import("vue").Ref<string, string>;
|
||||||
componenteEntrada: (entrada: ComponenteEntrada) => import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
formulaCustomizada: import("vue").Ref<string, string>;
|
||||||
|
formulaGerada: import("vue").ComputedRef<string>;
|
||||||
|
componenteEntrada: (entrada: import("../EliEntrada/tiposEntradas").ComponenteEntrada) => import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
||||||
value: {
|
value: {
|
||||||
type: PropType<string | null | undefined>;
|
type: PropType<string | null | undefined>;
|
||||||
default: undefined;
|
default: undefined;
|
||||||
|
|
@ -485,32 +489,58 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
|
||||||
}>, {
|
}>, {
|
||||||
value: string | null | undefined;
|
value: string | null | undefined;
|
||||||
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
||||||
opcoesEntrada: (entrada: ComponenteEntrada) => any;
|
opcoesEntrada: (entrada: import("../EliEntrada/tiposEntradas").ComponenteEntrada) => {
|
||||||
|
rotulo: string;
|
||||||
|
};
|
||||||
adicionar: () => void;
|
adicionar: () => void;
|
||||||
remover: (idx: number) => void;
|
remover: (idx: number) => void;
|
||||||
emitFechar: () => void;
|
emitFechar: () => void;
|
||||||
emitSalvar: () => void;
|
emitSalvar: () => void;
|
||||||
emitLimpar: () => void;
|
emitLimpar: () => void;
|
||||||
rotuloDoFiltro: typeof rotuloDoFiltro;
|
rotuloDoFiltro: typeof rotuloDoFiltro;
|
||||||
|
rotuloDoFiltroAtivo: (chave: string) => string;
|
||||||
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
||||||
fechar: () => true;
|
fechar: () => true;
|
||||||
limpar: () => true;
|
limpar: () => true;
|
||||||
salvar: (_linhas: any[]) => true;
|
/** Dado serializável emitido pelo botão Aplicar */
|
||||||
|
salvar: (_linhas: Array<{
|
||||||
|
chave: string;
|
||||||
|
valor: unknown;
|
||||||
|
}>, _formula?: string) => true;
|
||||||
}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
||||||
aberto: {
|
aberto: {
|
||||||
type: BooleanConstructor;
|
type: BooleanConstructor;
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
filtrosBase: {
|
filtrosBase: {
|
||||||
type: PropType<Array<FiltroBase<any>>>;
|
type: PropType<FiltroBase<string>[]>;
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
modelo: {
|
modelo: {
|
||||||
type: PropType<Array<any>>;
|
/** Linhas persistidas do pai — normalizadas internamente via normalizarModelo() */
|
||||||
|
type: PropType<Array<{
|
||||||
|
chave: string;
|
||||||
|
valor: unknown;
|
||||||
|
}>>;
|
||||||
required: true;
|
required: true;
|
||||||
};
|
};
|
||||||
|
modeloFormula: {
|
||||||
|
type: StringConstructor;
|
||||||
|
default: string;
|
||||||
|
};
|
||||||
}>> & Readonly<{
|
}>> & Readonly<{
|
||||||
onFechar?: (() => any) | undefined;
|
onFechar?: (() => any) | undefined;
|
||||||
onSalvar?: ((_linhas: any[]) => any) | undefined;
|
onSalvar?: ((_linhas: {
|
||||||
|
chave: string;
|
||||||
|
valor: unknown;
|
||||||
|
}[], _formula?: string | undefined) => any) | undefined;
|
||||||
onLimpar?: (() => any) | undefined;
|
onLimpar?: (() => any) | undefined;
|
||||||
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
}>, {
|
||||||
|
modeloFormula: string;
|
||||||
|
}, {}, {
|
||||||
|
CircleHelp: import("@lucide/vue").LucideIcon;
|
||||||
|
Filter: import("@lucide/vue").LucideIcon;
|
||||||
|
Plus: import("@lucide/vue").LucideIcon;
|
||||||
|
Trash2: import("@lucide/vue").LucideIcon;
|
||||||
|
X: import("@lucide/vue").LucideIcon;
|
||||||
|
}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { type PropType } from "vue";
|
import { type PropType } from "vue";
|
||||||
|
import "./EliTabelaPaginacao.css";
|
||||||
declare const _default: typeof __VLS_export;
|
declare const _default: typeof __VLS_export;
|
||||||
export default _default;
|
export default _default;
|
||||||
declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import "dayjs/locale/pt-br";
|
||||||
import { type PropType } from "vue";
|
import { type PropType } from "vue";
|
||||||
import type { tiposTabelaCelulas } from "./tiposTabelaCelulas";
|
import type { tiposTabelaCelulas } from "./tiposTabelaCelulas";
|
||||||
declare const _default: typeof __VLS_export;
|
declare const _default: typeof __VLS_export;
|
||||||
|
|
@ -8,11 +9,6 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
|
||||||
required: false;
|
required: false;
|
||||||
};
|
};
|
||||||
}>, {
|
}>, {
|
||||||
dados: {
|
|
||||||
valor: string;
|
|
||||||
formato: "data" | "data_hora" | "relativo";
|
|
||||||
acao?: () => void;
|
|
||||||
} | undefined;
|
|
||||||
textoData: import("vue").ComputedRef<string>;
|
textoData: import("vue").ComputedRef<string>;
|
||||||
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
||||||
dados: {
|
dados: {
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,6 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
|
||||||
type: PropType<tiposTabelaCelulas["numero"]>;
|
type: PropType<tiposTabelaCelulas["numero"]>;
|
||||||
};
|
};
|
||||||
}>, {
|
}>, {
|
||||||
dados: {
|
|
||||||
numero: number;
|
|
||||||
prefixo?: string;
|
|
||||||
sufixo?: string;
|
|
||||||
acao?: () => void;
|
|
||||||
} | undefined;
|
|
||||||
textoNumero: import("vue").ComputedRef<string>;
|
textoNumero: import("vue").ComputedRef<string>;
|
||||||
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
||||||
dados: {
|
dados: {
|
||||||
|
|
|
||||||
|
|
@ -7,16 +7,7 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
|
||||||
type: PropType<tiposTabelaCelulas["tags"]>;
|
type: PropType<tiposTabelaCelulas["tags"]>;
|
||||||
required: false;
|
required: false;
|
||||||
};
|
};
|
||||||
}>, {
|
}>, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
||||||
dados: {
|
|
||||||
opcoes: {
|
|
||||||
rotulo: string;
|
|
||||||
cor?: string;
|
|
||||||
icone?: import("@lucide/vue").LucideIcon;
|
|
||||||
acao?: () => void;
|
|
||||||
}[];
|
|
||||||
} | undefined;
|
|
||||||
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
|
||||||
dados: {
|
dados: {
|
||||||
type: PropType<tiposTabelaCelulas["tags"]>;
|
type: PropType<tiposTabelaCelulas["tags"]>;
|
||||||
required: false;
|
required: false;
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,7 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
|
||||||
dados: {
|
dados: {
|
||||||
type: PropType<tiposTabelaCelulas["textoSimples"]>;
|
type: PropType<tiposTabelaCelulas["textoSimples"]>;
|
||||||
};
|
};
|
||||||
}>, {
|
}>, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
||||||
dados: {
|
|
||||||
texto: string;
|
|
||||||
acao?: () => void;
|
|
||||||
} | undefined;
|
|
||||||
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
|
||||||
dados: {
|
dados: {
|
||||||
type: PropType<tiposTabelaCelulas["textoSimples"]>;
|
type: PropType<tiposTabelaCelulas["textoSimples"]>;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,7 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
|
||||||
dados: {
|
dados: {
|
||||||
type: PropType<tiposTabelaCelulas["textoTruncado"]>;
|
type: PropType<tiposTabelaCelulas["textoTruncado"]>;
|
||||||
};
|
};
|
||||||
}>, {
|
}>, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
||||||
dados: {
|
|
||||||
texto: string;
|
|
||||||
acao?: () => void;
|
|
||||||
} | undefined;
|
|
||||||
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
|
||||||
dados: {
|
dados: {
|
||||||
type: PropType<tiposTabelaCelulas["textoTruncado"]>;
|
type: PropType<tiposTabelaCelulas["textoTruncado"]>;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,19 @@
|
||||||
|
/**
|
||||||
|
* Mapa (dispatch table) que associa cada `tipoTabelaCelula` ao seu componente Vue de renderização.
|
||||||
|
* Usado por `EliTabelaCelula.vue` para resolver dinamicamente qual componente renderizar
|
||||||
|
* a partir do tipo retornado por `tipoEliColuna.celula(linha)`.
|
||||||
|
*
|
||||||
|
* Adicionar um novo tipo de célula requer:
|
||||||
|
* 1. Criar o componente `EliTabelaCelula<Tipo>.vue`
|
||||||
|
* 2. Adicionar a chave em `tipoTabelaCelula` (tiposTabelaCelulas.ts)
|
||||||
|
* 3. Registrar aqui no mapa
|
||||||
|
*/
|
||||||
export declare const registryTabelaCelulas: {
|
export declare const registryTabelaCelulas: {
|
||||||
readonly textoSimples: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
readonly textoSimples: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
||||||
dados: {
|
dados: {
|
||||||
type: import("vue").PropType<import("./tiposTabelaCelulas").tiposTabelaCelulas["textoSimples"]>;
|
type: import("vue").PropType<import("./tiposTabelaCelulas").tiposTabelaCelulas["textoSimples"]>;
|
||||||
};
|
};
|
||||||
}>, {
|
}>, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
||||||
dados: {
|
|
||||||
texto: string;
|
|
||||||
acao?: () => void;
|
|
||||||
} | undefined;
|
|
||||||
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
|
||||||
dados: {
|
dados: {
|
||||||
type: import("vue").PropType<import("./tiposTabelaCelulas").tiposTabelaCelulas["textoSimples"]>;
|
type: import("vue").PropType<import("./tiposTabelaCelulas").tiposTabelaCelulas["textoSimples"]>;
|
||||||
};
|
};
|
||||||
|
|
@ -17,12 +22,7 @@ export declare const registryTabelaCelulas: {
|
||||||
dados: {
|
dados: {
|
||||||
type: import("vue").PropType<import("./tiposTabelaCelulas").tiposTabelaCelulas["textoTruncado"]>;
|
type: import("vue").PropType<import("./tiposTabelaCelulas").tiposTabelaCelulas["textoTruncado"]>;
|
||||||
};
|
};
|
||||||
}>, {
|
}>, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
||||||
dados: {
|
|
||||||
texto: string;
|
|
||||||
acao?: () => void;
|
|
||||||
} | undefined;
|
|
||||||
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
|
||||||
dados: {
|
dados: {
|
||||||
type: import("vue").PropType<import("./tiposTabelaCelulas").tiposTabelaCelulas["textoTruncado"]>;
|
type: import("vue").PropType<import("./tiposTabelaCelulas").tiposTabelaCelulas["textoTruncado"]>;
|
||||||
};
|
};
|
||||||
|
|
@ -32,12 +32,6 @@ export declare const registryTabelaCelulas: {
|
||||||
type: import("vue").PropType<import("./tiposTabelaCelulas").tiposTabelaCelulas["numero"]>;
|
type: import("vue").PropType<import("./tiposTabelaCelulas").tiposTabelaCelulas["numero"]>;
|
||||||
};
|
};
|
||||||
}>, {
|
}>, {
|
||||||
dados: {
|
|
||||||
numero: number;
|
|
||||||
prefixo?: string;
|
|
||||||
sufixo?: string;
|
|
||||||
acao?: () => void;
|
|
||||||
} | undefined;
|
|
||||||
textoNumero: import("vue").ComputedRef<string>;
|
textoNumero: import("vue").ComputedRef<string>;
|
||||||
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
||||||
dados: {
|
dados: {
|
||||||
|
|
@ -49,16 +43,7 @@ export declare const registryTabelaCelulas: {
|
||||||
type: import("vue").PropType<import("./tiposTabelaCelulas").tiposTabelaCelulas["tags"]>;
|
type: import("vue").PropType<import("./tiposTabelaCelulas").tiposTabelaCelulas["tags"]>;
|
||||||
required: false;
|
required: false;
|
||||||
};
|
};
|
||||||
}>, {
|
}>, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
||||||
dados: {
|
|
||||||
opcoes: {
|
|
||||||
rotulo: string;
|
|
||||||
cor?: string;
|
|
||||||
icone?: import("@lucide/vue").LucideIcon;
|
|
||||||
acao?: () => void;
|
|
||||||
}[];
|
|
||||||
} | undefined;
|
|
||||||
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
|
||||||
dados: {
|
dados: {
|
||||||
type: import("vue").PropType<import("./tiposTabelaCelulas").tiposTabelaCelulas["tags"]>;
|
type: import("vue").PropType<import("./tiposTabelaCelulas").tiposTabelaCelulas["tags"]>;
|
||||||
required: false;
|
required: false;
|
||||||
|
|
@ -729,11 +714,6 @@ export declare const registryTabelaCelulas: {
|
||||||
required: false;
|
required: false;
|
||||||
};
|
};
|
||||||
}>, {
|
}>, {
|
||||||
dados: {
|
|
||||||
valor: string;
|
|
||||||
formato: "data" | "data_hora" | "relativo";
|
|
||||||
acao?: () => void;
|
|
||||||
} | undefined;
|
|
||||||
textoData: import("vue").ComputedRef<string>;
|
textoData: import("vue").ComputedRef<string>;
|
||||||
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
||||||
dados: {
|
dados: {
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,36 @@
|
||||||
export type EliTabelaColunasConfig = {
|
export type EliTabelaColunasConfig = {
|
||||||
/** Rotulos das colunas visiveis (em ordem). */
|
/** Rótulos das colunas visíveis (em ordem). */
|
||||||
visiveis: string[];
|
visiveis: string[];
|
||||||
/** Rotulos das colunas invisiveis. */
|
/** Rótulos das colunas invisíveis. */
|
||||||
invisiveis: string[];
|
invisiveis: string[];
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* Retorna a chave de `localStorage` utilizada para persistir a configuração de colunas de uma tabela.
|
||||||
|
*
|
||||||
|
* @param nomeTabela - Identificador único da tabela (campo `nome` de `tipoEliTabelaConsulta`)
|
||||||
|
* @returns Chave no formato `eli:tabela:<nomeTabela>:colunas`
|
||||||
|
*/
|
||||||
export declare function storageKeyColunas(nomeTabela: string): string;
|
export declare function storageKeyColunas(nomeTabela: string): string;
|
||||||
|
/**
|
||||||
|
* Carrega do `localStorage` a configuração de visibilidade e ordem das colunas de uma tabela.
|
||||||
|
* Em caso de dado corrompido ou ausente, retorna configuração vazia (sem colunas fixadas).
|
||||||
|
*
|
||||||
|
* @param nomeTabela - Identificador único da tabela
|
||||||
|
* @returns Configuração salva ou `{ visiveis: [], invisiveis: [] }` como fallback seguro
|
||||||
|
*/
|
||||||
export declare function carregarConfigColunas(nomeTabela: string): EliTabelaColunasConfig;
|
export declare function carregarConfigColunas(nomeTabela: string): EliTabelaColunasConfig;
|
||||||
|
/**
|
||||||
|
* Persiste no `localStorage` a configuração de visibilidade e ordem das colunas.
|
||||||
|
* Falhas de armazenamento (modo privado, storage cheio) são silenciadas.
|
||||||
|
*
|
||||||
|
* @param nomeTabela - Identificador único da tabela
|
||||||
|
* @param config - Configuração com listas de colunas visíveis e invisíveis
|
||||||
|
*/
|
||||||
export declare function salvarConfigColunas(nomeTabela: string, config: EliTabelaColunasConfig): void;
|
export declare function salvarConfigColunas(nomeTabela: string, config: EliTabelaColunasConfig): void;
|
||||||
|
/**
|
||||||
|
* Remove do `localStorage` a configuração de colunas salva para uma tabela.
|
||||||
|
* Usado para restaurar o estado padrão de visibilidade definido nas props.
|
||||||
|
*
|
||||||
|
* @param nomeTabela - Identificador único da tabela
|
||||||
|
*/
|
||||||
export declare function limparConfigColunas(nomeTabela: string): void;
|
export declare function limparConfigColunas(nomeTabela: string): void;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,33 @@
|
||||||
export type EliTabelaFiltroAvancadoSalvo<T> = Array<{
|
export type EliTabelaFiltroAvancadoSalvo<T> = {
|
||||||
coluna: keyof T;
|
linhas: Array<{
|
||||||
valor: T[keyof T];
|
chave: keyof T;
|
||||||
}>;
|
/** Valor vindo de JSON.parse — tipo desconhecido intencionalmente; faça narrowing no consumidor. */
|
||||||
|
valor: unknown;
|
||||||
|
}>;
|
||||||
|
formula?: string;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Carrega do `localStorage` as linhas de filtro avançado salvas para uma tabela.
|
||||||
|
* Trata dois formatos legados: array direto de filtros (v1) e objeto `{ linhas, formula }` (v2).
|
||||||
|
* Em caso de dado ausente, corrompido ou formato desconhecido, retorna estado vazio.
|
||||||
|
*
|
||||||
|
* @param nomeTabela - Identificador único da tabela (campo `nome` de `tipoEliTabelaConsulta`)
|
||||||
|
* @returns Estado de filtros salvo, ou `{ linhas: [], formula: '' }` como fallback seguro
|
||||||
|
*/
|
||||||
export declare function carregarFiltroAvancado<T>(nomeTabela: string): EliTabelaFiltroAvancadoSalvo<T>;
|
export declare function carregarFiltroAvancado<T>(nomeTabela: string): EliTabelaFiltroAvancadoSalvo<T>;
|
||||||
|
/**
|
||||||
|
* Persiste no `localStorage` o estado do filtro avançado de uma tabela.
|
||||||
|
* Salva tanto as linhas de filtro quanto a fórmula booleana customizada.
|
||||||
|
* Falhas de armazenamento são silenciadas para não interromper o fluxo do usuário.
|
||||||
|
*
|
||||||
|
* @param nomeTabela - Identificador único da tabela
|
||||||
|
* @param filtros - Estado completo dos filtros a persistir
|
||||||
|
*/
|
||||||
export declare function salvarFiltroAvancado<T>(nomeTabela: string, filtros: EliTabelaFiltroAvancadoSalvo<T>): void;
|
export declare function salvarFiltroAvancado<T>(nomeTabela: string, filtros: EliTabelaFiltroAvancadoSalvo<T>): void;
|
||||||
|
/**
|
||||||
|
* Remove do `localStorage` os filtros avançados salvos para uma tabela.
|
||||||
|
* Chamado ao executar "Limpar Filtros" no modal de filtro avançado.
|
||||||
|
*
|
||||||
|
* @param nomeTabela - Identificador único da tabela
|
||||||
|
*/
|
||||||
export declare function limparFiltroAvancado(nomeTabela: string): void;
|
export declare function limparFiltroAvancado(nomeTabela: string): void;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import type { LucideIcon } from "@lucide/vue";
|
import type { LucideIcon } from "@lucide/vue";
|
||||||
import type { tipoFiltro26 } from "p-comuns";
|
import type { tipoFiltroDeltaColunaTipada } 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";
|
||||||
|
|
@ -11,6 +10,20 @@ export type tipoComponenteCelulaBase<T extends tipoTabelaCelula> = readonly [
|
||||||
export type tipoComponenteCelula = {
|
export type tipoComponenteCelula = {
|
||||||
[K in tipoTabelaCelula]: tipoComponenteCelulaBase<K>;
|
[K in tipoTabelaCelula]: tipoComponenteCelulaBase<K>;
|
||||||
}[tipoTabelaCelula];
|
}[tipoTabelaCelula];
|
||||||
|
/**
|
||||||
|
* Cria uma tupla tipada `[tipo, dados]` que representa o conteúdo de uma célula da tabela.
|
||||||
|
* Funciona como type-safe builder: o TypeScript infere o tipo exato de `dados` a partir de `tipo`,
|
||||||
|
* garantindo que não seja possível passar dados incorretos para um tipo de célula.
|
||||||
|
*
|
||||||
|
* Uso esperado dentro de `tipoEliColuna.celula`:
|
||||||
|
* ```ts
|
||||||
|
* celula: (linha) => celulaTabela("textoSimples", { texto: linha.nome })
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @param tipo - Identificador do tipo de célula (chave de `tiposTabelaCelulas`)
|
||||||
|
* @param dados - Dados tipados para o tipo de célula escolhido
|
||||||
|
* @returns Tupla `[tipo, dados]` consumida pelo `EliTabelaCelula.vue`
|
||||||
|
*/
|
||||||
export declare const celulaTabela: <T extends tipoTabelaCelula>(tipo: T, dados: tiposTabelaCelulas[T]) => tipoComponenteCelulaBase<T>;
|
export declare const celulaTabela: <T extends tipoTabelaCelula>(tipo: T, dados: tiposTabelaCelulas[T]) => tipoComponenteCelulaBase<T>;
|
||||||
export type { tiposTabelaCelulas, tipoTabelaCelula };
|
export type { tiposTabelaCelulas, tipoTabelaCelula };
|
||||||
export type tipoEliColuna<T> = {
|
export type tipoEliColuna<T> = {
|
||||||
|
|
@ -52,7 +65,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?: tipoFiltro26<T>[];
|
filtros?: tipoFiltroDeltaColunaTipada<Extract<keyof T, string>>[];
|
||||||
coluna_ordem?: keyof T;
|
coluna_ordem?: keyof T;
|
||||||
direcao_ordem?: "asc" | "desc";
|
direcao_ordem?: "asc" | "desc";
|
||||||
offSet?: number;
|
offSet?: number;
|
||||||
|
|
@ -124,7 +137,7 @@ export type tipoEliTabelaConsulta<T> = {
|
||||||
/** Rótulo exibido no select de adicionar filtro */
|
/** Rótulo exibido no select de adicionar filtro */
|
||||||
rotulo: string;
|
rotulo: string;
|
||||||
/** vai gerar filtro e mescla com os filtros já existentes */
|
/** vai gerar filtro e mescla com os filtros já existentes */
|
||||||
filtro: (valor: unknown) => tipoFiltro26<T>;
|
filtro: (valor: unknown) => tipoFiltroDeltaColunaTipada<Extract<keyof T, string>> | undefined;
|
||||||
/** componente de entrada que vai receber o valor para o filtro */
|
/** componente de entrada que vai receber o valor para o filtro */
|
||||||
entrada: ComponenteEntrada;
|
entrada: ComponenteEntrada;
|
||||||
}[];
|
}[];
|
||||||
|
|
|
||||||
42
dist/types/componentes/EliTabela/useColunasOrdenacao.d.ts
vendored
Normal file
42
dist/types/componentes/EliTabela/useColunasOrdenacao.d.ts
vendored
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
/**
|
||||||
|
* useColunasOrdenacao
|
||||||
|
* Composable que encapsula a lógica de drag-and-drop e sincronização de estado
|
||||||
|
* do modal de configuração de colunas da EliTabela.
|
||||||
|
*
|
||||||
|
* Responsável por:
|
||||||
|
* - Sincronizar colunas visíveis/invisíveis com a configuração persistida
|
||||||
|
* - Aplicar ordem salva e adicionar novas colunas ao final
|
||||||
|
* - Gerenciar eventos de arrastar-e-soltar entre as duas listas
|
||||||
|
*
|
||||||
|
* Extraído de `EliTabelaModalColunas.vue` para manter o componente abaixo de 250 linhas.
|
||||||
|
*/
|
||||||
|
import { type Ref } from "vue";
|
||||||
|
import type { EliTabelaColunasConfig } from "./colunasStorage";
|
||||||
|
import type { tipoEliColuna } from "./types-eli-tabela";
|
||||||
|
type OrigemLista = "visiveis" | "invisiveis";
|
||||||
|
interface UseColunasOrdenacaoOptions {
|
||||||
|
aberto: Ref<boolean>;
|
||||||
|
rotulosColunas: Ref<string[]>;
|
||||||
|
configInicial: Ref<EliTabelaColunasConfig>;
|
||||||
|
colunas: Ref<tipoEliColuna<any>[]>;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Composable de drag-and-drop para reordenação e alternância de visibilidade de colunas.
|
||||||
|
* Sincroniza automaticamente quando `aberto`, `rotulosColunas`, `configInicial` ou `colunas` mudam.
|
||||||
|
*
|
||||||
|
* @param options.aberto - Ref que controla se o modal está aberto (gatilho de sincronização)
|
||||||
|
* @param options.rotulosColunas - Ref com todos os rótulos de colunas da tabela
|
||||||
|
* @param options.configInicial - Ref com a configuração persistida (visíveis + invisíveis)
|
||||||
|
* @param options.colunas - Ref com a definição completa das colunas (para ler `visivel: false` padrão)
|
||||||
|
* @returns Estado reativo e handlers de drag-and-drop prontos para o template
|
||||||
|
*/
|
||||||
|
export declare function useColunasOrdenacao({ aberto, rotulosColunas, configInicial, colunas, }: UseColunasOrdenacaoOptions): {
|
||||||
|
visiveisLocal: Ref<string[], string[]>;
|
||||||
|
invisiveisLocal: Ref<string[], string[]>;
|
||||||
|
itemSendoArrastado: Ref<string | null, string | null>;
|
||||||
|
onDragStart: (e: DragEvent, rotulo: string) => void;
|
||||||
|
onDragEnd: () => void;
|
||||||
|
onDragOverItem: (alvoRotulo: string, destino: OrigemLista) => void;
|
||||||
|
onDragOverLista: (destino: OrigemLista) => void;
|
||||||
|
};
|
||||||
|
export {};
|
||||||
132
dist/types/componentes/EliTabela/useEliTabela.d.ts
vendored
Normal file
132
dist/types/componentes/EliTabela/useEliTabela.d.ts
vendored
Normal file
|
|
@ -0,0 +1,132 @@
|
||||||
|
import type { tipoEliTabelaConsulta } from "./types-eli-tabela";
|
||||||
|
type TabelaProps = tipoEliTabelaConsulta<any>;
|
||||||
|
export declare function useEliTabela(props: {
|
||||||
|
tabela: TabelaProps;
|
||||||
|
selecionados: unknown[];
|
||||||
|
esconderDebug: boolean;
|
||||||
|
}, emit: (event: "update:selecionados", value: unknown[]) => void): {
|
||||||
|
menuAberto: import("vue").Ref<number | null, number | null>;
|
||||||
|
menuPopup: import("vue").Ref<{
|
||||||
|
menuEl: HTMLElement | null;
|
||||||
|
} | null, {
|
||||||
|
menuEl: HTMLElement | null;
|
||||||
|
} | {
|
||||||
|
menuEl: HTMLElement | null;
|
||||||
|
} | null>;
|
||||||
|
menuPopupPos: import("vue").Ref<{
|
||||||
|
top: number;
|
||||||
|
left: number;
|
||||||
|
}, {
|
||||||
|
top: number;
|
||||||
|
left: number;
|
||||||
|
} | {
|
||||||
|
top: number;
|
||||||
|
left: number;
|
||||||
|
}>;
|
||||||
|
possuiAcoes: (i: number) => boolean;
|
||||||
|
toggleMenu: (i: number, evento?: MouseEvent) => void;
|
||||||
|
acoesDisponiveisPorLinha: (i: number) => {
|
||||||
|
acao: import("./types-eli-tabela").tipoEliTabelaAcao<any>;
|
||||||
|
indice: number;
|
||||||
|
visivel: boolean;
|
||||||
|
}[];
|
||||||
|
paginaAtual: import("vue").Ref<number, number>;
|
||||||
|
valorBusca: import("vue").Ref<string, string>;
|
||||||
|
modalColunasAberto: import("vue").Ref<boolean, boolean>;
|
||||||
|
configColunas: import("vue").Ref<{
|
||||||
|
visiveis: string[];
|
||||||
|
invisiveis: string[];
|
||||||
|
}, import("./colunasStorage").EliTabelaColunasConfig | {
|
||||||
|
visiveis: string[];
|
||||||
|
invisiveis: string[];
|
||||||
|
}>;
|
||||||
|
linhasExpandidas: import("vue").Ref<Record<number, boolean>, Record<number, boolean>>;
|
||||||
|
rotulosColunas: import("vue").ComputedRef<string[]>;
|
||||||
|
colunasEfetivas: import("vue").ComputedRef<any[]>;
|
||||||
|
colunasInvisiveisEfetivas: import("vue").ComputedRef<import("./types-eli-tabela").tipoEliColuna<any>[]>;
|
||||||
|
temColunasInvisiveis: import("vue").ComputedRef<boolean>;
|
||||||
|
totalColunas: import("vue").ComputedRef<number>;
|
||||||
|
abrirModalColunas: () => void;
|
||||||
|
fecharModalColunas: () => void;
|
||||||
|
salvarModalColunas: (cfg: import("./colunasStorage").EliTabelaColunasConfig) => void;
|
||||||
|
alternarLinhaExpandida: (indice: number) => void;
|
||||||
|
mostrarSelecao: import("vue").ComputedRef<boolean>;
|
||||||
|
isSelecionado: (linha: unknown) => boolean;
|
||||||
|
alternarSelecao: (linha: unknown) => void;
|
||||||
|
todosSelecionados: import("vue").ComputedRef<boolean>;
|
||||||
|
algunsSelecionados: import("vue").ComputedRef<boolean>;
|
||||||
|
alternarSelecaoTodos: (forcarMarcar?: boolean) => void;
|
||||||
|
filtrosUi: import("vue").Ref<{
|
||||||
|
chave: string;
|
||||||
|
valor: unknown;
|
||||||
|
}[], {
|
||||||
|
chave: string;
|
||||||
|
valor: unknown;
|
||||||
|
}[] | {
|
||||||
|
chave: string;
|
||||||
|
valor: unknown;
|
||||||
|
}[]>;
|
||||||
|
filtrosFormula: import("vue").Ref<string, string>;
|
||||||
|
modalFiltroAberto: import("vue").Ref<boolean, boolean>;
|
||||||
|
exibirFiltroAvancado: import("vue").ComputedRef<boolean>;
|
||||||
|
filtrosAvancadosAtivos: import("vue").ComputedRef<import("p-comuns").tipoFiltroDeltaColunaTipada<any>[]>;
|
||||||
|
abrirModalFiltro: () => void;
|
||||||
|
fecharModalFiltro: () => void;
|
||||||
|
limparFiltrosAvancados: () => void;
|
||||||
|
salvarFiltrosAvancados: (novoLinhas: {
|
||||||
|
chave: string;
|
||||||
|
valor: unknown;
|
||||||
|
}[], novaFormula?: string) => void;
|
||||||
|
isDev: boolean;
|
||||||
|
tabela: import("vue").ComputedRef<tipoEliTabelaConsulta<any>>;
|
||||||
|
carregando: import("vue").Ref<boolean, boolean>;
|
||||||
|
erro: import("vue").Ref<string | null, string | null>;
|
||||||
|
linhas: import("vue").Ref<unknown[], unknown[]>;
|
||||||
|
linhasPaginadas: import("vue").ComputedRef<unknown[]>;
|
||||||
|
quantidade: import("vue").Ref<number, number>;
|
||||||
|
quantidadeFiltrada: import("vue").ComputedRef<number>;
|
||||||
|
acoesVisiveis: import("vue").Ref<boolean[][], boolean[][]>;
|
||||||
|
colunaOrdenacao: import("vue").Ref<string | null, string | null>;
|
||||||
|
direcaoOrdenacao: import("vue").Ref<"desc" | "asc", "desc" | "asc">;
|
||||||
|
registrosPorConsulta: import("vue").ComputedRef<number>;
|
||||||
|
totalPaginas: import("vue").ComputedRef<number>;
|
||||||
|
temAcoes: import("vue").ComputedRef<boolean>;
|
||||||
|
exibirBusca: import("vue").ComputedRef<boolean>;
|
||||||
|
acoesSuperiores: import("vue").ComputedRef<{
|
||||||
|
posicao: "superior" | "inferior";
|
||||||
|
icone?: import("@lucide/vue").LucideIcon;
|
||||||
|
cor?: string;
|
||||||
|
rotulo: string;
|
||||||
|
acao: (parametrosConsulta: import("./types-eli-tabela").parametrosConsulta<any> & {
|
||||||
|
atualizarConsulta: () => Promise<void>;
|
||||||
|
editarLista: (lista: any[]) => Promise<any[]>;
|
||||||
|
}) => void;
|
||||||
|
}[]>;
|
||||||
|
acoesInferiores: import("vue").ComputedRef<{
|
||||||
|
posicao: "superior" | "inferior";
|
||||||
|
icone?: import("@lucide/vue").LucideIcon;
|
||||||
|
cor?: string;
|
||||||
|
rotulo: string;
|
||||||
|
acao: (parametrosConsulta: import("./types-eli-tabela").parametrosConsulta<any> & {
|
||||||
|
atualizarConsulta: () => Promise<void>;
|
||||||
|
editarLista: (lista: any[]) => Promise<any[]>;
|
||||||
|
}) => void;
|
||||||
|
}[]>;
|
||||||
|
temAcoesSuperiores: import("vue").ComputedRef<boolean>;
|
||||||
|
temAcoesInferiores: import("vue").ComputedRef<boolean>;
|
||||||
|
parametrosConsultaAtuais: import("vue").ComputedRef<{
|
||||||
|
atualizarConsulta: () => Promise<void>;
|
||||||
|
editarLista: (novaLista: any[]) => Promise<any[]>;
|
||||||
|
filtros?: import("p-comuns").tipoFiltroDeltaColunaTipada<string>[] | undefined;
|
||||||
|
coluna_ordem?: string | number | symbol | undefined;
|
||||||
|
direcao_ordem?: "asc" | "desc";
|
||||||
|
offSet?: number;
|
||||||
|
limit?: number;
|
||||||
|
texto_busca?: string;
|
||||||
|
}>;
|
||||||
|
carregar: () => Promise<void>;
|
||||||
|
alternarOrdenacao: (chave?: string) => void;
|
||||||
|
atualizarBusca: (texto: string) => void;
|
||||||
|
irParaPagina: (pagina: number) => void;
|
||||||
|
};
|
||||||
|
export {};
|
||||||
38
dist/types/componentes/EliTabela/useEliTabelaColunas.d.ts
vendored
Normal file
38
dist/types/componentes/EliTabela/useEliTabelaColunas.d.ts
vendored
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
/**
|
||||||
|
* useEliTabelaColunas
|
||||||
|
* Gerencia visibilidade, ordenação e persistência da configuração de colunas da EliTabela.
|
||||||
|
* Controla também o modal de colunas e o estado de linhas expandidas (detalhe de colunas invisíveis).
|
||||||
|
*
|
||||||
|
* @param props - Props do componente (tabela.colunas, tabela.nome)
|
||||||
|
* @param temAcoes - Computed que indica se existem ações por linha (para totalColunas)
|
||||||
|
*/
|
||||||
|
import { type Ref } from "vue";
|
||||||
|
import { type EliTabelaColunasConfig } from "./colunasStorage";
|
||||||
|
import type { tipoEliColuna } from "./types-eli-tabela";
|
||||||
|
type PropsColunas = {
|
||||||
|
tabela: {
|
||||||
|
colunas: tipoEliColuna<any>[];
|
||||||
|
nome: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
export declare function useEliTabelaColunas(props: PropsColunas, temAcoes: Ref<boolean>): {
|
||||||
|
modalColunasAberto: Ref<boolean, boolean>;
|
||||||
|
configColunas: Ref<{
|
||||||
|
visiveis: string[];
|
||||||
|
invisiveis: string[];
|
||||||
|
}, EliTabelaColunasConfig | {
|
||||||
|
visiveis: string[];
|
||||||
|
invisiveis: string[];
|
||||||
|
}>;
|
||||||
|
linhasExpandidas: Ref<Record<number, boolean>, Record<number, boolean>>;
|
||||||
|
rotulosColunas: import("vue").ComputedRef<string[]>;
|
||||||
|
colunasEfetivas: import("vue").ComputedRef<any[]>;
|
||||||
|
colunasInvisiveisEfetivas: import("vue").ComputedRef<tipoEliColuna<any>[]>;
|
||||||
|
temColunasInvisiveis: import("vue").ComputedRef<boolean>;
|
||||||
|
totalColunas: import("vue").ComputedRef<number>;
|
||||||
|
abrirModalColunas: () => void;
|
||||||
|
fecharModalColunas: () => void;
|
||||||
|
salvarModalColunas: (cfg: EliTabelaColunasConfig) => void;
|
||||||
|
alternarLinhaExpandida: (indice: number) => void;
|
||||||
|
};
|
||||||
|
export {};
|
||||||
72
dist/types/componentes/EliTabela/useEliTabelaConsulta.d.ts
vendored
Normal file
72
dist/types/componentes/EliTabela/useEliTabelaConsulta.d.ts
vendored
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
/**
|
||||||
|
* useEliTabelaConsulta
|
||||||
|
* Gerencia o ciclo de consultas da EliTabela: carregamento, paginação, ordenação e busca.
|
||||||
|
* Usa um contador sequencial para descartar respostas de consultas antigas (race condition).
|
||||||
|
*
|
||||||
|
* @param props - Props do componente (tabela, esconderDebug)
|
||||||
|
* @param filtrosAvancadosAtivos - Computed com filtros ativos para envio à API
|
||||||
|
* @param paginaAtual - Ref da página atual (compartilhada com filtros e busca)
|
||||||
|
* @param valorBusca - Ref da busca textual (compartilhada com filtros avançados)
|
||||||
|
* @param linhasExpandidas - Ref das linhas expandidas (resetada ao carregar)
|
||||||
|
*/
|
||||||
|
import type { tipoFiltroDeltaColunaTipada } from "p-comuns";
|
||||||
|
import { type Ref } from "vue";
|
||||||
|
import type { parametrosConsulta, tipoEliTabelaConsulta } from "./types-eli-tabela";
|
||||||
|
type PropsConsulta = {
|
||||||
|
tabela: tipoEliTabelaConsulta<any>;
|
||||||
|
esconderDebug: boolean;
|
||||||
|
};
|
||||||
|
export declare function useEliTabelaConsulta(props: PropsConsulta, filtrosAvancadosAtivos: Ref<tipoFiltroDeltaColunaTipada<any>[]>, paginaAtual: Ref<number>, valorBusca: Ref<string>, linhasExpandidas: Ref<Record<number, boolean>>): {
|
||||||
|
isDev: boolean;
|
||||||
|
tabela: import("vue").ComputedRef<tipoEliTabelaConsulta<any>>;
|
||||||
|
carregando: Ref<boolean, boolean>;
|
||||||
|
erro: Ref<string | null, string | null>;
|
||||||
|
linhas: Ref<unknown[], unknown[]>;
|
||||||
|
linhasPaginadas: import("vue").ComputedRef<unknown[]>;
|
||||||
|
quantidade: Ref<number, number>;
|
||||||
|
quantidadeFiltrada: import("vue").ComputedRef<number>;
|
||||||
|
acoesVisiveis: Ref<boolean[][], boolean[][]>;
|
||||||
|
colunaOrdenacao: Ref<string | null, string | null>;
|
||||||
|
direcaoOrdenacao: Ref<"desc" | "asc", "desc" | "asc">;
|
||||||
|
registrosPorConsulta: import("vue").ComputedRef<number>;
|
||||||
|
totalPaginas: import("vue").ComputedRef<number>;
|
||||||
|
temAcoes: import("vue").ComputedRef<boolean>;
|
||||||
|
exibirBusca: import("vue").ComputedRef<boolean>;
|
||||||
|
acoesSuperiores: import("vue").ComputedRef<{
|
||||||
|
posicao: "superior" | "inferior";
|
||||||
|
icone?: import("@lucide/vue").LucideIcon;
|
||||||
|
cor?: string;
|
||||||
|
rotulo: string;
|
||||||
|
acao: (parametrosConsulta: parametrosConsulta<any> & {
|
||||||
|
atualizarConsulta: () => Promise<void>;
|
||||||
|
editarLista: (lista: any[]) => Promise<any[]>;
|
||||||
|
}) => void;
|
||||||
|
}[]>;
|
||||||
|
acoesInferiores: import("vue").ComputedRef<{
|
||||||
|
posicao: "superior" | "inferior";
|
||||||
|
icone?: import("@lucide/vue").LucideIcon;
|
||||||
|
cor?: string;
|
||||||
|
rotulo: string;
|
||||||
|
acao: (parametrosConsulta: parametrosConsulta<any> & {
|
||||||
|
atualizarConsulta: () => Promise<void>;
|
||||||
|
editarLista: (lista: any[]) => Promise<any[]>;
|
||||||
|
}) => void;
|
||||||
|
}[]>;
|
||||||
|
temAcoesSuperiores: import("vue").ComputedRef<boolean>;
|
||||||
|
temAcoesInferiores: import("vue").ComputedRef<boolean>;
|
||||||
|
parametrosConsultaAtuais: import("vue").ComputedRef<{
|
||||||
|
atualizarConsulta: () => Promise<void>;
|
||||||
|
editarLista: (novaLista: any[]) => Promise<any[]>;
|
||||||
|
filtros?: tipoFiltroDeltaColunaTipada<string>[] | undefined;
|
||||||
|
coluna_ordem?: string | number | symbol | undefined;
|
||||||
|
direcao_ordem?: "asc" | "desc";
|
||||||
|
offSet?: number;
|
||||||
|
limit?: number;
|
||||||
|
texto_busca?: string;
|
||||||
|
}>;
|
||||||
|
carregar: () => Promise<void>;
|
||||||
|
alternarOrdenacao: (chave?: string) => void;
|
||||||
|
atualizarBusca: (texto: string) => void;
|
||||||
|
irParaPagina: (pagina: number) => void;
|
||||||
|
};
|
||||||
|
export {};
|
||||||
45
dist/types/componentes/EliTabela/useEliTabelaFiltrosAvancados.d.ts
vendored
Normal file
45
dist/types/componentes/EliTabela/useEliTabelaFiltrosAvancados.d.ts
vendored
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
/**
|
||||||
|
* useEliTabelaFiltrosAvancados
|
||||||
|
* Gerencia o estado de filtros avançados da EliTabela, incluindo:
|
||||||
|
* - Linhas de filtro ativas (`filtrosUi`) e fórmula booleana (`filtrosFormula`)
|
||||||
|
* - Computed `filtrosAvancadosAtivos` que converte as linhas em FiltroDelta para a API
|
||||||
|
* - Abertura/fechamento e limpeza do modal de filtro avançado
|
||||||
|
*
|
||||||
|
* A fórmula é armazenada no formato `{chave}` (ex: `{email} E {nome}`) e convertida
|
||||||
|
* para IDs globais `{c1} {c2}` durante a montagem do FiltroDelta final.
|
||||||
|
*
|
||||||
|
* @param props - Props do componente (tabela.filtroAvancado, tabela.nome)
|
||||||
|
* @param paginaAtual - Ref da página atual (para resetar ao aplicar filtros)
|
||||||
|
* @param valorBusca - Ref da busca textual (limpa ao aplicar filtros avançados)
|
||||||
|
* @param carregar - Função de recarregamento dos dados
|
||||||
|
*/
|
||||||
|
import type { tipoFiltroDeltaColunaTipada } from "p-comuns";
|
||||||
|
import { type Ref } from "vue";
|
||||||
|
type PropsFiltros = {
|
||||||
|
tabela: {
|
||||||
|
filtroAvancado?: any[];
|
||||||
|
nome: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
type LinhaFiltroUI = {
|
||||||
|
chave: string;
|
||||||
|
valor: unknown;
|
||||||
|
};
|
||||||
|
export declare function useEliTabelaFiltrosAvancados(props: PropsFiltros, paginaAtual: Ref<number>, valorBusca: Ref<string>, carregar: () => Promise<void>): {
|
||||||
|
filtrosUi: Ref<{
|
||||||
|
chave: string;
|
||||||
|
valor: unknown;
|
||||||
|
}[], LinhaFiltroUI[] | {
|
||||||
|
chave: string;
|
||||||
|
valor: unknown;
|
||||||
|
}[]>;
|
||||||
|
filtrosFormula: Ref<string, string>;
|
||||||
|
modalFiltroAberto: Ref<boolean, boolean>;
|
||||||
|
exibirFiltroAvancado: import("vue").ComputedRef<boolean>;
|
||||||
|
filtrosAvancadosAtivos: import("vue").ComputedRef<tipoFiltroDeltaColunaTipada<any>[]>;
|
||||||
|
abrirModalFiltro: () => void;
|
||||||
|
fecharModalFiltro: () => void;
|
||||||
|
limparFiltrosAvancados: () => void;
|
||||||
|
salvarFiltrosAvancados: (novoLinhas: LinhaFiltroUI[], novaFormula?: string) => void;
|
||||||
|
};
|
||||||
|
export {};
|
||||||
43
dist/types/componentes/EliTabela/useEliTabelaMenu.d.ts
vendored
Normal file
43
dist/types/componentes/EliTabela/useEliTabelaMenu.d.ts
vendored
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
/**
|
||||||
|
* useEliTabelaMenu
|
||||||
|
* Gerencia o menu de ações por linha da EliTabela.
|
||||||
|
* Controla abertura/fechamento, posicionamento do popup e detecção de clique externo.
|
||||||
|
*
|
||||||
|
* @param props - Props do componente (tabela.acoesLinha)
|
||||||
|
* @param acoesVisiveis - Ref com matriz de visibilidade das ações por linha
|
||||||
|
*/
|
||||||
|
import { type Ref } from "vue";
|
||||||
|
import type { tipoEliTabelaAcao } from "./types-eli-tabela";
|
||||||
|
type PropsMenu = {
|
||||||
|
tabela: {
|
||||||
|
acoesLinha?: tipoEliTabelaAcao<any>[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
export declare function useEliTabelaMenu(props: PropsMenu, acoesVisiveis: Ref<boolean[][]>): {
|
||||||
|
menuAberto: Ref<number | null, number | null>;
|
||||||
|
menuPopup: Ref<{
|
||||||
|
menuEl: HTMLElement | null;
|
||||||
|
} | null, {
|
||||||
|
menuEl: HTMLElement | null;
|
||||||
|
} | {
|
||||||
|
menuEl: HTMLElement | null;
|
||||||
|
} | null>;
|
||||||
|
menuPopupPos: Ref<{
|
||||||
|
top: number;
|
||||||
|
left: number;
|
||||||
|
}, {
|
||||||
|
top: number;
|
||||||
|
left: number;
|
||||||
|
} | {
|
||||||
|
top: number;
|
||||||
|
left: number;
|
||||||
|
}>;
|
||||||
|
possuiAcoes: (i: number) => boolean;
|
||||||
|
toggleMenu: (i: number, evento?: MouseEvent) => void;
|
||||||
|
acoesDisponiveisPorLinha: (i: number) => {
|
||||||
|
acao: tipoEliTabelaAcao<any>;
|
||||||
|
indice: number;
|
||||||
|
visivel: boolean;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
export {};
|
||||||
26
dist/types/componentes/EliTabela/useEliTabelaSelecao.d.ts
vendored
Normal file
26
dist/types/componentes/EliTabela/useEliTabelaSelecao.d.ts
vendored
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
/**
|
||||||
|
* useEliTabelaSelecao
|
||||||
|
* Gerencia a seleção múltipla de linhas na EliTabela.
|
||||||
|
* Suporta seleção por objeto completo ou por chave configurável (`chave_selecao`).
|
||||||
|
*
|
||||||
|
* @param props - Props do componente (selecionados, tabela.chave_selecao, tabela.mostrar_selecao)
|
||||||
|
* @param emit - Função de emit para `update:selecionados`
|
||||||
|
* @param linhasPaginadas - Ref com as linhas da página atual (para todosSelecionados/algunsSelecionados)
|
||||||
|
*/
|
||||||
|
import { type Ref } from "vue";
|
||||||
|
type PropsSelecao = {
|
||||||
|
selecionados: unknown[];
|
||||||
|
tabela: {
|
||||||
|
mostrar_selecao?: boolean;
|
||||||
|
chave_selecao?: string | number | symbol;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
export declare function useEliTabelaSelecao(props: PropsSelecao, emit: (event: "update:selecionados", value: unknown[]) => void, linhasPaginadas: Ref<unknown[]>): {
|
||||||
|
mostrarSelecao: import("vue").ComputedRef<boolean>;
|
||||||
|
isSelecionado: (linha: unknown) => boolean;
|
||||||
|
alternarSelecao: (linha: unknown) => void;
|
||||||
|
todosSelecionados: import("vue").ComputedRef<boolean>;
|
||||||
|
algunsSelecionados: import("vue").ComputedRef<boolean>;
|
||||||
|
alternarSelecaoTodos: (forcarMarcar?: boolean) => void;
|
||||||
|
};
|
||||||
|
export {};
|
||||||
534
dist/types/componentes/EliTabela/useFiltroAvancado.d.ts
vendored
Normal file
534
dist/types/componentes/EliTabela/useFiltroAvancado.d.ts
vendored
Normal file
|
|
@ -0,0 +1,534 @@
|
||||||
|
/**
|
||||||
|
* useFiltroAvancado
|
||||||
|
* Composable que encapsula a lógica do modal de filtro avançado da EliTabela.
|
||||||
|
*
|
||||||
|
* Responsável por:
|
||||||
|
* - Gerenciar a lista de linhas de filtro ativas
|
||||||
|
* - Converter fórmulas booleanas entre notação de exibição (`{c1}`) e persistência (`{chave}`)
|
||||||
|
* - Normalizar filtros carregados do localStorage (compatibilidade com formatos legados)
|
||||||
|
* - Resolver o componente de entrada correto por tipo (`texto`, `numero`, `dataHora`)
|
||||||
|
*
|
||||||
|
* Extraído de `EliTabelaModalFiltroAvancado.vue` para manter o componente abaixo de 250 linhas.
|
||||||
|
*/
|
||||||
|
import { type Ref } from "vue";
|
||||||
|
import type { ComponenteEntrada } from "../EliEntrada/tiposEntradas";
|
||||||
|
import type { tipoEliTabelaConsulta } from "./types-eli-tabela";
|
||||||
|
export type FiltroBase<T> = NonNullable<tipoEliTabelaConsulta<T>["filtroAvancado"]>[number];
|
||||||
|
export type LinhaFiltro = {
|
||||||
|
chave: string;
|
||||||
|
entrada: ComponenteEntrada;
|
||||||
|
/** Valor do campo de filtro — unknown intencionalmente (vem de localStorage ou entrada do usuário). */
|
||||||
|
valor: unknown;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Retorna o rótulo de exibição de uma definição de filtro.
|
||||||
|
* Utilizado no template do modal para exibir o nome do campo ao usuário.
|
||||||
|
*
|
||||||
|
* @param f - Definição de filtro da configuração da tabela
|
||||||
|
* @returns Rótulo textual do filtro
|
||||||
|
*/
|
||||||
|
export declare function rotuloDoFiltro(f: FiltroBase<string>): string;
|
||||||
|
interface UseFiltroAvancadoOptions {
|
||||||
|
aberto: Ref<boolean>;
|
||||||
|
filtrosBase: Ref<FiltroBase<string>[]>;
|
||||||
|
modelo: Ref<Array<{
|
||||||
|
chave: string;
|
||||||
|
valor: unknown;
|
||||||
|
}>>;
|
||||||
|
modeloFormula: Ref<string>;
|
||||||
|
emit: {
|
||||||
|
(event: "fechar"): void;
|
||||||
|
(event: "limpar"): void;
|
||||||
|
(event: "salvar", linhas: Array<{
|
||||||
|
chave: string;
|
||||||
|
valor: unknown;
|
||||||
|
}>, formula?: string): void;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Composable principal do modal de filtro avançado da EliTabela.
|
||||||
|
* Sincroniza automaticamente quando o modal abre ou o modelo/filtrosBase mudam.
|
||||||
|
*
|
||||||
|
* @param options.aberto - Ref que controla se o modal está aberto
|
||||||
|
* @param options.filtrosBase - Ref com as definições de filtros disponíveis na tabela
|
||||||
|
* @param options.modelo - Ref com as linhas de filtro persistidas (vindo do pai)
|
||||||
|
* @param options.modeloFormula - Ref com a fórmula booleana persistida no pai
|
||||||
|
* @param options.emit - Função de emit do componente Vue pai
|
||||||
|
* @returns Estado reativo e ações do modal de filtro
|
||||||
|
*/
|
||||||
|
export declare function useFiltroAvancado({ aberto, filtrosBase, modelo, modeloFormula, emit, }: UseFiltroAvancadoOptions): {
|
||||||
|
linhas: Ref<{
|
||||||
|
chave: string;
|
||||||
|
entrada: readonly ["texto", {
|
||||||
|
rotulo: string;
|
||||||
|
placeholder?: string | undefined;
|
||||||
|
limiteCaracteres?: number | undefined;
|
||||||
|
formato?: "texto" | "email" | "url" | "telefone" | "cpfCnpj" | "cep" | undefined;
|
||||||
|
}] | readonly ["dataHora", {
|
||||||
|
rotulo: string;
|
||||||
|
placeholder?: string | undefined;
|
||||||
|
modo?: "data" | "dataHora" | undefined;
|
||||||
|
limpavel?: boolean | undefined;
|
||||||
|
erro?: boolean | undefined;
|
||||||
|
mensagensErro?: string | string[] | undefined;
|
||||||
|
dica?: string | undefined;
|
||||||
|
dicaPersistente?: boolean | undefined;
|
||||||
|
min?: string | undefined;
|
||||||
|
max?: string | undefined;
|
||||||
|
densidade?: import("../..").CampoDensidade | undefined;
|
||||||
|
variante?: import("../..").CampoVariante | undefined;
|
||||||
|
}] | readonly ["numero", {
|
||||||
|
rotulo: string;
|
||||||
|
placeholder?: string | undefined;
|
||||||
|
sufixo?: string | undefined;
|
||||||
|
prefixo?: string | undefined;
|
||||||
|
precisao?: number | undefined;
|
||||||
|
}] | readonly ["paragrafo", {
|
||||||
|
rotulo: string;
|
||||||
|
placeholder?: string | undefined;
|
||||||
|
linhas?: number | undefined;
|
||||||
|
limiteCaracteres?: number | undefined;
|
||||||
|
limpavel?: boolean | undefined;
|
||||||
|
erro?: boolean | undefined;
|
||||||
|
mensagensErro?: string | string[] | undefined;
|
||||||
|
dica?: string | undefined;
|
||||||
|
dicaPersistente?: boolean | undefined;
|
||||||
|
densidade?: import("../..").CampoDensidade | undefined;
|
||||||
|
variante?: import("../..").CampoVariante | undefined;
|
||||||
|
}] | readonly ["selecao", {
|
||||||
|
rotulo: string;
|
||||||
|
placeholder?: string | undefined;
|
||||||
|
itens: () => {
|
||||||
|
chave: string;
|
||||||
|
rotulo: string;
|
||||||
|
}[] | Promise<{
|
||||||
|
chave: string;
|
||||||
|
rotulo: string;
|
||||||
|
}[]>;
|
||||||
|
limpavel?: boolean | undefined;
|
||||||
|
erro?: boolean | undefined;
|
||||||
|
mensagensErro?: string | string[] | undefined;
|
||||||
|
dica?: string | undefined;
|
||||||
|
dicaPersistente?: boolean | undefined;
|
||||||
|
densidade?: import("../..").CampoDensidade | undefined;
|
||||||
|
variante?: import("../..").CampoVariante | undefined;
|
||||||
|
}];
|
||||||
|
valor: unknown;
|
||||||
|
}[], LinhaFiltro[] | {
|
||||||
|
chave: string;
|
||||||
|
entrada: readonly ["texto", {
|
||||||
|
rotulo: string;
|
||||||
|
placeholder?: string | undefined;
|
||||||
|
limiteCaracteres?: number | undefined;
|
||||||
|
formato?: "texto" | "email" | "url" | "telefone" | "cpfCnpj" | "cep" | undefined;
|
||||||
|
}] | readonly ["dataHora", {
|
||||||
|
rotulo: string;
|
||||||
|
placeholder?: string | undefined;
|
||||||
|
modo?: "data" | "dataHora" | undefined;
|
||||||
|
limpavel?: boolean | undefined;
|
||||||
|
erro?: boolean | undefined;
|
||||||
|
mensagensErro?: string | string[] | undefined;
|
||||||
|
dica?: string | undefined;
|
||||||
|
dicaPersistente?: boolean | undefined;
|
||||||
|
min?: string | undefined;
|
||||||
|
max?: string | undefined;
|
||||||
|
densidade?: import("../..").CampoDensidade | undefined;
|
||||||
|
variante?: import("../..").CampoVariante | undefined;
|
||||||
|
}] | readonly ["numero", {
|
||||||
|
rotulo: string;
|
||||||
|
placeholder?: string | undefined;
|
||||||
|
sufixo?: string | undefined;
|
||||||
|
prefixo?: string | undefined;
|
||||||
|
precisao?: number | undefined;
|
||||||
|
}] | readonly ["paragrafo", {
|
||||||
|
rotulo: string;
|
||||||
|
placeholder?: string | undefined;
|
||||||
|
linhas?: number | undefined;
|
||||||
|
limiteCaracteres?: number | undefined;
|
||||||
|
limpavel?: boolean | undefined;
|
||||||
|
erro?: boolean | undefined;
|
||||||
|
mensagensErro?: string | string[] | undefined;
|
||||||
|
dica?: string | undefined;
|
||||||
|
dicaPersistente?: boolean | undefined;
|
||||||
|
densidade?: import("../..").CampoDensidade | undefined;
|
||||||
|
variante?: import("../..").CampoVariante | undefined;
|
||||||
|
}] | readonly ["selecao", {
|
||||||
|
rotulo: string;
|
||||||
|
placeholder?: string | undefined;
|
||||||
|
itens: () => {
|
||||||
|
chave: string;
|
||||||
|
rotulo: string;
|
||||||
|
}[] | Promise<{
|
||||||
|
chave: string;
|
||||||
|
rotulo: string;
|
||||||
|
}[]>;
|
||||||
|
limpavel?: boolean | undefined;
|
||||||
|
erro?: boolean | undefined;
|
||||||
|
mensagensErro?: string | string[] | undefined;
|
||||||
|
dica?: string | undefined;
|
||||||
|
dicaPersistente?: boolean | undefined;
|
||||||
|
densidade?: import("../..").CampoDensidade | undefined;
|
||||||
|
variante?: import("../..").CampoVariante | undefined;
|
||||||
|
}];
|
||||||
|
valor: unknown;
|
||||||
|
}[]>;
|
||||||
|
opcoesParaAdicionar: import("vue").ComputedRef<{
|
||||||
|
chave: string;
|
||||||
|
rotulo: string;
|
||||||
|
filtro: (valor: unknown) => import("p-comuns").tipoFiltroDeltaColunaTipada<"small" | "length" | "concat" | "slice" | "indexOf" | "lastIndexOf" | "includes" | "toString" | "charAt" | "charCodeAt" | "localeCompare" | "match" | "replace" | "search" | "split" | "substring" | "toLowerCase" | "toLocaleLowerCase" | "toUpperCase" | "toLocaleUpperCase" | "trim" | "substr" | "codePointAt" | "endsWith" | "normalize" | "repeat" | "startsWith" | "anchor" | "big" | "blink" | "bold" | "fixed" | "fontcolor" | "fontsize" | "italics" | "link" | "strike" | "sub" | "sup" | "padStart" | "padEnd" | "trimEnd" | "trimStart" | "trimLeft" | "trimRight" | "matchAll" | "valueOf"> | undefined;
|
||||||
|
entrada: ComponenteEntrada;
|
||||||
|
}[]>;
|
||||||
|
chaveParaAdicionar: Ref<string, string>;
|
||||||
|
formulaCustomizada: Ref<string, string>;
|
||||||
|
formulaGerada: import("vue").ComputedRef<string>;
|
||||||
|
componenteEntrada: (entrada: ComponenteEntrada) => import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
||||||
|
value: {
|
||||||
|
type: import("vue").PropType<string | null | undefined>;
|
||||||
|
default: undefined;
|
||||||
|
};
|
||||||
|
opcoes: {
|
||||||
|
type: import("vue").PropType<{
|
||||||
|
rotulo: string;
|
||||||
|
placeholder?: string;
|
||||||
|
} & {
|
||||||
|
modo?: "data" | "dataHora";
|
||||||
|
limpavel?: boolean;
|
||||||
|
erro?: boolean;
|
||||||
|
mensagensErro?: string | string[];
|
||||||
|
dica?: string;
|
||||||
|
dicaPersistente?: boolean;
|
||||||
|
min?: string;
|
||||||
|
max?: string;
|
||||||
|
densidade?: import("../..").CampoDensidade;
|
||||||
|
variante?: import("../..").CampoVariante;
|
||||||
|
}>;
|
||||||
|
required: false;
|
||||||
|
default: undefined;
|
||||||
|
};
|
||||||
|
modelValue: {
|
||||||
|
type: import("vue").PropType<string | null>;
|
||||||
|
default: null;
|
||||||
|
};
|
||||||
|
modo: {
|
||||||
|
type: import("vue").PropType<"data" | "dataHora" | undefined>;
|
||||||
|
default: undefined;
|
||||||
|
};
|
||||||
|
rotulo: {
|
||||||
|
type: StringConstructor;
|
||||||
|
default: undefined;
|
||||||
|
};
|
||||||
|
placeholder: {
|
||||||
|
type: StringConstructor;
|
||||||
|
default: undefined;
|
||||||
|
};
|
||||||
|
desabilitado: {
|
||||||
|
type: BooleanConstructor;
|
||||||
|
default: undefined;
|
||||||
|
};
|
||||||
|
limpavel: {
|
||||||
|
type: BooleanConstructor;
|
||||||
|
default: undefined;
|
||||||
|
};
|
||||||
|
erro: {
|
||||||
|
type: BooleanConstructor;
|
||||||
|
default: undefined;
|
||||||
|
};
|
||||||
|
mensagensErro: {
|
||||||
|
type: import("vue").PropType<string | string[]>;
|
||||||
|
default: undefined;
|
||||||
|
};
|
||||||
|
dica: {
|
||||||
|
type: StringConstructor;
|
||||||
|
default: undefined;
|
||||||
|
};
|
||||||
|
dicaPersistente: {
|
||||||
|
type: BooleanConstructor;
|
||||||
|
default: undefined;
|
||||||
|
};
|
||||||
|
densidade: {
|
||||||
|
type: import("vue").PropType<import("../..").CampoDensidade>;
|
||||||
|
default: undefined;
|
||||||
|
};
|
||||||
|
variante: {
|
||||||
|
type: import("vue").PropType<import("../..").CampoVariante>;
|
||||||
|
default: undefined;
|
||||||
|
};
|
||||||
|
min: {
|
||||||
|
type: import("vue").PropType<string | undefined>;
|
||||||
|
default: undefined;
|
||||||
|
};
|
||||||
|
max: {
|
||||||
|
type: import("vue").PropType<string | undefined>;
|
||||||
|
default: undefined;
|
||||||
|
};
|
||||||
|
}>, {
|
||||||
|
attrs: import("vue").Attrs;
|
||||||
|
valor: import("vue").WritableComputedRef<string, string>;
|
||||||
|
tipoInput: import("vue").ComputedRef<"date" | "datetime-local">;
|
||||||
|
minLocal: import("vue").ComputedRef<string | undefined>;
|
||||||
|
maxLocal: import("vue").ComputedRef<string | undefined>;
|
||||||
|
opcoesEfetivas: import("vue").ComputedRef<{
|
||||||
|
rotulo: string;
|
||||||
|
placeholder?: string;
|
||||||
|
} & {
|
||||||
|
modo?: "data" | "dataHora";
|
||||||
|
limpavel?: boolean;
|
||||||
|
erro?: boolean;
|
||||||
|
mensagensErro?: string | string[];
|
||||||
|
dica?: string;
|
||||||
|
dicaPersistente?: boolean;
|
||||||
|
min?: string;
|
||||||
|
max?: string;
|
||||||
|
densidade?: import("../..").CampoDensidade;
|
||||||
|
variante?: import("../..").CampoVariante;
|
||||||
|
}>;
|
||||||
|
desabilitadoEfetivo: import("vue").ComputedRef<boolean>;
|
||||||
|
emitCompatFocus: () => void;
|
||||||
|
emitCompatBlur: () => void;
|
||||||
|
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
||||||
|
"update:value": (_v: string | null) => true;
|
||||||
|
input: (_v: string | null) => true;
|
||||||
|
change: (_v: string | null) => true;
|
||||||
|
"update:modelValue": (_v: string | null) => true;
|
||||||
|
alterar: (_v: string | null) => true;
|
||||||
|
foco: () => true;
|
||||||
|
desfoco: () => true;
|
||||||
|
focus: () => true;
|
||||||
|
blur: () => true;
|
||||||
|
}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
||||||
|
value: {
|
||||||
|
type: import("vue").PropType<string | null | undefined>;
|
||||||
|
default: undefined;
|
||||||
|
};
|
||||||
|
opcoes: {
|
||||||
|
type: import("vue").PropType<{
|
||||||
|
rotulo: string;
|
||||||
|
placeholder?: string;
|
||||||
|
} & {
|
||||||
|
modo?: "data" | "dataHora";
|
||||||
|
limpavel?: boolean;
|
||||||
|
erro?: boolean;
|
||||||
|
mensagensErro?: string | string[];
|
||||||
|
dica?: string;
|
||||||
|
dicaPersistente?: boolean;
|
||||||
|
min?: string;
|
||||||
|
max?: string;
|
||||||
|
densidade?: import("../..").CampoDensidade;
|
||||||
|
variante?: import("../..").CampoVariante;
|
||||||
|
}>;
|
||||||
|
required: false;
|
||||||
|
default: undefined;
|
||||||
|
};
|
||||||
|
modelValue: {
|
||||||
|
type: import("vue").PropType<string | null>;
|
||||||
|
default: null;
|
||||||
|
};
|
||||||
|
modo: {
|
||||||
|
type: import("vue").PropType<"data" | "dataHora" | undefined>;
|
||||||
|
default: undefined;
|
||||||
|
};
|
||||||
|
rotulo: {
|
||||||
|
type: StringConstructor;
|
||||||
|
default: undefined;
|
||||||
|
};
|
||||||
|
placeholder: {
|
||||||
|
type: StringConstructor;
|
||||||
|
default: undefined;
|
||||||
|
};
|
||||||
|
desabilitado: {
|
||||||
|
type: BooleanConstructor;
|
||||||
|
default: undefined;
|
||||||
|
};
|
||||||
|
limpavel: {
|
||||||
|
type: BooleanConstructor;
|
||||||
|
default: undefined;
|
||||||
|
};
|
||||||
|
erro: {
|
||||||
|
type: BooleanConstructor;
|
||||||
|
default: undefined;
|
||||||
|
};
|
||||||
|
mensagensErro: {
|
||||||
|
type: import("vue").PropType<string | string[]>;
|
||||||
|
default: undefined;
|
||||||
|
};
|
||||||
|
dica: {
|
||||||
|
type: StringConstructor;
|
||||||
|
default: undefined;
|
||||||
|
};
|
||||||
|
dicaPersistente: {
|
||||||
|
type: BooleanConstructor;
|
||||||
|
default: undefined;
|
||||||
|
};
|
||||||
|
densidade: {
|
||||||
|
type: import("vue").PropType<import("../..").CampoDensidade>;
|
||||||
|
default: undefined;
|
||||||
|
};
|
||||||
|
variante: {
|
||||||
|
type: import("vue").PropType<import("../..").CampoVariante>;
|
||||||
|
default: undefined;
|
||||||
|
};
|
||||||
|
min: {
|
||||||
|
type: import("vue").PropType<string | undefined>;
|
||||||
|
default: undefined;
|
||||||
|
};
|
||||||
|
max: {
|
||||||
|
type: import("vue").PropType<string | undefined>;
|
||||||
|
default: undefined;
|
||||||
|
};
|
||||||
|
}>> & Readonly<{
|
||||||
|
onInput?: ((_v: string | null) => any) | undefined;
|
||||||
|
"onUpdate:value"?: ((_v: string | null) => any) | undefined;
|
||||||
|
onChange?: ((_v: string | null) => any) | undefined;
|
||||||
|
"onUpdate:modelValue"?: ((_v: string | null) => any) | undefined;
|
||||||
|
onAlterar?: ((_v: string | null) => any) | undefined;
|
||||||
|
onFoco?: (() => any) | undefined;
|
||||||
|
onDesfoco?: (() => any) | undefined;
|
||||||
|
onFocus?: (() => any) | undefined;
|
||||||
|
onBlur?: (() => any) | undefined;
|
||||||
|
}>, {
|
||||||
|
modo: "data" | "dataHora" | undefined;
|
||||||
|
limpavel: boolean;
|
||||||
|
erro: boolean;
|
||||||
|
mensagensErro: string | string[];
|
||||||
|
dica: string;
|
||||||
|
dicaPersistente: boolean;
|
||||||
|
min: string | undefined;
|
||||||
|
max: string | undefined;
|
||||||
|
densidade: import("../..").CampoDensidade;
|
||||||
|
variante: import("../..").CampoVariante;
|
||||||
|
opcoes: {
|
||||||
|
rotulo: string;
|
||||||
|
placeholder?: string;
|
||||||
|
} & {
|
||||||
|
modo?: "data" | "dataHora";
|
||||||
|
limpavel?: boolean;
|
||||||
|
erro?: boolean;
|
||||||
|
mensagensErro?: string | string[];
|
||||||
|
dica?: string;
|
||||||
|
dicaPersistente?: boolean;
|
||||||
|
min?: string;
|
||||||
|
max?: string;
|
||||||
|
densidade?: import("../..").CampoDensidade;
|
||||||
|
variante?: import("../..").CampoVariante;
|
||||||
|
};
|
||||||
|
value: string | null | undefined;
|
||||||
|
modelValue: string | null;
|
||||||
|
rotulo: string;
|
||||||
|
placeholder: string;
|
||||||
|
desabilitado: boolean;
|
||||||
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any> | import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
||||||
|
value: {
|
||||||
|
type: import("vue").PropType<number | null | undefined>;
|
||||||
|
default: undefined;
|
||||||
|
};
|
||||||
|
opcoes: {
|
||||||
|
type: import("vue").PropType<{
|
||||||
|
rotulo: string;
|
||||||
|
placeholder?: string;
|
||||||
|
} & {
|
||||||
|
sufixo?: string;
|
||||||
|
prefixo?: string;
|
||||||
|
precisao?: number;
|
||||||
|
}>;
|
||||||
|
required: true;
|
||||||
|
};
|
||||||
|
}>, {
|
||||||
|
attrs: import("vue").Attrs;
|
||||||
|
emit: ((event: "input", _v: number | null | undefined) => void) & ((event: "update:value", _v: number | null | undefined) => void) & ((event: "change", _v: number | null | undefined) => void) & ((event: "focus") => void) & ((event: "blur") => void);
|
||||||
|
displayValue: Ref<string, string>;
|
||||||
|
isInteiro: import("vue").ComputedRef<boolean>;
|
||||||
|
onUpdateModelValue: (texto: string) => void;
|
||||||
|
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
||||||
|
"update:value": (_v: number | null | undefined) => true;
|
||||||
|
input: (_v: number | null | undefined) => true;
|
||||||
|
change: (_v: number | null | undefined) => true;
|
||||||
|
focus: () => true;
|
||||||
|
blur: () => true;
|
||||||
|
}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
||||||
|
value: {
|
||||||
|
type: import("vue").PropType<number | null | undefined>;
|
||||||
|
default: undefined;
|
||||||
|
};
|
||||||
|
opcoes: {
|
||||||
|
type: import("vue").PropType<{
|
||||||
|
rotulo: string;
|
||||||
|
placeholder?: string;
|
||||||
|
} & {
|
||||||
|
sufixo?: string;
|
||||||
|
prefixo?: string;
|
||||||
|
precisao?: number;
|
||||||
|
}>;
|
||||||
|
required: true;
|
||||||
|
};
|
||||||
|
}>> & Readonly<{
|
||||||
|
onInput?: ((_v: number | null | undefined) => any) | undefined;
|
||||||
|
"onUpdate:value"?: ((_v: number | null | undefined) => any) | undefined;
|
||||||
|
onChange?: ((_v: number | null | undefined) => any) | undefined;
|
||||||
|
onFocus?: (() => any) | undefined;
|
||||||
|
onBlur?: (() => any) | undefined;
|
||||||
|
}>, {
|
||||||
|
value: number | null | undefined;
|
||||||
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any> | import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
||||||
|
value: {
|
||||||
|
type: import("vue").PropType<string | null | undefined>;
|
||||||
|
default: undefined;
|
||||||
|
};
|
||||||
|
opcoes: {
|
||||||
|
type: import("vue").PropType<{
|
||||||
|
rotulo: string;
|
||||||
|
placeholder?: string;
|
||||||
|
} & {
|
||||||
|
limiteCaracteres?: number;
|
||||||
|
formato?: "texto" | "email" | "url" | "telefone" | "cpfCnpj" | "cep";
|
||||||
|
}>;
|
||||||
|
required: true;
|
||||||
|
};
|
||||||
|
}>, {
|
||||||
|
attrs: import("vue").Attrs;
|
||||||
|
emit: ((event: "input", _v: string | null | undefined) => void) & ((event: "update:value", _v: string | null | undefined) => void) & ((event: "change", _v: string | null | undefined) => void) & ((event: "focus") => void) & ((event: "blur") => void);
|
||||||
|
localValue: import("vue").WritableComputedRef<string | null | undefined, string | null | undefined>;
|
||||||
|
inputHtmlType: import("vue").ComputedRef<"text" | "email" | "url">;
|
||||||
|
inputMode: import("vue").ComputedRef<string | undefined>;
|
||||||
|
onInput: (e: Event) => void;
|
||||||
|
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
||||||
|
"update:value": (_v: string | null | undefined) => true;
|
||||||
|
input: (_v: string | null | undefined) => true;
|
||||||
|
change: (_v: string | null | undefined) => true;
|
||||||
|
focus: () => true;
|
||||||
|
blur: () => true;
|
||||||
|
}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
||||||
|
value: {
|
||||||
|
type: import("vue").PropType<string | null | undefined>;
|
||||||
|
default: undefined;
|
||||||
|
};
|
||||||
|
opcoes: {
|
||||||
|
type: import("vue").PropType<{
|
||||||
|
rotulo: string;
|
||||||
|
placeholder?: string;
|
||||||
|
} & {
|
||||||
|
limiteCaracteres?: number;
|
||||||
|
formato?: "texto" | "email" | "url" | "telefone" | "cpfCnpj" | "cep";
|
||||||
|
}>;
|
||||||
|
required: true;
|
||||||
|
};
|
||||||
|
}>> & Readonly<{
|
||||||
|
onInput?: ((_v: string | null | undefined) => any) | undefined;
|
||||||
|
"onUpdate:value"?: ((_v: string | null | undefined) => any) | undefined;
|
||||||
|
onChange?: ((_v: string | null | undefined) => any) | undefined;
|
||||||
|
onFocus?: (() => any) | undefined;
|
||||||
|
onBlur?: (() => any) | undefined;
|
||||||
|
}>, {
|
||||||
|
value: string | null | undefined;
|
||||||
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
||||||
|
opcoesEntrada: (entrada: ComponenteEntrada) => {
|
||||||
|
rotulo: string;
|
||||||
|
};
|
||||||
|
adicionar: () => void;
|
||||||
|
remover: (idx: number) => void;
|
||||||
|
emitFechar: () => void;
|
||||||
|
emitSalvar: () => void;
|
||||||
|
emitLimpar: () => void;
|
||||||
|
rotuloDoFiltro: typeof rotuloDoFiltro;
|
||||||
|
rotuloDoFiltroAtivo: (chave: string) => string;
|
||||||
|
};
|
||||||
|
export {};
|
||||||
3
dist/types/index.d.ts
vendored
3
dist/types/index.d.ts
vendored
|
|
@ -1,10 +1,11 @@
|
||||||
import type { Plugin } from "vue";
|
import type { Plugin } from "vue";
|
||||||
import "./styles/eli-vue-fonts.css";
|
import "./styles/eli-vue-fonts.css";
|
||||||
|
import "./design-tokens.css";
|
||||||
import { EliBotao } from "./componentes/botao";
|
import { EliBotao } from "./componentes/botao";
|
||||||
import { EliCartao } from "./componentes/cartao";
|
import { EliCartao } from "./componentes/cartao";
|
||||||
import { EliBadge } from "./componentes/indicador";
|
import { EliBadge } from "./componentes/indicador";
|
||||||
import { EliOlaMundo } from "./componentes/ola_mundo";
|
import { EliOlaMundo } from "./componentes/ola_mundo";
|
||||||
export { criarFiltro26 } from "p-comuns";
|
export { iniciarFiltroDelta } 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";
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "eli-vue",
|
"name": "eli-vue",
|
||||||
"version": "0.1.144",
|
"version": "0.1.183",
|
||||||
"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",
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"biome": "pnpm exec biome check --write",
|
"biome": "pnpm exec biome check --write",
|
||||||
"check": "pnpm run biome && npx tsc --noEmit",
|
"check": "pnpm run biome && npx tsc --noEmit",
|
||||||
"prebuild": "npm version patch --no-git-tag-version",
|
"prebuild": "npm version patch --no-git-tag-version && bash scripts/check-linhas.sh",
|
||||||
"build:types": "vue-tsc -p tsconfig.build.json --declaration --emitDeclarationOnly",
|
"build:types": "vue-tsc -p tsconfig.build.json --declaration --emitDeclarationOnly",
|
||||||
"build": "pnpm run biome && vite build && npm run build:types",
|
"build": "pnpm run biome && vite build && npm run build:types",
|
||||||
"dev": "pnpm run biome && pnpm run fechar && vite",
|
"dev": "pnpm run biome && pnpm run fechar && vite",
|
||||||
|
|
@ -41,6 +41,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@lucide/vue": "^1.16.0",
|
"@lucide/vue": "^1.16.0",
|
||||||
|
"alasql": "^4.17.3",
|
||||||
"cross-fetch": "^4.1.0",
|
"cross-fetch": "^4.1.0",
|
||||||
"dayjs": "^1.11.19",
|
"dayjs": "^1.11.19",
|
||||||
"p-comuns": "git+https://git2.idz.one/publico/_comuns.git",
|
"p-comuns": "git+https://git2.idz.one/publico/_comuns.git",
|
||||||
|
|
|
||||||
2728
pnpm-lock.yaml
generated
2728
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
49
scripts/check-linhas.sh
Executable file
49
scripts/check-linhas.sh
Executable file
|
|
@ -0,0 +1,49 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# check-linhas.sh
|
||||||
|
# Garante que nenhum arquivo *.vue, *.ts ou *.tsx ultrapasse o limite de linhas definido.
|
||||||
|
# Executado automaticamente no prebuild via package.json.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
LIMITE=250
|
||||||
|
|
||||||
|
# Arquivos isentos permanentes (gerados, dados de teste, etc.)
|
||||||
|
# Formato: caminho relativo a partir da raiz do projeto
|
||||||
|
ISENTOS=(
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
RAIZ="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
|
ERROS=0
|
||||||
|
|
||||||
|
while IFS= read -r arquivo; do
|
||||||
|
relativo="${arquivo#$RAIZ/}"
|
||||||
|
|
||||||
|
# Verifica se está na lista de isentos
|
||||||
|
isento=false
|
||||||
|
for i in "${ISENTOS[@]}"; do
|
||||||
|
if [ "$relativo" = "$i" ]; then
|
||||||
|
isento=true
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if $isento; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
linhas=$(wc -l < "$arquivo")
|
||||||
|
if [ "$linhas" -gt "$LIMITE" ]; then
|
||||||
|
echo "❌ $relativo ($linhas linhas) — limite: $LIMITE"
|
||||||
|
ERROS=$((ERROS + 1))
|
||||||
|
fi
|
||||||
|
done < <(find "$RAIZ/src" \( -name "*.vue" -o -name "*.ts" -o -name "*.tsx" \) -not -path "*/node_modules/*")
|
||||||
|
|
||||||
|
if [ "$ERROS" -gt 0 ]; then
|
||||||
|
echo ""
|
||||||
|
echo "⚠️ $ERROS arquivo(s) ultrapassam o limite de $LIMITE linhas."
|
||||||
|
echo " Estratégias: subcomponentes, composables use*.ts, ou separar CSS."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "✅ Todos os arquivos (*.vue, *.ts, *.tsx) estão dentro do limite de $LIMITE linhas."
|
||||||
59
scripts/check-vue-linhas.sh
Executable file
59
scripts/check-vue-linhas.sh
Executable file
|
|
@ -0,0 +1,59 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# check-vue-linhas.sh
|
||||||
|
# Garante que nenhum arquivo *.vue ultrapasse o limite de linhas definido.
|
||||||
|
#
|
||||||
|
# Arquivos listados em ISENTOS são legados em processo de refatoração —
|
||||||
|
# cada um deve ser quebrado em componentes menores antes de ser removido daqui.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
LIMITE=250
|
||||||
|
|
||||||
|
# Todos os arquivos *.vue devem respeitar o limite.
|
||||||
|
# Não existem isentos.
|
||||||
|
ISENTOS=()
|
||||||
|
|
||||||
|
RAIZ="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
|
ERROS=0
|
||||||
|
|
||||||
|
# Constrói padrão grep para os isentos
|
||||||
|
isentos_grep=""
|
||||||
|
for isento in "${ISENTOS[@]}"; do
|
||||||
|
if [ -n "$isentos_grep" ]; then
|
||||||
|
isentos_grep="${isentos_grep}\|${isento}"
|
||||||
|
else
|
||||||
|
isentos_grep="${isento}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
while IFS= read -r arquivo; do
|
||||||
|
relativo="${arquivo#$RAIZ/}"
|
||||||
|
|
||||||
|
# Verifica se está na lista de isentos
|
||||||
|
isento=false
|
||||||
|
for i in "${ISENTOS[@]}"; do
|
||||||
|
if [ "$relativo" = "$i" ]; then
|
||||||
|
isento=true
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if $isento; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
linhas=$(wc -l < "$arquivo")
|
||||||
|
if [ "$linhas" -gt "$LIMITE" ]; then
|
||||||
|
echo "❌ $relativo ($linhas linhas) — limite: $LIMITE"
|
||||||
|
ERROS=$((ERROS + 1))
|
||||||
|
fi
|
||||||
|
done < <(find "$RAIZ/src" -name "*.vue" -not -path "*/node_modules/*")
|
||||||
|
|
||||||
|
if [ "$ERROS" -gt 0 ]; then
|
||||||
|
echo ""
|
||||||
|
echo "⚠️ $ERROS arquivo(s) Vue ultrapassam o limite de $LIMITE linhas."
|
||||||
|
echo " Quebre em componentes menores antes de fazer build."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "✅ Todos os arquivos Vue estão dentro do limite de $LIMITE linhas."
|
||||||
|
|
@ -23,7 +23,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { dayjsbr } from "p-comuns"
|
import dayjs from "dayjs"
|
||||||
import { computed, defineComponent, type PropType } from "vue"
|
import { computed, defineComponent, type PropType } from "vue"
|
||||||
import type { CampoDensidade, CampoVariante } from "../../tipos"
|
import type { CampoDensidade, CampoVariante } from "../../tipos"
|
||||||
import type { PadroesEntradas } from "./tiposEntradas"
|
import type { PadroesEntradas } from "./tiposEntradas"
|
||||||
|
|
@ -122,13 +122,9 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const modoEfetivo = computed<"data" | "dataHora">(
|
const modoEfetivo = computed<"data" | "dataHora">(() => opcoesEfetivas.value.modo ?? "dataHora")
|
||||||
() => opcoesEfetivas.value.modo ?? "dataHora",
|
|
||||||
)
|
|
||||||
|
|
||||||
const desabilitadoEfetivo = computed<boolean>(() =>
|
const desabilitadoEfetivo = computed<boolean>(() => Boolean(props.desabilitado))
|
||||||
Boolean(props.desabilitado),
|
|
||||||
)
|
|
||||||
|
|
||||||
const tipoInput = computed<"date" | "datetime-local">(() =>
|
const tipoInput = computed<"date" | "datetime-local">(() =>
|
||||||
modoEfetivo.value === "data" ? "date" : "datetime-local",
|
modoEfetivo.value === "data" ? "date" : "datetime-local",
|
||||||
|
|
@ -136,23 +132,21 @@ export default defineComponent({
|
||||||
|
|
||||||
function isoParaInputDatetime(valorIso: string): string {
|
function isoParaInputDatetime(valorIso: string): string {
|
||||||
if (modoEfetivo.value === "data") {
|
if (modoEfetivo.value === "data") {
|
||||||
return dayjsbr(valorIso).format("YYYY-MM-DD")
|
return dayjs(valorIso).format("YYYY-MM-DD")
|
||||||
}
|
}
|
||||||
return dayjsbr(valorIso).format("YYYY-MM-DDTHH:mm")
|
return dayjs(valorIso).format("YYYY-MM-DDTHH:mm")
|
||||||
}
|
}
|
||||||
|
|
||||||
function inputDatetimeParaIsoLocal(valorInput: string): string {
|
function inputDatetimeParaIsoLocal(valorInput: string): string {
|
||||||
if (modoEfetivo.value === "data") {
|
if (modoEfetivo.value === "data") {
|
||||||
return dayjsbr(`${valorInput}T00:00`).format()
|
return dayjs(`${valorInput}T00:00`).format()
|
||||||
}
|
}
|
||||||
return dayjsbr(valorInput).format()
|
return dayjs(valorInput).format()
|
||||||
}
|
}
|
||||||
|
|
||||||
const effectiveModelValue = computed<string | null>(() => {
|
const effectiveModelValue = computed<string | null>(() => {
|
||||||
// Prioridade: value (novo) se vier definido; senão usa modelValue (antigo)
|
// Prioridade: value (novo) se vier definido; senão usa modelValue (antigo)
|
||||||
return props.value !== undefined
|
return props.value !== undefined ? (props.value ?? null) : props.modelValue
|
||||||
? (props.value ?? null)
|
|
||||||
: props.modelValue
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const valor = computed<string>({
|
const valor = computed<string>({
|
||||||
|
|
|
||||||
14
src/componentes/EliEntrada/EliEntradaNumero.css
Normal file
14
src/componentes/EliEntrada/EliEntradaNumero.css
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
.eli-entrada__prefixo,
|
||||||
|
.eli-entrada__sufixo {
|
||||||
|
opacity: 0.75;
|
||||||
|
font-size: 0.9em;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-entrada__prefixo {
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-entrada__sufixo {
|
||||||
|
margin-left: 6px;
|
||||||
|
}
|
||||||
112
src/componentes/EliEntrada/EliEntradaNumero.helpers.ts
Normal file
112
src/componentes/EliEntrada/EliEntradaNumero.helpers.ts
Normal file
|
|
@ -0,0 +1,112 @@
|
||||||
|
/**
|
||||||
|
* Helpers de formatação e parsing numérico usados pelo EliEntradaNumero.
|
||||||
|
* Extraídos do componente Vue para manter o arquivo dentro de 250 linhas.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function casasDecimaisFromPrecisao(precisao: number): number {
|
||||||
|
if (!Number.isFinite(precisao) || precisao <= 0) return 0
|
||||||
|
if (precisao >= 1) return 0
|
||||||
|
const texto = precisao.toString()
|
||||||
|
if (texto.includes("e-")) {
|
||||||
|
const [, exp] = texto.split("e-")
|
||||||
|
const n = Number(exp)
|
||||||
|
return Number.isFinite(n) ? n : 0
|
||||||
|
}
|
||||||
|
const idx = texto.indexOf(".")
|
||||||
|
if (idx === -1) return 0
|
||||||
|
const dec = texto.slice(idx + 1).replace(/0+$/, "")
|
||||||
|
return dec.length
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Analisa a entrada de texto e converte em um valor numérico válido (float).
|
||||||
|
* Troca a vírgula brasileira por ponto antes de passar para o parser nativo.
|
||||||
|
*
|
||||||
|
* @param texto - O texto bruto digitado ou vindo do estado
|
||||||
|
* @returns O número processado ou `null` caso inválido ou vazio
|
||||||
|
*/
|
||||||
|
export function parseNumero(texto: string): number | null {
|
||||||
|
const normalizado = (texto ?? "").trim().replace(/,/g, ".")
|
||||||
|
if (!normalizado) return null
|
||||||
|
const n = Number(normalizado)
|
||||||
|
if (Number.isNaN(n)) return null
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formata um valor numérico para a exibição na interface (usando vírgula como separador).
|
||||||
|
*
|
||||||
|
* @param value - O valor numérico de fato emitido
|
||||||
|
* @param casas - Opcionalmente, o número exato de casas decimais para fixar
|
||||||
|
* @returns String formatada amigável para exibição em pt-BR
|
||||||
|
*/
|
||||||
|
export function formatarNumero(value: number | null | undefined, casas: number | null) {
|
||||||
|
if (value === null || value === undefined) return ""
|
||||||
|
if (casas === null) return String(value).replace(/\./g, ",")
|
||||||
|
const fixed = Number(value).toFixed(Math.max(0, casas))
|
||||||
|
return fixed.replace(/\./g, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retira todo tipo de sujeira de uma string, devolvendo restritamente números (para inteiros).
|
||||||
|
*
|
||||||
|
* @param texto - String de entrada
|
||||||
|
* @returns Apenas dígitos de 0 a 9
|
||||||
|
*/
|
||||||
|
export function somenteNumeros(texto: string) {
|
||||||
|
return (texto ?? "").replace(/\D+/g, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Higieniza o texto preservando apenas os dígitos e exatamente UM separador decimal.
|
||||||
|
*
|
||||||
|
* @param texto - Texto misto de entrada
|
||||||
|
* @returns String contendo número e (opcionalmente) uma vírgula ou ponto residual
|
||||||
|
*/
|
||||||
|
export function somenteNumerosESeparadorDecimal(texto: string) {
|
||||||
|
const limpo = (texto ?? "").replace(/[^0-9.,]+/g, "")
|
||||||
|
const matchSep = limpo.match(/[.,]/)
|
||||||
|
if (!matchSep) return limpo
|
||||||
|
|
||||||
|
const sep = matchSep[0]
|
||||||
|
const idx = limpo.indexOf(sep)
|
||||||
|
const antesRaw = limpo.slice(0, idx).replace(/[.,]/g, "")
|
||||||
|
const depois = limpo.slice(idx + 1).replace(/[.,]/g, "")
|
||||||
|
const antes = antesRaw.length ? antesRaw : "0"
|
||||||
|
return `${antes}${sep}${depois}`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trunca a parte decimal de um texto numérico ao número máximo de casas permitidas.
|
||||||
|
* Aplicado durante a digitação para evitar que o usuário ultrapasse a precisão configurada.
|
||||||
|
*
|
||||||
|
* @param texto - Texto numérico já higienizado (com separador único)
|
||||||
|
* @param casas - Número máximo de casas decimais; `null` = sem limite
|
||||||
|
* @returns Texto com a parte decimal truncada no limite especificado
|
||||||
|
*/
|
||||||
|
export function limitarCasasDecimais(texto: string, casas: number | null) {
|
||||||
|
if (casas === null) return texto
|
||||||
|
if (casas <= 0) return texto.replace(/[.,]/g, "")
|
||||||
|
|
||||||
|
const matchSep = texto.match(/[.,]/)
|
||||||
|
if (!matchSep) return texto
|
||||||
|
const sep = matchSep[0]
|
||||||
|
const idx = texto.indexOf(sep)
|
||||||
|
|
||||||
|
const inteiro = texto.slice(0, idx)
|
||||||
|
const frac = texto.slice(idx + 1)
|
||||||
|
return `${inteiro}${sep}${frac.slice(0, casas)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve o edge-case de digitação transitória parcial terminada em separador (ex: "1,").
|
||||||
|
*
|
||||||
|
* @param texto - O texto já higienizado, tipicamente terminando em vírgula ou ponto
|
||||||
|
* @returns A conversão parcial estrita ou `null` caso falhe
|
||||||
|
*/
|
||||||
|
export function valorParcialDecimal(texto: string): number | null {
|
||||||
|
const m = texto.match(/^(\d+)[.,]$/)
|
||||||
|
if (!m) return null
|
||||||
|
const n = Number(m[1])
|
||||||
|
return Number.isNaN(n) ? null : n
|
||||||
|
}
|
||||||
|
|
@ -27,126 +27,19 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent, type PropType, ref, watch } from "vue"
|
import { computed, defineComponent, type PropType, ref, watch } from "vue"
|
||||||
import type { PadroesEntradas } from "./tiposEntradas"
|
import type { PadroesEntradas } from "./tiposEntradas"
|
||||||
|
import "./EliEntradaNumero.css"
|
||||||
|
import {
|
||||||
|
casasDecimaisFromPrecisao,
|
||||||
|
formatarNumero,
|
||||||
|
limitarCasasDecimais,
|
||||||
|
parseNumero,
|
||||||
|
somenteNumeros,
|
||||||
|
somenteNumerosESeparadorDecimal,
|
||||||
|
valorParcialDecimal,
|
||||||
|
} from "./EliEntradaNumero.helpers"
|
||||||
|
|
||||||
type EntradaNumero = PadroesEntradas["numero"]
|
type EntradaNumero = PadroesEntradas["numero"]
|
||||||
|
|
||||||
function casasDecimaisFromPrecisao(precisao: number): number {
|
|
||||||
if (!Number.isFinite(precisao) || precisao <= 0) return 0
|
|
||||||
if (precisao >= 1) return 0
|
|
||||||
|
|
||||||
// Preferência por contar casas decimais na representação (ex.: 0.01 -> 2)
|
|
||||||
const texto = precisao.toString()
|
|
||||||
if (texto.includes("e-")) {
|
|
||||||
const [, exp] = texto.split("e-")
|
|
||||||
const n = Number(exp)
|
|
||||||
return Number.isFinite(n) ? n : 0
|
|
||||||
}
|
|
||||||
const idx = texto.indexOf(".")
|
|
||||||
if (idx === -1) return 0
|
|
||||||
const dec = texto.slice(idx + 1).replace(/0+$/, "")
|
|
||||||
return dec.length
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Analisa a entrada de texto e converte em um valor numérico válido (float).
|
|
||||||
* Troca a vírgula brasileira por ponto antes de passar para o parser nativo.
|
|
||||||
*
|
|
||||||
* @param texto - O texto bruto digitado ou vindo do estado
|
|
||||||
* @returns O número processado ou `null` caso inválido ou vazio
|
|
||||||
*/
|
|
||||||
function parseNumero(texto: string): number | null {
|
|
||||||
const normalizado = (texto ?? "").trim().replace(/,/g, ".")
|
|
||||||
if (!normalizado) return null
|
|
||||||
const n = Number(normalizado)
|
|
||||||
if (Number.isNaN(n)) return null
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Formata um valor numérico para a exibição na interface (usando vírgula como separador).
|
|
||||||
* Em caso de precisão nula (modo livre), preserva a semântica de exibição direta sem forçar o preenchimento de zeros (toFixed).
|
|
||||||
*
|
|
||||||
* @param value - O valor numérico de fato emitido
|
|
||||||
* @param casas - Opcionalmente, o número exato de casas decimais para fixar
|
|
||||||
* @returns String formatada amigável para exibição em pt-BR
|
|
||||||
*/
|
|
||||||
function formatarNumero(
|
|
||||||
value: number | null | undefined,
|
|
||||||
casas: number | null,
|
|
||||||
) {
|
|
||||||
if (value === null || value === undefined) return ""
|
|
||||||
if (casas === null) return String(value).replace(/\./g, ",")
|
|
||||||
|
|
||||||
// Garantimos que o texto visual corresponda ao value (fixed).
|
|
||||||
const fixed = Number(value).toFixed(Math.max(0, casas))
|
|
||||||
// Exibe com vírgula (pt-BR)
|
|
||||||
return fixed.replace(/\./g, ",")
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retira todo tipo de sujeira de uma string, devolvendo restritamente números (para inteiros).
|
|
||||||
*
|
|
||||||
* @param texto - String de entrada
|
|
||||||
* @returns Apenas dígitos de 0 a 9
|
|
||||||
*/
|
|
||||||
function somenteNumeros(texto: string) {
|
|
||||||
return (texto ?? "").replace(/\D+/g, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Higieniza o texto preservando apenas os dígitos e exatamente UM separador decimal.
|
|
||||||
* Colapsa múltiplos separadores e normaliza se o separador vier no início (",4" => "0,4").
|
|
||||||
*
|
|
||||||
* @param texto - Texto misto de entrada
|
|
||||||
* @returns String contendo número e (opcionalmente) uma vírgula ou ponto residual
|
|
||||||
*/
|
|
||||||
function somenteNumerosESeparadorDecimal(texto: string) {
|
|
||||||
// Mantém apenas números e UM separador decimal ("," ou "."), sem sinais.
|
|
||||||
// Implementação baseada em regex + colapso de separadores.
|
|
||||||
const limpo = (texto ?? "").replace(/[^0-9.,]+/g, "")
|
|
||||||
const matchSep = limpo.match(/[.,]/)
|
|
||||||
if (!matchSep) return limpo
|
|
||||||
|
|
||||||
const sep = matchSep[0]
|
|
||||||
const idx = limpo.indexOf(sep)
|
|
||||||
const antesRaw = limpo.slice(0, idx).replace(/[.,]/g, "")
|
|
||||||
const depois = limpo.slice(idx + 1).replace(/[.,]/g, "")
|
|
||||||
|
|
||||||
// Se o usuário começa pelo separador (",4"), normalizamos para "0,4".
|
|
||||||
const antes = antesRaw.length ? antesRaw : "0"
|
|
||||||
return `${antes}${sep}${depois}`
|
|
||||||
}
|
|
||||||
|
|
||||||
function limitarCasasDecimais(texto: string, casas: number | null) {
|
|
||||||
if (casas === null) return texto
|
|
||||||
if (casas <= 0) return texto.replace(/[.,]/g, "")
|
|
||||||
|
|
||||||
const matchSep = texto.match(/[.,]/)
|
|
||||||
if (!matchSep) return texto
|
|
||||||
const sep = matchSep[0]
|
|
||||||
const idx = texto.indexOf(sep)
|
|
||||||
|
|
||||||
const inteiro = texto.slice(0, idx)
|
|
||||||
const frac = texto.slice(idx + 1)
|
|
||||||
return `${inteiro}${sep}${frac.slice(0, casas)}`
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolve o edge-case de digitação transitória parcial terminada em separador (ex: "1,").
|
|
||||||
* Identifica se a string tem este formato pendente e retorna temporariamente sua base inteira
|
|
||||||
* sem permitir que o valor se torne nulo.
|
|
||||||
*
|
|
||||||
* @param texto - O texto já higienizado, tipicamente terminando em vírgula ou ponto
|
|
||||||
* @returns A conversão parcial estrita ou `null` caso falhe
|
|
||||||
*/
|
|
||||||
function valorParcialDecimal(texto: string): number | null {
|
|
||||||
// Regra (B): se o usuário digitou "1," ou "1." emite 1.
|
|
||||||
const m = texto.match(/^(\d+)[.,]$/)
|
|
||||||
if (!m) return null
|
|
||||||
const n = Number(m[1])
|
|
||||||
return Number.isNaN(n) ? null : n
|
|
||||||
}
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "EliEntradaNumero",
|
name: "EliEntradaNumero",
|
||||||
inheritAttrs: false,
|
inheritAttrs: false,
|
||||||
|
|
@ -229,12 +122,8 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
|
|
||||||
// Modo livre (sem precisao) ou inteiro: aceita números e 1 separador (no caso livre)
|
// Modo livre (sem precisao) ou inteiro: aceita números e 1 separador (no caso livre)
|
||||||
const base = isInteiro.value
|
const base = isInteiro.value ? somenteNumeros(texto) : somenteNumerosESeparadorDecimal(texto)
|
||||||
? somenteNumeros(texto)
|
const textoFiltrado = isInteiro.value ? base : limitarCasasDecimais(base, casasDecimais.value)
|
||||||
: somenteNumerosESeparadorDecimal(texto)
|
|
||||||
const textoFiltrado = isInteiro.value
|
|
||||||
? base
|
|
||||||
: limitarCasasDecimais(base, casasDecimais.value)
|
|
||||||
|
|
||||||
// Emissão:
|
// Emissão:
|
||||||
// - vazio => null
|
// - vazio => null
|
||||||
|
|
@ -242,9 +131,7 @@ export default defineComponent({
|
||||||
// - caso geral => parse normal
|
// - caso geral => parse normal
|
||||||
let out: EntradaNumero["value"] = null
|
let out: EntradaNumero["value"] = null
|
||||||
if (textoFiltrado) {
|
if (textoFiltrado) {
|
||||||
const parcial = isInteiro.value
|
const parcial = isInteiro.value ? null : valorParcialDecimal(textoFiltrado)
|
||||||
? null
|
|
||||||
: valorParcialDecimal(textoFiltrado)
|
|
||||||
const n = parcial ?? parseNumero(textoFiltrado)
|
const n = parcial ?? parseNumero(textoFiltrado)
|
||||||
out = (n === null ? null : n) as EntradaNumero["value"]
|
out = (n === null ? null : n) as EntradaNumero["value"]
|
||||||
}
|
}
|
||||||
|
|
@ -269,19 +156,3 @@ export default defineComponent({
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
|
||||||
.eli-entrada__prefixo,
|
|
||||||
.eli-entrada__sufixo {
|
|
||||||
opacity: 0.75;
|
|
||||||
font-size: 0.9em;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eli-entrada__prefixo {
|
|
||||||
margin-right: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eli-entrada__sufixo {
|
|
||||||
margin-left: 6px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
||||||
|
|
@ -23,14 +23,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {
|
import { computed, defineComponent, onMounted, type PropType, ref, watch } from "vue"
|
||||||
computed,
|
|
||||||
defineComponent,
|
|
||||||
onMounted,
|
|
||||||
type PropType,
|
|
||||||
ref,
|
|
||||||
watch,
|
|
||||||
} from "vue"
|
|
||||||
import { VSelect } from "vuetify/components"
|
import { VSelect } from "vuetify/components"
|
||||||
import type { PadroesEntradas } from "./tiposEntradas"
|
import type { PadroesEntradas } from "./tiposEntradas"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -65,8 +65,7 @@ export default defineComponent({
|
||||||
|
|
||||||
const inputMode = computed<string | undefined>(() => {
|
const inputMode = computed<string | undefined>(() => {
|
||||||
if (formato.value === "telefone") return "tel"
|
if (formato.value === "telefone") return "tel"
|
||||||
if (formato.value === "cpfCnpj" || formato.value === "cep")
|
if (formato.value === "cpfCnpj" || formato.value === "cep") return "numeric"
|
||||||
return "numeric"
|
|
||||||
return undefined
|
return undefined
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,16 @@ import EliEntradaTexto from "./EliEntradaTexto.vue"
|
||||||
|
|
||||||
import type { TipoEntrada } from "./tiposEntradas"
|
import type { TipoEntrada } from "./tiposEntradas"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mapa (dispatch table) que associa cada `TipoEntrada` ao seu componente Vue correspondente.
|
||||||
|
* Usado pelo sistema de filtro avançado para renderizar dinamicamente o campo correto
|
||||||
|
* a partir da configuração declarada em `tipoEliTabelaConsulta.filtroAvancado[].entrada`.
|
||||||
|
*
|
||||||
|
* Adicionar um novo tipo de entrada requer:
|
||||||
|
* 1. Criar o componente Vue
|
||||||
|
* 2. Adicionar a chave em `TipoEntrada` (tiposEntradas.ts)
|
||||||
|
* 3. Registrar aqui no mapa
|
||||||
|
*/
|
||||||
export const registryTabelaCelulas = {
|
export const registryTabelaCelulas = {
|
||||||
texto: EliEntradaTexto,
|
texto: EliEntradaTexto,
|
||||||
numero: EliEntradaNumero,
|
numero: EliEntradaNumero,
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,14 @@ function somenteNumeros(v: string): string {
|
||||||
return v.replace(/\D+/g, "")
|
return v.replace(/\D+/g, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formata um CPF ou CNPJ aplicando a máscara brasileira padrão durante a digitação.
|
||||||
|
* Detecta automaticamente o tipo pelo comprimento (≤11 dígitos = CPF, >11 = CNPJ).
|
||||||
|
* Não valida o dígito verificador — apenas aplica a formatação visual.
|
||||||
|
*
|
||||||
|
* @param v - Valor bruto (pode conter letras, pontos, traços etc.)
|
||||||
|
* @returns String formatada no padrão `000.000.000-00` (CPF) ou `00.000.000/0000-00` (CNPJ)
|
||||||
|
*/
|
||||||
export function formatarCpfCnpj(v: string): string {
|
export function formatarCpfCnpj(v: string): string {
|
||||||
const d = somenteNumeros(v)
|
const d = somenteNumeros(v)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,20 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.eli-tabela__table-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela__td--estados {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela__estados-inline {
|
||||||
|
border: none;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.eli-tabela__table {
|
.eli-tabela__table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-collapse: separate;
|
border-collapse: separate;
|
||||||
|
|
@ -29,21 +43,30 @@
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.eli-tabela__tbody .eli-tabela__tr .eli-tabela__td {
|
||||||
|
background: var(--cor-eli-superficie, #ffffff);
|
||||||
|
}
|
||||||
|
|
||||||
.eli-tabela__tbody .eli-tabela__tr--zebra .eli-tabela__td {
|
.eli-tabela__tbody .eli-tabela__tr--zebra .eli-tabela__td {
|
||||||
background: rgba(15, 23, 42, 0.02);
|
background: var(--cor-eli-primaria-zebra, #F6F9E8);
|
||||||
}
|
}
|
||||||
|
|
||||||
.eli-tabela__th,
|
.eli-tabela__th,
|
||||||
.eli-tabela__td {
|
.eli-tabela__td {
|
||||||
padding: 10px 12px;
|
padding: 10px 12px;
|
||||||
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
|
border-bottom: 1px solid var(--cor-eli-borda);
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
|
|
||||||
.eli-tabela__th {
|
.eli-tabela__th {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
background: rgba(0, 0, 0, 0.03);
|
font-size: 0.82rem;
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--cor-eli-neutro-escuro, #3E4436);
|
||||||
|
background: var(--cor-eli-primaria-suave, #BFC99C);
|
||||||
|
border-bottom: 2px solid var(--cor-eli-primaria-bg, #EEF3D0);
|
||||||
}
|
}
|
||||||
|
|
||||||
.eli-tabela__th--ordenavel {
|
.eli-tabela__th--ordenavel {
|
||||||
|
|
@ -71,16 +94,16 @@
|
||||||
|
|
||||||
.eli-tabela__th-botao:hover,
|
.eli-tabela__th-botao:hover,
|
||||||
.eli-tabela__th-botao:focus-visible {
|
.eli-tabela__th-botao:focus-visible {
|
||||||
color: rgba(15, 23, 42, 0.85);
|
color: var(--cor-eli-primaria-texto, #2E3F00);
|
||||||
}
|
}
|
||||||
|
|
||||||
.eli-tabela__th-botao:focus-visible {
|
.eli-tabela__th-botao:focus-visible {
|
||||||
outline: 2px solid rgba(37, 99, 235, 0.45);
|
outline: 2px solid var(--cor-eli-primaria, #688C00);
|
||||||
outline-offset: 2px;
|
outline-offset: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.eli-tabela__th-botao--ativo {
|
.eli-tabela__th-botao--ativo {
|
||||||
color: rgba(15, 23, 42, 0.85);
|
color: var(--cor-eli-primaria-texto, #2E3F00);
|
||||||
}
|
}
|
||||||
|
|
||||||
.eli-tabela__th-texto {
|
.eli-tabela__th-texto {
|
||||||
|
|
@ -110,14 +133,19 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.eli-tabela--erro {
|
.eli-tabela--erro {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 10px;
|
||||||
border: 1px solid rgba(220, 53, 69, 0.35);
|
border: 1px solid rgba(220, 53, 69, 0.35);
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.eli-tabela--carregando {
|
.eli-tabela--carregando {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
border: 1px dashed rgba(0, 0, 0, 0.25);
|
border: 1px dashed rgba(0, 0, 0, 0.25);
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
|
|
@ -134,12 +162,33 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.eli-tabela--vazio {
|
.eli-tabela--vazio {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
border: 1px dashed rgba(0, 0, 0, 0.25);
|
border: 1px dashed rgba(0, 0, 0, 0.25);
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.eli-tabela__estado-icone {
|
||||||
|
flex-shrink: 0;
|
||||||
|
color: rgba(15, 23, 42, 0.45);
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela__estado-icone--erro {
|
||||||
|
color: var(--cor-eli-erro);
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela__estado-icone--spin {
|
||||||
|
animation: eli-tabela-spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes eli-tabela-spin {
|
||||||
|
from { transform: rotate(0deg); }
|
||||||
|
to { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
.eli-tabela__th--acoes {
|
.eli-tabela__th--acoes {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
@ -222,8 +271,8 @@
|
||||||
padding: 0 14px;
|
padding: 0 14px;
|
||||||
border-radius: var(--eli-tabela-cabecalho-controle-radius);
|
border-radius: var(--eli-tabela-cabecalho-controle-radius);
|
||||||
border: none;
|
border: none;
|
||||||
background: rgba(37, 99, 235, 0.12);
|
background: var(--cor-eli-primaria-bg);
|
||||||
color: rgba(37, 99, 235, 0.95);
|
color: var(--cor-eli-primaria-escura);
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
|
|
@ -233,11 +282,11 @@
|
||||||
|
|
||||||
.eli-tabela__acoes-cabecalho-botao:hover,
|
.eli-tabela__acoes-cabecalho-botao:hover,
|
||||||
.eli-tabela__acoes-cabecalho-botao:focus-visible {
|
.eli-tabela__acoes-cabecalho-botao:focus-visible {
|
||||||
background: rgba(37, 99, 235, 0.2);
|
background: var(--cor-eli-primaria-clara);
|
||||||
}
|
}
|
||||||
|
|
||||||
.eli-tabela__acoes-cabecalho-botao:focus-visible {
|
.eli-tabela__acoes-cabecalho-botao:focus-visible {
|
||||||
outline: 2px solid rgba(37, 99, 235, 0.35);
|
outline: 2px solid var(--cor-eli-primaria-clara);
|
||||||
outline-offset: 2px;
|
outline-offset: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -274,7 +323,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.eli-tabela__acoes-toggle:focus-visible {
|
.eli-tabela__acoes-toggle:focus-visible {
|
||||||
outline: 2px solid rgba(37, 99, 235, 0.45);
|
outline: 2px solid var(--cor-eli-primaria-clara);
|
||||||
outline-offset: 2px;
|
outline-offset: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -289,7 +338,7 @@
|
||||||
padding: 6px 0;
|
padding: 6px 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
background: #ffffff;
|
background: var(--cor-eli-superficie);
|
||||||
border: 1px solid rgba(15, 23, 42, 0.08);
|
border: 1px solid rgba(15, 23, 42, 0.08);
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
box-shadow: 0 12px 30px rgba(15, 23, 42, 0.18);
|
box-shadow: 0 12px 30px rgba(15, 23, 42, 0.18);
|
||||||
|
|
@ -365,7 +414,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.eli-tabela__expander-botao:focus-visible {
|
.eli-tabela__expander-botao:focus-visible {
|
||||||
outline: 2px solid rgba(37, 99, 235, 0.45);
|
outline: 2px solid var(--cor-eli-primaria-clara);
|
||||||
outline-offset: 2px;
|
outline-offset: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -445,8 +494,8 @@
|
||||||
|
|
||||||
.eli-tabela__checkbox:checked,
|
.eli-tabela__checkbox:checked,
|
||||||
.eli-tabela__checkbox:indeterminate {
|
.eli-tabela__checkbox:indeterminate {
|
||||||
background-color: #1976d2;
|
background-color: var(--cor-eli-primaria);
|
||||||
border-color: #1976d2;
|
border-color: var(--cor-eli-primaria);
|
||||||
}
|
}
|
||||||
|
|
||||||
.eli-tabela__checkbox:checked::after {
|
.eli-tabela__checkbox:checked::after {
|
||||||
|
|
@ -473,6 +522,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.eli-tabela__checkbox:focus-visible {
|
.eli-tabela__checkbox:focus-visible {
|
||||||
box-shadow: 0 0 0 2px rgba(25, 118, 210, 0.3);
|
box-shadow: 0 0 0 2px var(--cor-eli-primaria-clara);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,57 +31,60 @@
|
||||||
|
|
||||||
<EliTabelaModalFiltroAvancado
|
<EliTabelaModalFiltroAvancado
|
||||||
:aberto="modalFiltroAberto"
|
:aberto="modalFiltroAberto"
|
||||||
:filtrosBase="tabela.filtroAvancado ?? []"
|
:filtrosBase="(tabela.filtroAvancado ?? []) as FiltroBaseModal[]"
|
||||||
:modelo="filtrosUi"
|
:modelo="filtrosUi"
|
||||||
|
:modeloFormula="filtrosFormula"
|
||||||
@fechar="fecharModalFiltro"
|
@fechar="fecharModalFiltro"
|
||||||
@limpar="limparFiltrosAvancados"
|
@limpar="limparFiltrosAvancados"
|
||||||
@salvar="salvarFiltrosAvancados"
|
@salvar="salvarFiltrosAvancados"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<table class="eli-tabela__table">
|
<div class="eli-tabela__table-wrapper">
|
||||||
<EliTabelaHead
|
<table class="eli-tabela__table">
|
||||||
:colunas="colunasEfetivas"
|
<EliTabelaHead
|
||||||
:temAcoes="temAcoes"
|
:colunas="colunasEfetivas"
|
||||||
:temColunasInvisiveis="temColunasInvisiveis"
|
:temAcoes="temAcoes"
|
||||||
:colunaOrdenacao="colunaOrdenacao"
|
:temColunasInvisiveis="temColunasInvisiveis"
|
||||||
:direcaoOrdenacao="direcaoOrdenacao"
|
:colunaOrdenacao="colunaOrdenacao"
|
||||||
:mostrarSelecao="mostrarSelecao"
|
:direcaoOrdenacao="direcaoOrdenacao"
|
||||||
:todosSelecionados="todosSelecionados"
|
:mostrarSelecao="mostrarSelecao"
|
||||||
:algunsSelecionados="algunsSelecionados"
|
:todosSelecionados="todosSelecionados"
|
||||||
@alternar-ordenacao="alternarOrdenacao"
|
:algunsSelecionados="algunsSelecionados"
|
||||||
@alternar-selecao-todos="alternarSelecaoTodos"
|
@alternar-ordenacao="alternarOrdenacao"
|
||||||
/>
|
@alternar-selecao-todos="alternarSelecaoTodos"
|
||||||
|
/>
|
||||||
|
|
||||||
<tbody v-if="carregando || Boolean(erro) || !linhas.length">
|
<tbody v-if="carregando || Boolean(erro) || !linhas.length">
|
||||||
<tr>
|
<tr>
|
||||||
<td :colspan="totalColunas" style="padding: 0;">
|
<td :colspan="totalColunas" class="eli-tabela__td--estados">
|
||||||
<EliTabelaEstados
|
<EliTabelaEstados
|
||||||
:carregando="carregando"
|
:carregando="carregando"
|
||||||
:erro="erro"
|
:erro="erro"
|
||||||
:mensagemVazio="tabela.mensagemVazio"
|
:mensagemVazio="tabela.mensagemVazio"
|
||||||
style="border: none; border-radius: 0;"
|
class="eli-tabela__estados-inline"
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
||||||
<EliTabelaBody
|
<EliTabelaBody
|
||||||
v-else
|
v-else
|
||||||
:colunas="colunasEfetivas"
|
:colunas="colunasEfetivas"
|
||||||
:colunasInvisiveis="colunasInvisiveisEfetivas"
|
:colunasInvisiveis="colunasInvisiveisEfetivas"
|
||||||
:temColunasInvisiveis="temColunasInvisiveis"
|
:temColunasInvisiveis="temColunasInvisiveis"
|
||||||
:linhasExpandidas="linhasExpandidas"
|
:linhasExpandidas="linhasExpandidas"
|
||||||
:linhas="linhasPaginadas"
|
:linhas="linhasPaginadas"
|
||||||
:temAcoes="temAcoes"
|
:temAcoes="temAcoes"
|
||||||
:menuAberto="menuAberto"
|
:menuAberto="menuAberto"
|
||||||
:possuiAcoes="possuiAcoes"
|
:possuiAcoes="possuiAcoes"
|
||||||
:toggleMenu="toggleMenu"
|
:toggleMenu="toggleMenu"
|
||||||
:alternarLinhaExpandida="alternarLinhaExpandida"
|
:alternarLinhaExpandida="alternarLinhaExpandida"
|
||||||
:mostrarSelecao="mostrarSelecao"
|
:mostrarSelecao="mostrarSelecao"
|
||||||
:isSelecionado="isSelecionado"
|
:isSelecionado="isSelecionado"
|
||||||
:alternarSelecao="alternarSelecao"
|
:alternarSelecao="alternarSelecao"
|
||||||
/>
|
/>
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
<EliTabelaMenuAcoes
|
<EliTabelaMenuAcoes
|
||||||
ref="menuPopup"
|
ref="menuPopup"
|
||||||
|
|
@ -108,28 +111,16 @@
|
||||||
/**
|
/**
|
||||||
* EliTabela
|
* EliTabela
|
||||||
* Componente de tabela consultável com busca, paginação, ordenação e ações por linha.
|
* Componente de tabela consultável com busca, paginação, ordenação e ações por linha.
|
||||||
|
* O setup() é orquestrado por `useEliTabela`, que compõe os seguintes sub-composables:
|
||||||
|
* - useEliTabelaConsulta — carregamento, paginação, ordenação, busca
|
||||||
|
* - useEliTabelaFiltrosAvancados — filtros avançados, modal e persistência
|
||||||
|
* - useEliTabelaSelecao — seleção múltipla de linhas
|
||||||
|
* - useEliTabelaColunas — visibilidade, ordem e modal de colunas
|
||||||
|
* - useEliTabelaMenu — menu de ações por linha e posicionamento de popup
|
||||||
*/
|
*/
|
||||||
|
import { defineComponent, type PropType } from "vue"
|
||||||
/** Enum de códigos de resposta utilizado na consulta */
|
import "./EliTabela.css"
|
||||||
import { codigosResposta } from "p-respostas"
|
|
||||||
/** Dependências do Vue (Composition API) */
|
|
||||||
import {
|
|
||||||
computed,
|
|
||||||
defineComponent,
|
|
||||||
onBeforeUnmount,
|
|
||||||
onMounted,
|
|
||||||
type PropType,
|
|
||||||
ref,
|
|
||||||
watch,
|
|
||||||
} from "vue"
|
|
||||||
// operadores usados no tipo de configuração; o operador aplicado vem travado no filtroAvancado.
|
|
||||||
import {
|
|
||||||
carregarConfigColunas,
|
|
||||||
type EliTabelaColunasConfig,
|
|
||||||
salvarConfigColunas,
|
|
||||||
} from "./colunasStorage"
|
|
||||||
import EliTabelaBody from "./EliTabelaBody.vue"
|
import EliTabelaBody from "./EliTabelaBody.vue"
|
||||||
/** Componentes auxiliares */
|
|
||||||
import EliTabelaCabecalho from "./EliTabelaCabecalho.vue"
|
import EliTabelaCabecalho from "./EliTabelaCabecalho.vue"
|
||||||
import EliTabelaDebug from "./EliTabelaDebug.vue"
|
import EliTabelaDebug from "./EliTabelaDebug.vue"
|
||||||
import EliTabelaEstados from "./EliTabelaEstados.vue"
|
import EliTabelaEstados from "./EliTabelaEstados.vue"
|
||||||
|
|
@ -138,18 +129,12 @@ import EliTabelaMenuAcoes from "./EliTabelaMenuAcoes.vue"
|
||||||
import EliTabelaModalColunas from "./EliTabelaModalColunas.vue"
|
import EliTabelaModalColunas from "./EliTabelaModalColunas.vue"
|
||||||
import EliTabelaModalFiltroAvancado from "./EliTabelaModalFiltroAvancado.vue"
|
import EliTabelaModalFiltroAvancado from "./EliTabelaModalFiltroAvancado.vue"
|
||||||
import EliTabelaPaginacao from "./EliTabelaPaginacao.vue"
|
import EliTabelaPaginacao from "./EliTabelaPaginacao.vue"
|
||||||
import {
|
import type { tipoEliTabelaConsulta } from "./types-eli-tabela"
|
||||||
carregarFiltroAvancado,
|
import { useEliTabela } from "./useEliTabela"
|
||||||
limparFiltroAvancado,
|
import type { FiltroBase } from "./useFiltroAvancado"
|
||||||
salvarFiltroAvancado,
|
|
||||||
} from "./filtroAvancadoStorage"
|
// biome-ignore lint/correctness/noUnusedVariables: tipo usado no template SFC no cast :filtrosBase
|
||||||
/** Tipos da configuração/contrato da tabela */
|
type FiltroBaseModal = FiltroBase<string>
|
||||||
import type {
|
|
||||||
parametrosConsulta,
|
|
||||||
tipoEliColuna,
|
|
||||||
tipoEliTabelaConsulta,
|
|
||||||
tipoFiltro,
|
|
||||||
} from "./types-eli-tabela"
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "EliTabela",
|
name: "EliTabela",
|
||||||
|
|
@ -170,821 +155,22 @@ export default defineComponent({
|
||||||
tabela: {
|
tabela: {
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: dynamic table
|
// biome-ignore lint/suspicious/noExplicitAny: dynamic table
|
||||||
type: Object as PropType<tipoEliTabelaConsulta<any>>,
|
type: Object as PropType<tipoEliTabelaConsulta<any>>,
|
||||||
required: true,
|
required: true as const,
|
||||||
},
|
},
|
||||||
/** Array de itens selecionados (v-model:selecionados) */
|
/** Array de itens selecionados (v-model:selecionados) */
|
||||||
selecionados: {
|
selecionados: {
|
||||||
type: Array as PropType<unknown[]>,
|
type: Array as PropType<unknown[]>,
|
||||||
default: () => [],
|
default: () => [],
|
||||||
},
|
},
|
||||||
|
/** Ocultar painel de debug lateral (EliTabelaDebug) */
|
||||||
|
esconderDebug: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
emits: ["update:selecionados"],
|
emits: ["update:selecionados"],
|
||||||
setup(props, { emit }) {
|
setup(props, { emit }) {
|
||||||
/** Flag para habilitar elementos de debug */
|
return useEliTabela(props, emit)
|
||||||
const isDev = import.meta.env.DEV
|
|
||||||
/** Estados de carregamento/erro e dados retornados */
|
|
||||||
const carregando = ref(false)
|
|
||||||
const erro = ref<string | null>(null)
|
|
||||||
const linhas = ref<unknown[]>([])
|
|
||||||
const quantidade = ref<number>(0)
|
|
||||||
|
|
||||||
/** Controle de visibilidade das ações por linha */
|
|
||||||
const acoesVisiveis = ref<boolean[][]>([])
|
|
||||||
/** Estado do menu de ações (aberto, elemento e posição) */
|
|
||||||
const menuAberto = ref<number | null>(null)
|
|
||||||
// O componente EliTabelaMenuAcoes expõe `menuEl` (ref do elemento <ul>)
|
|
||||||
const menuPopup = ref<{ menuEl: { value: HTMLElement | null } } | null>(
|
|
||||||
null,
|
|
||||||
)
|
|
||||||
const menuPopupPos = ref({ top: 0, left: 0 })
|
|
||||||
|
|
||||||
/** Filtros e ordenação */
|
|
||||||
const valorBusca = ref<string>("")
|
|
||||||
const paginaAtual = ref(1)
|
|
||||||
const colunaOrdenacao = ref<string | null>(null)
|
|
||||||
const direcaoOrdenacao = ref<"asc" | "desc">("asc")
|
|
||||||
|
|
||||||
/** Filtro avançado (config + estado modal) */
|
|
||||||
const modalFiltroAberto = ref(false)
|
|
||||||
type LinhaFiltroUI = {
|
|
||||||
chave: string
|
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: dynamic value
|
|
||||||
valor: any
|
|
||||||
}
|
|
||||||
const filtrosUi = ref<Array<LinhaFiltroUI>>(
|
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: dynamic load
|
|
||||||
carregarFiltroAvancado<any>(props.tabela.nome) as any,
|
|
||||||
)
|
|
||||||
|
|
||||||
function abrirModalFiltro() {
|
|
||||||
modalFiltroAberto.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
function fecharModalFiltro() {
|
|
||||||
modalFiltroAberto.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
function limparFiltrosAvancados() {
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
|
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: dynamic ui
|
|
||||||
function salvarFiltrosAvancados(novo: any[]) {
|
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: dynamic ui
|
|
||||||
filtrosUi.value = (novo ?? []) as any
|
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: dynamic storage
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
|
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: generic
|
|
||||||
const filtrosAvancadosAtivos = computed<tipoFiltro<any>[]>(() => {
|
|
||||||
const base = props.tabela.filtroAvancado ?? []
|
|
||||||
|
|
||||||
return (
|
|
||||||
(filtrosUi.value ?? [])
|
|
||||||
.filter((f) => f && f.chave !== undefined)
|
|
||||||
.map((f) => {
|
|
||||||
const b = base.find((x) => String(x.chave) === String(f.chave))
|
|
||||||
if (!b) return null
|
|
||||||
|
|
||||||
// O filtro já vem pronto da função definida na configuração
|
|
||||||
return b.filtro(f.valor)
|
|
||||||
})
|
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: generic
|
|
||||||
.filter(Boolean) as tipoFiltro<any>[]
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
/** Alias reativo da prop tabela */
|
|
||||||
const tabela = computed(() => props.tabela)
|
|
||||||
|
|
||||||
/** Seleção Múltipla */
|
|
||||||
const mostrarSelecao = computed(() => Boolean(props.tabela.mostrar_selecao))
|
|
||||||
const chaveSelecao = computed(
|
|
||||||
() => props.tabela.chave_selecao as string | undefined,
|
|
||||||
)
|
|
||||||
|
|
||||||
function obterChaveLinha(linha: unknown): unknown {
|
|
||||||
if (chaveSelecao.value && linha && typeof linha === "object") {
|
|
||||||
return (linha as Record<string, unknown>)[chaveSelecao.value]
|
|
||||||
}
|
|
||||||
return linha
|
|
||||||
}
|
|
||||||
|
|
||||||
function isSelecionado(linha: unknown): boolean {
|
|
||||||
const chave = obterChaveLinha(linha)
|
|
||||||
if (chaveSelecao.value) {
|
|
||||||
return props.selecionados.some((s) => obterChaveLinha(s) === chave)
|
|
||||||
}
|
|
||||||
return props.selecionados.includes(linha)
|
|
||||||
}
|
|
||||||
|
|
||||||
function alternarSelecao(linha: unknown) {
|
|
||||||
const atual = [...props.selecionados]
|
|
||||||
const chave = obterChaveLinha(linha)
|
|
||||||
|
|
||||||
let index = -1
|
|
||||||
if (chaveSelecao.value) {
|
|
||||||
index = atual.findIndex((s) => obterChaveLinha(s) === chave)
|
|
||||||
} else {
|
|
||||||
index = atual.indexOf(linha)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (index === -1) {
|
|
||||||
atual.push(linha)
|
|
||||||
} else {
|
|
||||||
atual.splice(index, 1)
|
|
||||||
}
|
|
||||||
emit("update:selecionados", atual)
|
|
||||||
}
|
|
||||||
|
|
||||||
const todosSelecionados = computed(() => {
|
|
||||||
if (linhasPaginadas.value.length === 0) return false
|
|
||||||
return linhasPaginadas.value.every((linha) => isSelecionado(linha))
|
|
||||||
})
|
|
||||||
|
|
||||||
const algunsSelecionados = computed(() => {
|
|
||||||
if (linhasPaginadas.value.length === 0) return false
|
|
||||||
return (
|
|
||||||
linhasPaginadas.value.some((linha) => isSelecionado(linha)) &&
|
|
||||||
!todosSelecionados.value
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
function alternarSelecaoTodos(forcarMarcar?: boolean) {
|
|
||||||
const marcarTodos = forcarMarcar ?? !todosSelecionados.value
|
|
||||||
let atual = [...props.selecionados]
|
|
||||||
|
|
||||||
if (marcarTodos) {
|
|
||||||
// Adiciona os que faltam na página atual
|
|
||||||
for (const linha of linhasPaginadas.value) {
|
|
||||||
if (!isSelecionado(linha)) {
|
|
||||||
atual.push(linha)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Remove os da página atual
|
|
||||||
const chavesPagina = linhasPaginadas.value.map(obterChaveLinha)
|
|
||||||
if (chaveSelecao.value) {
|
|
||||||
atual = atual.filter(
|
|
||||||
(s) => !chavesPagina.includes(obterChaveLinha(s)),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
atual = atual.filter((s) => !chavesPagina.includes(s))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
emit("update:selecionados", atual)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Exibição da busca e ações do cabeçalho */
|
|
||||||
const exibirBusca = computed(() =>
|
|
||||||
Boolean(props.tabela.mostrarCaixaDeBusca),
|
|
||||||
)
|
|
||||||
const acoesSuperiores = computed(() =>
|
|
||||||
(props.tabela.acoesTabela ?? []).filter((a) => a.posicao === "superior"),
|
|
||||||
)
|
|
||||||
const acoesInferiores = computed(() =>
|
|
||||||
(props.tabela.acoesTabela ?? []).filter((a) => a.posicao === "inferior"),
|
|
||||||
)
|
|
||||||
const temAcoesSuperiores = computed(() => acoesSuperiores.value.length > 0)
|
|
||||||
const temAcoesInferiores = computed(() => acoesInferiores.value.length > 0)
|
|
||||||
|
|
||||||
const parametrosConsultaAtuais = computed(() => {
|
|
||||||
const limite = Math.max(1, registrosPorConsulta.value)
|
|
||||||
const offset = (paginaAtual.value - 1) * limite
|
|
||||||
|
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: dynamic params
|
|
||||||
const params: parametrosConsulta<any> = {
|
|
||||||
offSet: offset,
|
|
||||||
limit: limite,
|
|
||||||
}
|
|
||||||
|
|
||||||
const busca = (valorBusca.value ?? "").trim()
|
|
||||||
if (busca) {
|
|
||||||
params.texto_busca = busca
|
|
||||||
} else {
|
|
||||||
const filtros = filtrosAvancadosAtivos.value
|
|
||||||
if (filtros.length) params.filtros = filtros
|
|
||||||
}
|
|
||||||
|
|
||||||
if (colunaOrdenacao.value) {
|
|
||||||
params.coluna_ordem = colunaOrdenacao.value
|
|
||||||
params.direcao_ordem = direcaoOrdenacao.value
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...params,
|
|
||||||
atualizarConsulta: async () => {
|
|
||||||
await carregar()
|
|
||||||
},
|
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: dynamic list update
|
|
||||||
editarLista: async (novaLista: any[]) => {
|
|
||||||
linhas.value = novaLista
|
|
||||||
return novaLista
|
|
||||||
},
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
/** Colunas: visibilidade/ordem com persistência */
|
|
||||||
const modalColunasAberto = ref(false)
|
|
||||||
const configColunas = ref<EliTabelaColunasConfig>(
|
|
||||||
carregarConfigColunas(props.tabela.nome),
|
|
||||||
)
|
|
||||||
|
|
||||||
/** Linhas expandidas (para exibir colunas invisíveis) */
|
|
||||||
const linhasExpandidas = ref<Record<number, boolean>>({})
|
|
||||||
|
|
||||||
const rotulosColunas = computed(() =>
|
|
||||||
props.tabela.colunas.map((c) => c.rotulo),
|
|
||||||
)
|
|
||||||
|
|
||||||
const colunasInvisiveisEfetivas = computed(() => {
|
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: dynamic column
|
|
||||||
const colunas = props.tabela.colunas as Array<tipoEliColuna<any>>
|
|
||||||
|
|
||||||
const configTemDados =
|
|
||||||
(configColunas.value.visiveis?.length ?? 0) > 0 ||
|
|
||||||
(configColunas.value.invisiveis?.length ?? 0) > 0
|
|
||||||
|
|
||||||
const invisiveisBaseRotulos = configTemDados
|
|
||||||
? (configColunas.value.invisiveis ?? [])
|
|
||||||
: colunas.filter((c) => c.visivel === false).map((c) => c.rotulo)
|
|
||||||
|
|
||||||
const invisiveisSet = new Set(invisiveisBaseRotulos)
|
|
||||||
const base = colunas.filter((c) => invisiveisSet.has(c.rotulo))
|
|
||||||
|
|
||||||
// ordenação: usa a lista (salva ou derivada do default) e adiciona novas ao final
|
|
||||||
const ordemSalva = invisiveisBaseRotulos
|
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: dynamic map
|
|
||||||
const mapa = new Map<string, tipoEliColuna<any>>()
|
|
||||||
for (const c of base) {
|
|
||||||
if (!mapa.has(c.rotulo)) mapa.set(c.rotulo, c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: dynamic column
|
|
||||||
const ordenadas: Array<tipoEliColuna<any>> = []
|
|
||||||
for (const r of ordemSalva) {
|
|
||||||
const c = mapa.get(r)
|
|
||||||
if (c) ordenadas.push(c)
|
|
||||||
}
|
|
||||||
for (const c of base) {
|
|
||||||
if (!ordenadas.includes(c)) ordenadas.push(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ordenadas
|
|
||||||
})
|
|
||||||
|
|
||||||
const temColunasInvisiveis = computed(
|
|
||||||
() => colunasInvisiveisEfetivas.value.length > 0,
|
|
||||||
)
|
|
||||||
|
|
||||||
const colunasEfetivas = computed(() => {
|
|
||||||
const colunas = props.tabela.colunas
|
|
||||||
const todosRotulos = rotulosColunas.value
|
|
||||||
|
|
||||||
const configTemDados =
|
|
||||||
(configColunas.value.visiveis?.length ?? 0) > 0 ||
|
|
||||||
(configColunas.value.invisiveis?.length ?? 0) > 0
|
|
||||||
|
|
||||||
const invisiveisBaseRotulos = configTemDados
|
|
||||||
? (configColunas.value.invisiveis ?? [])
|
|
||||||
: // biome-ignore lint/suspicious/noExplicitAny: dynamic column
|
|
||||||
(props.tabela.colunas as Array<tipoEliColuna<any>>)
|
|
||||||
.filter((c) => c.visivel === false)
|
|
||||||
.map((c) => c.rotulo)
|
|
||||||
|
|
||||||
const invisiveisSet = new Set(invisiveisBaseRotulos)
|
|
||||||
|
|
||||||
// base visiveis: remove invisiveis
|
|
||||||
const visiveisBaseRotulos = todosRotulos.filter(
|
|
||||||
(r) => !invisiveisSet.has(r),
|
|
||||||
)
|
|
||||||
const visiveisSet = new Set(visiveisBaseRotulos)
|
|
||||||
|
|
||||||
// aplica ordem salva; novas (sem definicao) entram no fim, respeitando ordem original
|
|
||||||
const ordemSalva = configTemDados
|
|
||||||
? (configColunas.value.visiveis ?? [])
|
|
||||||
: []
|
|
||||||
const ordemFinal: string[] = []
|
|
||||||
|
|
||||||
for (const r of ordemSalva) {
|
|
||||||
if (visiveisSet.has(r)) ordemFinal.push(r)
|
|
||||||
}
|
|
||||||
for (const r of visiveisBaseRotulos) {
|
|
||||||
if (!ordemFinal.includes(r)) ordemFinal.push(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
// mapeia rótulo -> coluna, preservando duplicatas (se existirem) pelo primeiro match.
|
|
||||||
// OBS: pressupoe rotulo unico; se repetir, comportamento fica indefinido.
|
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: dynamic map
|
|
||||||
const mapa = new Map<string, any>()
|
|
||||||
for (const c of colunas) {
|
|
||||||
if (!mapa.has(c.rotulo)) mapa.set(c.rotulo, c)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ordemFinal.map((r) => mapa.get(r)).filter(Boolean)
|
|
||||||
})
|
|
||||||
|
|
||||||
const totalColunas = computed(() => {
|
|
||||||
return (
|
|
||||||
colunasEfetivas.value.length +
|
|
||||||
(temAcoes.value ? 1 : 0) +
|
|
||||||
(temColunasInvisiveis.value ? 1 : 0)
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
function abrirModalColunas() {
|
|
||||||
modalColunasAberto.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
function fecharModalColunas() {
|
|
||||||
modalColunasAberto.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
function salvarModalColunas(cfg: EliTabelaColunasConfig) {
|
|
||||||
configColunas.value = cfg
|
|
||||||
salvarConfigColunas(props.tabela.nome, cfg)
|
|
||||||
modalColunasAberto.value = false
|
|
||||||
|
|
||||||
// ao mudar colunas, fecha detalhes expandidos
|
|
||||||
linhasExpandidas.value = {}
|
|
||||||
}
|
|
||||||
|
|
||||||
function alternarLinhaExpandida(indice: number) {
|
|
||||||
const atual = Boolean(linhasExpandidas.value[indice])
|
|
||||||
linhasExpandidas.value = {
|
|
||||||
...linhasExpandidas.value,
|
|
||||||
[indice]: !atual,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Registros por consulta (normaliza para inteiro positivo) */
|
|
||||||
const registrosPorConsulta = computed(() => {
|
|
||||||
const valor = props.tabela.registros_por_consulta
|
|
||||||
if (typeof valor === "number" && valor > 0) {
|
|
||||||
return Math.floor(valor)
|
|
||||||
}
|
|
||||||
return 10
|
|
||||||
})
|
|
||||||
|
|
||||||
/** 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 = quantidade.value ?? 0
|
|
||||||
if (!total) return 1
|
|
||||||
return Math.max(1, Math.ceil(total / 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)
|
|
||||||
|
|
||||||
const exibirFiltroAvancado = computed(
|
|
||||||
() => (props.tabela.filtroAvancado ?? []).length > 0,
|
|
||||||
)
|
|
||||||
|
|
||||||
/** Sequencial para evitar race conditions entre consultas */
|
|
||||||
let carregamentoSequencial = 0
|
|
||||||
|
|
||||||
/** Calcula a posição do menu de ações na viewport */
|
|
||||||
function atualizarPosicaoMenu(anchor: HTMLElement) {
|
|
||||||
const rect = anchor.getBoundingClientRect()
|
|
||||||
const gap = 8
|
|
||||||
|
|
||||||
// Alinha no canto inferior direito do botão.
|
|
||||||
// Se estourar a tela para baixo, abre para cima.
|
|
||||||
const alturaMenu = menuPopup.value?.menuEl?.value?.offsetHeight ?? 0
|
|
||||||
const larguraMenu = menuPopup.value?.menuEl?.value?.offsetWidth ?? 180
|
|
||||||
|
|
||||||
let top = rect.bottom + gap
|
|
||||||
const left = rect.right - larguraMenu
|
|
||||||
|
|
||||||
if (alturaMenu && top + alturaMenu > window.innerHeight - gap) {
|
|
||||||
top = rect.top - gap - alturaMenu
|
|
||||||
}
|
|
||||||
|
|
||||||
menuPopupPos.value = {
|
|
||||||
top: Math.max(gap, Math.round(top)),
|
|
||||||
left: Math.max(gap, Math.round(left)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Fecha o menu quando ocorre clique fora */
|
|
||||||
function handleClickFora(evento: MouseEvent) {
|
|
||||||
if (menuAberto.value === null) return
|
|
||||||
|
|
||||||
const alvo = evento.target as Node
|
|
||||||
if (menuPopup.value?.menuEl?.value?.contains(alvo)) return
|
|
||||||
|
|
||||||
if (import.meta.env.DEV) {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.log("[EliTabela] click fora => fechar menu", {
|
|
||||||
menuAberto: menuAberto.value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
menuAberto.value = null
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Alterna ordenação e recarrega os dados */
|
|
||||||
function alternarOrdenacao(chave?: string) {
|
|
||||||
if (!chave) return
|
|
||||||
|
|
||||||
if (colunaOrdenacao.value === chave) {
|
|
||||||
direcaoOrdenacao.value =
|
|
||||||
direcaoOrdenacao.value === "asc" ? "desc" : "asc"
|
|
||||||
void carregar()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
colunaOrdenacao.value = chave
|
|
||||||
direcaoOrdenacao.value = "asc"
|
|
||||||
if (paginaAtual.value !== 1) {
|
|
||||||
paginaAtual.value = 1
|
|
||||||
} else {
|
|
||||||
void carregar()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Atualiza a busca e reinicia paginação, se necessário */
|
|
||||||
function atualizarBusca(texto: string) {
|
|
||||||
if (valorBusca.value === texto) return
|
|
||||||
|
|
||||||
valorBusca.value = texto
|
|
||||||
if (paginaAtual.value !== 1) {
|
|
||||||
paginaAtual.value = 1
|
|
||||||
} else {
|
|
||||||
void carregar()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Navega para a página solicitada com limites */
|
|
||||||
function irParaPagina(pagina: number) {
|
|
||||||
const alvo = Math.min(Math.max(1, pagina), totalPaginas.value)
|
|
||||||
if (alvo !== paginaAtual.value) {
|
|
||||||
paginaAtual.value = alvo
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lista ações visíveis por linha, respeitando regras sync/async de `exibir`.
|
|
||||||
*/
|
|
||||||
function acoesDisponiveisPorLinha(i: number) {
|
|
||||||
const acoesLinha = props.tabela.acoesLinha ?? []
|
|
||||||
const visibilidade = acoesVisiveis.value[i] ?? []
|
|
||||||
|
|
||||||
return acoesLinha
|
|
||||||
.map((acao, indice) => {
|
|
||||||
const fallbackVisivel =
|
|
||||||
acao.exibir === undefined
|
|
||||||
? true
|
|
||||||
: typeof acao.exibir === "boolean"
|
|
||||||
? acao.exibir
|
|
||||||
: false
|
|
||||||
|
|
||||||
return {
|
|
||||||
acao,
|
|
||||||
indice,
|
|
||||||
visivel: visibilidade[indice] ?? fallbackVisivel,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.filter((item) => item.visivel)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Informa se a linha possui ações disponíveis */
|
|
||||||
function possuiAcoes(i: number) {
|
|
||||||
return acoesDisponiveisPorLinha(i).length > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Abre/fecha o menu de ações da linha e posiciona o popup */
|
|
||||||
function toggleMenu(i: number, evento?: MouseEvent) {
|
|
||||||
if (!possuiAcoes(i)) return
|
|
||||||
|
|
||||||
if (import.meta.env.DEV) {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.log("[EliTabela] toggleMenu (antes)", {
|
|
||||||
i,
|
|
||||||
atual: menuAberto.value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (menuAberto.value === i) {
|
|
||||||
menuAberto.value = null
|
|
||||||
|
|
||||||
if (import.meta.env.DEV) {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.log("[EliTabela] toggleMenu => fechou", { i })
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
menuAberto.value = i
|
|
||||||
|
|
||||||
if (import.meta.env.DEV) {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.log("[EliTabela] toggleMenu => abriu", { i })
|
|
||||||
}
|
|
||||||
|
|
||||||
// posiciona assim que abrir
|
|
||||||
const anchor = (evento?.currentTarget as HTMLElement | null) ?? null
|
|
||||||
if (anchor) {
|
|
||||||
// primeiro posicionamento (antes do menu medir)
|
|
||||||
atualizarPosicaoMenu(anchor)
|
|
||||||
// reposiciona no próximo frame para pegar altura real
|
|
||||||
requestAnimationFrame(() => atualizarPosicaoMenu(anchor))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Executa consulta, aplica filtros e resolve visibilidade de ações.
|
|
||||||
* Usa o contador sequencial para ignorar respostas antigas.
|
|
||||||
*/
|
|
||||||
async function carregar() {
|
|
||||||
const idCarregamento = ++carregamentoSequencial
|
|
||||||
carregando.value = true
|
|
||||||
erro.value = null
|
|
||||||
|
|
||||||
try {
|
|
||||||
acoesVisiveis.value = []
|
|
||||||
menuAberto.value = null
|
|
||||||
linhasExpandidas.value = {}
|
|
||||||
|
|
||||||
const limite = Math.max(1, registrosPorConsulta.value)
|
|
||||||
const offset = (paginaAtual.value - 1) * limite
|
|
||||||
|
|
||||||
const parametrosConsulta: {
|
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: generic
|
|
||||||
filtros?: tipoFiltro<any>[]
|
|
||||||
coluna_ordem?: never
|
|
||||||
direcao_ordem?: "asc" | "desc"
|
|
||||||
offSet: number
|
|
||||||
limit: number
|
|
||||||
texto_busca?: string
|
|
||||||
} = {
|
|
||||||
offSet: offset,
|
|
||||||
limit: limite,
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
const tabelaConfig = props.tabela
|
|
||||||
const res = await tabelaConfig.consulta(parametrosConsulta)
|
|
||||||
|
|
||||||
if (idCarregamento !== carregamentoSequencial) return
|
|
||||||
|
|
||||||
if (res.cod !== codigosResposta.sucesso) {
|
|
||||||
linhas.value = []
|
|
||||||
quantidade.value = 0
|
|
||||||
erro.value = res.mensagem
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const valores = res.valor?.valores ?? []
|
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: dynamic response
|
|
||||||
const total = (res.valor as any)?.quantidade ?? valores.length
|
|
||||||
|
|
||||||
linhas.value = valores
|
|
||||||
quantidade.value = Number(total) || 0
|
|
||||||
|
|
||||||
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) {
|
|
||||||
acoesVisiveis.value = []
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const preResultado = valores.map(() =>
|
|
||||||
acoesLinhaConfiguradas.map((acao) => {
|
|
||||||
if (acao.exibir === undefined) return true
|
|
||||||
if (typeof acao.exibir === "boolean") return acao.exibir
|
|
||||||
return false
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
acoesVisiveis.value = preResultado
|
|
||||||
|
|
||||||
const visibilidade = await Promise.all(
|
|
||||||
valores.map(async (linha) =>
|
|
||||||
Promise.all(
|
|
||||||
acoesLinhaConfiguradas.map(async (acao) => {
|
|
||||||
if (acao.exibir === undefined) return true
|
|
||||||
if (typeof acao.exibir === "boolean") return acao.exibir
|
|
||||||
|
|
||||||
try {
|
|
||||||
const resultado = acao.exibir(linha as never)
|
|
||||||
return Boolean(await Promise.resolve(resultado))
|
|
||||||
} catch {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
if (idCarregamento === carregamentoSequencial) {
|
|
||||||
acoesVisiveis.value = visibilidade
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error("[EliTabela] Erro capturado em carregar():", e)
|
|
||||||
if (idCarregamento !== carregamentoSequencial) return
|
|
||||||
|
|
||||||
linhas.value = []
|
|
||||||
quantidade.value = 0
|
|
||||||
erro.value = e instanceof Error ? e.message : "Erro ao carregar dados."
|
|
||||||
} finally {
|
|
||||||
if (idCarregamento === carregamentoSequencial) {
|
|
||||||
carregando.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Ciclo de vida: registra listener global e carrega dados iniciais */
|
|
||||||
onMounted(() => {
|
|
||||||
document.addEventListener("click", handleClickFora)
|
|
||||||
void carregar()
|
|
||||||
})
|
|
||||||
|
|
||||||
/** Ciclo de vida: remove listener global */
|
|
||||||
onBeforeUnmount(() => {
|
|
||||||
document.removeEventListener("click", handleClickFora)
|
|
||||||
})
|
|
||||||
|
|
||||||
/** Watch: ao desabilitar busca, limpa termo e recarrega */
|
|
||||||
watch(
|
|
||||||
() => props.tabela.mostrarCaixaDeBusca,
|
|
||||||
(mostrar) => {
|
|
||||||
if (!mostrar && valorBusca.value) {
|
|
||||||
valorBusca.value = ""
|
|
||||||
if (paginaAtual.value !== 1) {
|
|
||||||
paginaAtual.value = 1
|
|
||||||
} else {
|
|
||||||
void carregar()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
/** Watch: mudança de página dispara nova consulta */
|
|
||||||
watch(paginaAtual, (nova, antiga) => {
|
|
||||||
if (nova !== antiga) void carregar()
|
|
||||||
})
|
|
||||||
|
|
||||||
/** Watch: troca de configuração reseta estados e recarrega */
|
|
||||||
watch(
|
|
||||||
() => props.tabela,
|
|
||||||
() => {
|
|
||||||
menuAberto.value = null
|
|
||||||
colunaOrdenacao.value = null
|
|
||||||
direcaoOrdenacao.value = "asc"
|
|
||||||
valorBusca.value = ""
|
|
||||||
modalColunasAberto.value = false
|
|
||||||
modalFiltroAberto.value = false
|
|
||||||
configColunas.value = carregarConfigColunas(props.tabela.nome)
|
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: dynamic reload
|
|
||||||
filtrosUi.value = carregarFiltroAvancado<any>(props.tabela.nome) as any
|
|
||||||
linhasExpandidas.value = {}
|
|
||||||
if (paginaAtual.value !== 1) {
|
|
||||||
paginaAtual.value = 1
|
|
||||||
} else {
|
|
||||||
void carregar()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
/** Watch: alteração do limite de registros reinicia paginação */
|
|
||||||
watch(
|
|
||||||
() => props.tabela.registros_por_consulta,
|
|
||||||
() => {
|
|
||||||
if (paginaAtual.value !== 1) {
|
|
||||||
paginaAtual.value = 1
|
|
||||||
} else {
|
|
||||||
void carregar()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
/** Watch: mudança nas linhas fecha o menu aberto */
|
|
||||||
watch(linhas, () => {
|
|
||||||
menuAberto.value = null
|
|
||||||
linhasExpandidas.value = {}
|
|
||||||
})
|
|
||||||
|
|
||||||
/** Exposição para o template (state, computed, helpers e actions) */
|
|
||||||
return {
|
|
||||||
// state
|
|
||||||
isDev,
|
|
||||||
tabela,
|
|
||||||
carregando,
|
|
||||||
erro,
|
|
||||||
linhas,
|
|
||||||
linhasPaginadas,
|
|
||||||
filtrosAvancadosAtivos,
|
|
||||||
quantidadeFiltrada,
|
|
||||||
quantidade,
|
|
||||||
menuAberto,
|
|
||||||
valorBusca,
|
|
||||||
paginaAtual,
|
|
||||||
colunaOrdenacao,
|
|
||||||
direcaoOrdenacao,
|
|
||||||
totalPaginas,
|
|
||||||
registrosPorConsulta,
|
|
||||||
|
|
||||||
// computed
|
|
||||||
exibirBusca,
|
|
||||||
exibirFiltroAvancado,
|
|
||||||
acoesSuperiores,
|
|
||||||
temAcoesSuperiores,
|
|
||||||
acoesInferiores,
|
|
||||||
temAcoesInferiores,
|
|
||||||
parametrosConsultaAtuais,
|
|
||||||
temAcoes,
|
|
||||||
totalColunas,
|
|
||||||
colunasEfetivas,
|
|
||||||
rotulosColunas,
|
|
||||||
modalColunasAberto,
|
|
||||||
configColunas,
|
|
||||||
|
|
||||||
temColunasInvisiveis,
|
|
||||||
colunasInvisiveisEfetivas,
|
|
||||||
linhasExpandidas,
|
|
||||||
|
|
||||||
// selecao
|
|
||||||
mostrarSelecao,
|
|
||||||
isSelecionado,
|
|
||||||
alternarSelecao,
|
|
||||||
todosSelecionados,
|
|
||||||
algunsSelecionados,
|
|
||||||
alternarSelecaoTodos,
|
|
||||||
|
|
||||||
abrirModalColunas,
|
|
||||||
abrirModalFiltro,
|
|
||||||
fecharModalColunas,
|
|
||||||
salvarModalColunas,
|
|
||||||
|
|
||||||
modalFiltroAberto,
|
|
||||||
filtrosUi,
|
|
||||||
salvarFiltrosAvancados,
|
|
||||||
limparFiltrosAvancados,
|
|
||||||
fecharModalFiltro,
|
|
||||||
|
|
||||||
alternarLinhaExpandida,
|
|
||||||
|
|
||||||
// actions
|
|
||||||
alternarOrdenacao,
|
|
||||||
atualizarBusca,
|
|
||||||
irParaPagina,
|
|
||||||
acoesDisponiveisPorLinha,
|
|
||||||
possuiAcoes,
|
|
||||||
toggleMenu,
|
|
||||||
|
|
||||||
// popup
|
|
||||||
menuPopup,
|
|
||||||
menuPopupPos,
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style src="./EliTabela.css"></style>
|
|
||||||
|
|
|
||||||
|
|
@ -102,12 +102,12 @@ export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
colunas: {
|
colunas: {
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: dynamic column
|
// biome-ignore lint/suspicious/noExplicitAny: dynamic column
|
||||||
type: Array as PropType<Array<tipoEliColuna<any>>>,
|
type: Array as PropType<tipoEliColuna<any>[]>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
colunasInvisiveis: {
|
colunasInvisiveis: {
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: dynamic column
|
// biome-ignore lint/suspicious/noExplicitAny: dynamic column
|
||||||
type: Array as PropType<Array<tipoEliColuna<any>>>,
|
type: Array as PropType<tipoEliColuna<any>[]>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
temColunasInvisiveis: {
|
temColunasInvisiveis: {
|
||||||
|
|
@ -119,7 +119,7 @@ export default defineComponent({
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
linhas: {
|
linhas: {
|
||||||
type: Array as PropType<Array<unknown>>,
|
type: Array as PropType<unknown[]>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
temAcoes: {
|
temAcoes: {
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
class="eli-tabela__acoes-cabecalho-botao eli-tabela__acoes-cabecalho-botao--colunas"
|
class="eli-tabela__acoes-cabecalho-botao eli-tabela__acoes-cabecalho-botao--colunas"
|
||||||
@click="emitColunas"
|
@click="emitColunas"
|
||||||
>
|
>
|
||||||
|
<Columns3 :size="15" :stroke-width="2" />
|
||||||
Colunas
|
Colunas
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
|
@ -17,6 +18,7 @@
|
||||||
class="eli-tabela__acoes-cabecalho-botao eli-tabela__acoes-cabecalho-botao--filtro"
|
class="eli-tabela__acoes-cabecalho-botao eli-tabela__acoes-cabecalho-botao--filtro"
|
||||||
@click="emitFiltroAvancado"
|
@click="emitFiltroAvancado"
|
||||||
>
|
>
|
||||||
|
<Filter :size="15" :stroke-width="2" />
|
||||||
Filtro
|
Filtro
|
||||||
</button>
|
</button>
|
||||||
<EliTabelaCaixaDeBusca :modelo="valorBusca" @buscar="emitBuscar" />
|
<EliTabelaCaixaDeBusca :modelo="valorBusca" @buscar="emitBuscar" />
|
||||||
|
|
@ -46,12 +48,13 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { Columns3, Filter } from "@lucide/vue"
|
||||||
import { computed, defineComponent, type PropType } from "vue"
|
import { computed, defineComponent, type PropType } from "vue"
|
||||||
import EliTabelaCaixaDeBusca from "./EliTabelaCaixaDeBusca.vue"
|
import EliTabelaCaixaDeBusca from "./EliTabelaCaixaDeBusca.vue"
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "EliTabelaCabecalho",
|
name: "EliTabelaCabecalho",
|
||||||
components: { EliTabelaCaixaDeBusca },
|
components: { Columns3, EliTabelaCaixaDeBusca, Filter },
|
||||||
props: {
|
props: {
|
||||||
exibirBusca: {
|
exibirBusca: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@ export default defineComponent({
|
||||||
border-radius: var(--eli-tabela-cabecalho-controle-radius, 8px);
|
border-radius: var(--eli-tabela-cabecalho-controle-radius, 8px);
|
||||||
border: 1px solid rgba(15, 23, 42, 0.15);
|
border: 1px solid rgba(15, 23, 42, 0.15);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: #fff;
|
background: var(--cor-eli-superficie);
|
||||||
|
|
||||||
/* Herda do container do cabeçalho (.eli-tabela__cabecalho) quando presente. */
|
/* Herda do container do cabeçalho (.eli-tabela__cabecalho) quando presente. */
|
||||||
height: var(--eli-tabela-cabecalho-controle-altura, 34px);
|
height: var(--eli-tabela-cabecalho-controle-altura, 34px);
|
||||||
|
|
@ -104,7 +104,7 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
|
|
||||||
.eli-tabela__busca-input::placeholder {
|
.eli-tabela__busca-input::placeholder {
|
||||||
color: rgba(107, 114, 128, 0.85);
|
color: var(--cor-eli-neutro);
|
||||||
}
|
}
|
||||||
|
|
||||||
.eli-tabela__busca-botao {
|
.eli-tabela__busca-botao {
|
||||||
|
|
@ -112,8 +112,8 @@ export default defineComponent({
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
border: none;
|
border: none;
|
||||||
background: rgba(37, 99, 235, 0.12);
|
background: var(--cor-eli-primaria-bg);
|
||||||
color: rgba(37, 99, 235, 0.95);
|
color: var(--cor-eli-primaria-escura);
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding: 0 12px;
|
padding: 0 12px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
@ -126,12 +126,12 @@ export default defineComponent({
|
||||||
|
|
||||||
.eli-tabela__busca-botao:hover,
|
.eli-tabela__busca-botao:hover,
|
||||||
.eli-tabela__busca-botao:focus-visible {
|
.eli-tabela__busca-botao:focus-visible {
|
||||||
background: rgba(37, 99, 235, 0.2);
|
background: var(--cor-eli-primaria-clara);
|
||||||
color: rgba(37, 99, 235, 1);
|
color: var(--cor-eli-primaria-escura);
|
||||||
}
|
}
|
||||||
|
|
||||||
.eli-tabela__busca-botao:focus-visible {
|
.eli-tabela__busca-botao:focus-visible {
|
||||||
outline: 2px solid rgba(37, 99, 235, 0.35);
|
outline: 2px solid var(--cor-eli-primaria-clara);
|
||||||
outline-offset: 2px;
|
outline-offset: 2px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,21 @@
|
||||||
<!-- Debug visual (somente DEV): expõe estados internos úteis para testes e diagnóstico -->
|
<!-- Debug visual (somente DEV): expõe estados internos úteis para testes e diagnóstico -->
|
||||||
<div
|
<div
|
||||||
v-if="isDev"
|
v-if="isDev"
|
||||||
style="position: fixed; left: 8px; bottom: 8px; z-index: 999999; background: rgba(185,28,28,0.9); color: #fff; padding: 6px 10px; border-radius: 8px; font-size: 12px; max-width: 500px;"
|
style="position: fixed; right: 0; top: 0; bottom: 0; width: 360px; z-index: 999999; background: rgba(20, 20, 20, 0.95); color: #32CD32; padding: 20px 16px; font-family: 'Courier New', Courier, monospace; font-size: 12px; overflow-y: auto; border-left: 1px solid #333; display: flex; flex-direction: column; gap: 6px;"
|
||||||
>
|
>
|
||||||
<div><b>EliTabela debug</b></div>
|
<div style="font-weight: bold; color: #fff; margin-bottom: 8px; text-transform: uppercase; letter-spacing: 1px; border-bottom: 1px solid #333; padding-bottom: 8px;">
|
||||||
<div>menuAberto: {{ menuAberto }}</div>
|
EliTabela // Log Terminal
|
||||||
<div>menuPos: top={{ menuPopupPos.top }}, left={{ menuPopupPos.left }}</div>
|
</div>
|
||||||
<slot />
|
<div style="color: #bbb;">menuAberto: <span style="color: #fff;">{{ menuAberto }}</span></div>
|
||||||
|
<div style="color: #bbb;">menuPos: top=<span style="color: #fff;">{{ menuPopupPos.top }}</span>, left=<span style="color: #fff;">{{ menuPopupPos.left }}</span></div>
|
||||||
|
<div style="margin-top: 8px; white-space: pre-wrap; word-break: break-all;">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, type PropType } from "vue"
|
import { defineComponent, onMounted, onUnmounted, type PropType, watch } from "vue"
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "EliTabelaDebug",
|
name: "EliTabelaDebug",
|
||||||
|
|
@ -30,5 +34,39 @@ export default defineComponent({
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
setup(props) {
|
||||||
|
const applyStyle = () => {
|
||||||
|
let styleTag = document.getElementById("eli-tabela-debug-style")
|
||||||
|
if (props.isDev) {
|
||||||
|
if (!styleTag) {
|
||||||
|
styleTag = document.createElement("style")
|
||||||
|
styleTag.id = "eli-tabela-debug-style"
|
||||||
|
styleTag.textContent = `
|
||||||
|
html, body {
|
||||||
|
overflow-x: auto !important;
|
||||||
|
}
|
||||||
|
#app, .v-application__wrap, .v-application {
|
||||||
|
padding-right: 360px !important;
|
||||||
|
box-sizing: border-box !important;
|
||||||
|
}
|
||||||
|
.v-container, .stack {
|
||||||
|
overflow-x: auto !important;
|
||||||
|
max-width: 100% !important;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
document.head.appendChild(styleTag)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (styleTag) styleTag.remove()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => props.isDev, applyStyle)
|
||||||
|
onMounted(applyStyle)
|
||||||
|
onUnmounted(() => {
|
||||||
|
const styleTag = document.getElementById("eli-tabela-debug-style")
|
||||||
|
if (styleTag) styleTag.remove()
|
||||||
|
})
|
||||||
|
},
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
colunasInvisiveis: {
|
colunasInvisiveis: {
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: dynamic column
|
// biome-ignore lint/suspicious/noExplicitAny: dynamic column
|
||||||
type: Array as PropType<Array<tipoEliColuna<any>>>,
|
type: Array as PropType<tipoEliColuna<any>[]>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,33 @@
|
||||||
<template>
|
<template>
|
||||||
<!-- Estado de carregamento: consulta em andamento -->
|
<!-- Estado de carregamento: consulta em andamento -->
|
||||||
<div v-if="carregando" class="eli-tabela eli-tabela--carregando" aria-busy="true">
|
<div v-if="carregando" class="eli-tabela eli-tabela--carregando" aria-busy="true">
|
||||||
Carregando...
|
<LoaderCircle class="eli-tabela__estado-icone eli-tabela__estado-icone--spin" :size="22" :stroke-width="2" />
|
||||||
|
<span>Carregando…</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Estado de erro: mostra mensagem retornada pela consulta -->
|
<!-- Estado de erro: mostra mensagem retornada pela consulta -->
|
||||||
<div v-else-if="erro" class="eli-tabela eli-tabela--erro" role="alert">
|
<div v-else-if="erro" class="eli-tabela eli-tabela--erro" role="alert">
|
||||||
<div class="eli-tabela__erro-titulo">Erro</div>
|
<AlertCircle class="eli-tabela__estado-icone eli-tabela__estado-icone--erro" :size="18" :stroke-width="2" />
|
||||||
<div class="eli-tabela__erro-mensagem">{{ erro }}</div>
|
<div>
|
||||||
|
<div class="eli-tabela__erro-titulo">Erro</div>
|
||||||
|
<div class="eli-tabela__erro-mensagem">{{ erro }}</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Estado vazio: consulta sem registros -->
|
<!-- Estado vazio: consulta sem registros -->
|
||||||
<div v-else class="eli-tabela eli-tabela--vazio">
|
<div v-else class="eli-tabela eli-tabela--vazio">
|
||||||
{{ mensagemVazio ?? "Nenhum registro encontrado." }}
|
<Inbox class="eli-tabela__estado-icone" :size="22" :stroke-width="1.5" />
|
||||||
|
<span>{{ mensagemVazio ?? "Nenhum registro encontrado." }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { AlertCircle, Inbox, LoaderCircle } from "@lucide/vue"
|
||||||
import { defineComponent, type PropType } from "vue"
|
import { defineComponent, type PropType } from "vue"
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "EliTabelaEstados",
|
name: "EliTabelaEstados",
|
||||||
|
components: { AlertCircle, Inbox, LoaderCircle },
|
||||||
props: {
|
props: {
|
||||||
carregando: {
|
carregando: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@ export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
colunas: {
|
colunas: {
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: dynamic column
|
// biome-ignore lint/suspicious/noExplicitAny: dynamic column
|
||||||
type: Array as PropType<Array<tipoEliColuna<any>>>,
|
type: Array as PropType<tipoEliColuna<any>[]>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
temAcoes: {
|
temAcoes: {
|
||||||
|
|
@ -109,9 +109,8 @@ export default defineComponent({
|
||||||
"alternar-selecao-todos": () => true,
|
"alternar-selecao-todos": () => true,
|
||||||
},
|
},
|
||||||
setup(_props, { emit }) {
|
setup(_props, { emit }) {
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: dynamic column
|
function isOrdenavel(coluna: tipoEliColuna<unknown>): boolean {
|
||||||
function isOrdenavel(coluna: any) {
|
return coluna.coluna_ordem !== undefined && coluna.coluna_ordem !== null
|
||||||
return coluna?.coluna_ordem !== undefined && coluna?.coluna_ordem !== null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function emitAlternarOrdenacao(chave: string) {
|
function emitAlternarOrdenacao(chave: string) {
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,7 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
acoes: {
|
acoes: {
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: dynamic typing needed
|
// biome-ignore lint/suspicious/noExplicitAny: dynamic typing needed
|
||||||
type: Array as PropType<Array<ItemAcao<any>>>,
|
type: Array as PropType<ItemAcao<any>[]>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
linha: {
|
linha: {
|
||||||
|
|
|
||||||
342
src/componentes/EliTabela/EliTabelaModalColunas.css
Normal file
342
src/componentes/EliTabela/EliTabelaModalColunas.css
Normal file
|
|
@ -0,0 +1,342 @@
|
||||||
|
.eli-tabela-modal-colunas__overlay {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(15, 23, 42, 0.45);
|
||||||
|
backdrop-filter: blur(2px);
|
||||||
|
z-index: 4000;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-colunas__modal {
|
||||||
|
width: min(820px, 100%);
|
||||||
|
background: var(--cor-eli-superficie);
|
||||||
|
border-radius: 16px;
|
||||||
|
border: 1px solid var(--cor-eli-borda);
|
||||||
|
box-shadow: 0 24px 64px rgba(15, 23, 42, 0.22);
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Header ── */
|
||||||
|
.eli-tabela-modal-colunas__header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 14px 16px;
|
||||||
|
border-bottom: 1px solid var(--cor-eli-borda);
|
||||||
|
background: var(--cor-eli-primaria-header, #ADCC44);
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-colunas__header-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-colunas__header-icone {
|
||||||
|
color: var(--cor-eli-primaria-escura);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-colunas__titulo {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--cor-eli-primaria-texto);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-colunas__fechar {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--cor-eli-primaria-escura);
|
||||||
|
transition: background 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-colunas__fechar:hover,
|
||||||
|
.eli-tabela-modal-colunas__fechar:focus-visible {
|
||||||
|
background: rgba(15, 23, 42, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-colunas__fechar:focus-visible {
|
||||||
|
outline: 2px solid var(--cor-eli-primaria);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Instrução ── */
|
||||||
|
.eli-tabela-modal-colunas__instrucao {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 8px 16px;
|
||||||
|
background: var(--cor-eli-fundo);
|
||||||
|
border-bottom: 1px solid var(--cor-eli-borda);
|
||||||
|
font-size: 0.78rem;
|
||||||
|
color: var(--cor-eli-neutro);
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-colunas__instrucao-icone {
|
||||||
|
flex-shrink: 0;
|
||||||
|
color: var(--cor-eli-borda-forte);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Conteúdo ── */
|
||||||
|
.eli-tabela-modal-colunas__conteudo {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 0;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-colunas__coluna {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-colunas__coluna + .eli-tabela-modal-colunas__coluna {
|
||||||
|
border-left: 1px solid var(--cor-eli-borda);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Cabeçalho das colunas ── */
|
||||||
|
.eli-tabela-modal-colunas__coluna-cabecalho {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
padding: 6px 10px;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-colunas__coluna-cabecalho--visiveis {
|
||||||
|
color: var(--cor-eli-primaria-escura);
|
||||||
|
background: var(--cor-eli-primaria-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-colunas__coluna-cabecalho--invisiveis {
|
||||||
|
color: var(--cor-eli-neutro-escuro);
|
||||||
|
background: var(--cor-eli-fundo);
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-colunas__coluna-contador {
|
||||||
|
margin-left: auto;
|
||||||
|
font-size: 0.72rem;
|
||||||
|
font-weight: 700;
|
||||||
|
padding: 1px 7px;
|
||||||
|
border-radius: 9999px;
|
||||||
|
background: rgba(15, 23, 42, 0.08);
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Lista ── */
|
||||||
|
.eli-tabela-modal-colunas__lista {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 220px;
|
||||||
|
border: 1.5px dashed var(--cor-eli-borda);
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 8px;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: border-color 0.2s ease, background 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-colunas__lista--visiveis {
|
||||||
|
background: rgba(104, 140, 0, 0.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-colunas__lista--invisiveis {
|
||||||
|
background: var(--cor-eli-fundo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Item ── */
|
||||||
|
.eli-tabela-modal-colunas__item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 9px 10px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid var(--cor-eli-borda);
|
||||||
|
background: var(--cor-eli-superficie);
|
||||||
|
cursor: grab;
|
||||||
|
user-select: none;
|
||||||
|
transition: box-shadow 0.15s ease, opacity 0.15s ease, border-color 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-colunas__item + .eli-tabela-modal-colunas__item {
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-colunas__item:hover {
|
||||||
|
border-color: var(--cor-eli-borda-forte);
|
||||||
|
box-shadow: 0 2px 8px rgba(15, 23, 42, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-colunas__item:active {
|
||||||
|
cursor: grabbing;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-colunas__item--invisivel {
|
||||||
|
opacity: 0.72;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-colunas__item--arrastando {
|
||||||
|
opacity: 0.15;
|
||||||
|
border-style: dashed;
|
||||||
|
border-color: var(--cor-eli-primaria-media);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Partes do item ── */
|
||||||
|
.eli-tabela-modal-colunas__item-handle {
|
||||||
|
color: var(--cor-eli-borda-forte);
|
||||||
|
flex-shrink: 0;
|
||||||
|
transition: color 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-colunas__item:hover .eli-tabela-modal-colunas__item-handle {
|
||||||
|
color: var(--cor-eli-neutro);
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-colunas__item-texto {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--cor-eli-neutro-escuro);
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-colunas__item-badge-icone {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-colunas__item-badge-icone--visivel {
|
||||||
|
color: var(--cor-eli-primaria-acao);
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-colunas__item-badge-icone--invisivel {
|
||||||
|
color: var(--cor-eli-borda-forte);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Estado vazio ── */
|
||||||
|
.eli-tabela-modal-colunas__vazio {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 24px 12px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: var(--cor-eli-neutro);
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-colunas__vazio-icone {
|
||||||
|
color: var(--cor-eli-borda-forte);
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-colunas__vazio-icone--ok {
|
||||||
|
color: var(--cor-eli-primaria-acao);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Animações TransitionGroup ── */
|
||||||
|
.list-move,
|
||||||
|
.list-enter-active,
|
||||||
|
.list-leave-active {
|
||||||
|
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-enter-from,
|
||||||
|
.list-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-leave-active {
|
||||||
|
position: absolute;
|
||||||
|
width: calc(100% - 16px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Footer ── */
|
||||||
|
.eli-tabela-modal-colunas__footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-top: 1px solid var(--cor-eli-borda);
|
||||||
|
background: var(--cor-eli-fundo);
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-colunas__botao {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
height: 34px;
|
||||||
|
padding: 0 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid var(--cor-eli-borda);
|
||||||
|
background: var(--cor-eli-superficie);
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: background 0.2s ease, border-color 0.2s ease, color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-colunas__botao--sec {
|
||||||
|
color: var(--cor-eli-neutro-escuro);
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-colunas__botao--sec:hover,
|
||||||
|
.eli-tabela-modal-colunas__botao--sec:focus-visible {
|
||||||
|
background: var(--cor-eli-fundo);
|
||||||
|
border-color: var(--cor-eli-borda-forte);
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-colunas__botao--sec:focus-visible {
|
||||||
|
outline: 2px solid var(--cor-eli-primaria-clara);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-colunas__botao--prim {
|
||||||
|
border: none;
|
||||||
|
background: var(--cor-eli-primaria);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-colunas__botao--prim:hover,
|
||||||
|
.eli-tabela-modal-colunas__botao--prim:focus-visible {
|
||||||
|
background: var(--cor-eli-primaria-escura);
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-colunas__botao--prim:focus-visible {
|
||||||
|
outline: 2px solid var(--cor-eli-primaria-clara);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Responsivo ── */
|
||||||
|
@media (max-width: 720px) {
|
||||||
|
.eli-tabela-modal-colunas__conteudo {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-colunas__coluna + .eli-tabela-modal-colunas__coluna {
|
||||||
|
border-left: none;
|
||||||
|
border-top: 1px solid var(--cor-eli-borda);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,78 +5,124 @@
|
||||||
class="eli-tabela-modal-colunas__modal"
|
class="eli-tabela-modal-colunas__modal"
|
||||||
role="dialog"
|
role="dialog"
|
||||||
aria-modal="true"
|
aria-modal="true"
|
||||||
aria-label="Configurar colunas"
|
aria-labelledby="eli-modal-colunas-titulo"
|
||||||
>
|
>
|
||||||
|
<!-- Header -->
|
||||||
<header class="eli-tabela-modal-colunas__header">
|
<header class="eli-tabela-modal-colunas__header">
|
||||||
<h3 class="eli-tabela-modal-colunas__titulo">Colunas</h3>
|
<div class="eli-tabela-modal-colunas__header-info">
|
||||||
<button type="button" class="eli-tabela-modal-colunas__fechar" aria-label="Fechar" @click="emitFechar">
|
<Columns2 :size="18" :stroke-width="2" class="eli-tabela-modal-colunas__header-icone" />
|
||||||
×
|
<h3 id="eli-modal-colunas-titulo" class="eli-tabela-modal-colunas__titulo">Configurar colunas</h3>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="eli-tabela-modal-colunas__fechar"
|
||||||
|
aria-label="Fechar modal de colunas"
|
||||||
|
@click="emitFechar"
|
||||||
|
>
|
||||||
|
<X :size="16" :stroke-width="2.5" />
|
||||||
</button>
|
</button>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
<!-- Instrução contextual -->
|
||||||
|
<div class="eli-tabela-modal-colunas__instrucao">
|
||||||
|
<GripVertical :size="14" :stroke-width="2" class="eli-tabela-modal-colunas__instrucao-icone" />
|
||||||
|
Arraste para reordenar ou mover entre as listas
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Conteúdo: duas colunas -->
|
||||||
<div class="eli-tabela-modal-colunas__conteudo">
|
<div class="eli-tabela-modal-colunas__conteudo">
|
||||||
|
|
||||||
|
<!-- Visíveis -->
|
||||||
<div class="eli-tabela-modal-colunas__coluna">
|
<div class="eli-tabela-modal-colunas__coluna">
|
||||||
<div class="eli-tabela-modal-colunas__coluna-titulo">Visíveis</div>
|
<div class="eli-tabela-modal-colunas__coluna-cabecalho eli-tabela-modal-colunas__coluna-cabecalho--visiveis">
|
||||||
|
<Eye :size="14" :stroke-width="2" />
|
||||||
|
<span>Visíveis</span>
|
||||||
|
<span class="eli-tabela-modal-colunas__coluna-contador">{{ visiveisLocal.length }}</span>
|
||||||
|
</div>
|
||||||
<TransitionGroup
|
<TransitionGroup
|
||||||
name="list"
|
name="list"
|
||||||
tag="div"
|
tag="div"
|
||||||
class="eli-tabela-modal-colunas__lista"
|
class="eli-tabela-modal-colunas__lista eli-tabela-modal-colunas__lista--visiveis"
|
||||||
@dragover.prevent="onDragOverLista('visiveis')"
|
@dragover.prevent="onDragOverLista('visiveis')"
|
||||||
@drop="onDragEnd"
|
@drop="onDragEnd"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-for="rotulo in visiveisLocal"
|
v-for="rotulo in visiveisLocal"
|
||||||
:key="`vis-${rotulo}`"
|
:key="`vis-${rotulo}`"
|
||||||
class="eli-tabela-modal-colunas__item"
|
class="eli-tabela-modal-colunas__item eli-tabela-modal-colunas__item--visivel"
|
||||||
:class="{
|
:class="{
|
||||||
'eli-tabela-modal-colunas__item--arrastando': itemSendoArrastado === rotulo
|
'eli-tabela-modal-colunas__item--arrastando': itemSendoArrastado === rotulo
|
||||||
}"
|
}"
|
||||||
draggable="true"
|
draggable="true"
|
||||||
|
:title="`Arraste para mover '${rotulo}'`"
|
||||||
@dragstart="onDragStart($event, rotulo)"
|
@dragstart="onDragStart($event, rotulo)"
|
||||||
@dragend="onDragEnd"
|
@dragend="onDragEnd"
|
||||||
@dragover.stop.prevent="onDragOverItem(rotulo, 'visiveis')"
|
@dragover.stop.prevent="onDragOverItem(rotulo, 'visiveis')"
|
||||||
@drop.stop.prevent="onDragEnd"
|
@drop.stop.prevent="onDragEnd"
|
||||||
>
|
>
|
||||||
<span class="eli-tabela-modal-colunas__item-handle" aria-hidden="true">⋮⋮</span>
|
<GripVertical :size="15" :stroke-width="2" class="eli-tabela-modal-colunas__item-handle" aria-hidden="true" />
|
||||||
<span class="eli-tabela-modal-colunas__item-texto">{{ rotulo }}</span>
|
<span class="eli-tabela-modal-colunas__item-texto">{{ rotulo }}</span>
|
||||||
|
<Eye :size="13" :stroke-width="2" class="eli-tabela-modal-colunas__item-badge-icone eli-tabela-modal-colunas__item-badge-icone--visivel" aria-hidden="true" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Estado vazio -->
|
||||||
|
<div v-if="visiveisLocal.length === 0" key="vazio-visiveis" class="eli-tabela-modal-colunas__vazio">
|
||||||
|
<EyeOff :size="28" :stroke-width="1.5" class="eli-tabela-modal-colunas__vazio-icone" />
|
||||||
|
<span>Nenhuma coluna visível.<br>Arraste itens da lista ao lado.</span>
|
||||||
</div>
|
</div>
|
||||||
</TransitionGroup>
|
</TransitionGroup>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Invisíveis -->
|
||||||
<div class="eli-tabela-modal-colunas__coluna">
|
<div class="eli-tabela-modal-colunas__coluna">
|
||||||
<div class="eli-tabela-modal-colunas__coluna-titulo">Invisíveis</div>
|
<div class="eli-tabela-modal-colunas__coluna-cabecalho eli-tabela-modal-colunas__coluna-cabecalho--invisiveis">
|
||||||
|
<EyeOff :size="14" :stroke-width="2" />
|
||||||
|
<span>Ocultas</span>
|
||||||
|
<span class="eli-tabela-modal-colunas__coluna-contador">{{ invisiveisLocal.length }}</span>
|
||||||
|
</div>
|
||||||
<TransitionGroup
|
<TransitionGroup
|
||||||
name="list"
|
name="list"
|
||||||
tag="div"
|
tag="div"
|
||||||
class="eli-tabela-modal-colunas__lista"
|
class="eli-tabela-modal-colunas__lista eli-tabela-modal-colunas__lista--invisiveis"
|
||||||
@dragover.prevent="onDragOverLista('invisiveis')"
|
@dragover.prevent="onDragOverLista('invisiveis')"
|
||||||
@drop="onDragEnd"
|
@drop="onDragEnd"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-for="rotulo in invisiveisLocal"
|
v-for="rotulo in invisiveisLocal"
|
||||||
:key="`inv-${rotulo}`"
|
:key="`inv-${rotulo}`"
|
||||||
class="eli-tabela-modal-colunas__item"
|
class="eli-tabela-modal-colunas__item eli-tabela-modal-colunas__item--invisivel"
|
||||||
:class="{
|
:class="{
|
||||||
'eli-tabela-modal-colunas__item--arrastando': itemSendoArrastado === rotulo
|
'eli-tabela-modal-colunas__item--arrastando': itemSendoArrastado === rotulo
|
||||||
}"
|
}"
|
||||||
draggable="true"
|
draggable="true"
|
||||||
|
:title="`Arraste para mover '${rotulo}'`"
|
||||||
@dragstart="onDragStart($event, rotulo)"
|
@dragstart="onDragStart($event, rotulo)"
|
||||||
@dragend="onDragEnd"
|
@dragend="onDragEnd"
|
||||||
@dragover.stop.prevent="onDragOverItem(rotulo, 'invisiveis')"
|
@dragover.stop.prevent="onDragOverItem(rotulo, 'invisiveis')"
|
||||||
@drop.stop.prevent="onDragEnd"
|
@drop.stop.prevent="onDragEnd"
|
||||||
>
|
>
|
||||||
<span class="eli-tabela-modal-colunas__item-handle" aria-hidden="true">⋮⋮</span>
|
<GripVertical :size="15" :stroke-width="2" class="eli-tabela-modal-colunas__item-handle" aria-hidden="true" />
|
||||||
<span class="eli-tabela-modal-colunas__item-texto">{{ rotulo }}</span>
|
<span class="eli-tabela-modal-colunas__item-texto">{{ rotulo }}</span>
|
||||||
|
<EyeOff :size="13" :stroke-width="2" class="eli-tabela-modal-colunas__item-badge-icone eli-tabela-modal-colunas__item-badge-icone--invisivel" aria-hidden="true" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Estado vazio -->
|
||||||
|
<div v-if="invisiveisLocal.length === 0" key="vazio-invisiveis" class="eli-tabela-modal-colunas__vazio">
|
||||||
|
<Eye :size="28" :stroke-width="1.5" class="eli-tabela-modal-colunas__vazio-icone eli-tabela-modal-colunas__vazio-icone--ok" />
|
||||||
|
<span>Todas as colunas estão visíveis.</span>
|
||||||
</div>
|
</div>
|
||||||
</TransitionGroup>
|
</TransitionGroup>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
<footer class="eli-tabela-modal-colunas__footer">
|
<footer class="eli-tabela-modal-colunas__footer">
|
||||||
<button type="button" class="eli-tabela-modal-colunas__botao eli-tabela-modal-colunas__botao--sec" @click="emitFechar">
|
<button type="button" class="eli-tabela-modal-colunas__botao eli-tabela-modal-colunas__botao--sec" @click="emitFechar">
|
||||||
|
<X :size="14" :stroke-width="2.5" />
|
||||||
Cancelar
|
Cancelar
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="eli-tabela-modal-colunas__botao eli-tabela-modal-colunas__botao--prim" @click="emitSalvar">
|
<button type="button" class="eli-tabela-modal-colunas__botao eli-tabela-modal-colunas__botao--prim" @click="emitSalvar">
|
||||||
|
<Check :size="14" :stroke-width="2.5" />
|
||||||
Salvar
|
Salvar
|
||||||
</button>
|
</button>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
@ -86,30 +132,23 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, type PropType, ref, watch } from "vue"
|
import { Check, Columns2, Eye, EyeOff, GripVertical, X } from "@lucide/vue"
|
||||||
|
import { computed, defineComponent, type PropType } from "vue"
|
||||||
|
import "./EliTabelaModalColunas.css"
|
||||||
import type { EliTabelaColunasConfig } from "./colunasStorage"
|
import type { EliTabelaColunasConfig } from "./colunasStorage"
|
||||||
import type { tipoEliColuna } from "./types-eli-tabela"
|
import type { tipoEliColuna } from "./types-eli-tabela"
|
||||||
|
import { useColunasOrdenacao } from "./useColunasOrdenacao"
|
||||||
type OrigemLista = "visiveis" | "invisiveis"
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "EliTabelaModalColunas",
|
name: "EliTabelaModalColunas",
|
||||||
|
components: { Columns2, Eye, EyeOff, GripVertical, X, Check },
|
||||||
props: {
|
props: {
|
||||||
aberto: {
|
aberto: { type: Boolean, required: true },
|
||||||
type: Boolean,
|
rotulosColunas: { type: Array as PropType<string[]>, required: true },
|
||||||
required: true,
|
configInicial: { type: Object as PropType<EliTabelaColunasConfig>, required: true },
|
||||||
},
|
|
||||||
rotulosColunas: {
|
|
||||||
type: Array as PropType<string[]>,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
configInicial: {
|
|
||||||
type: Object as PropType<EliTabelaColunasConfig>,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
colunas: {
|
colunas: {
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: dynamic typing needed
|
// biome-ignore lint/suspicious/noExplicitAny: dynamic typing needed
|
||||||
type: Array as PropType<Array<tipoEliColuna<any>>>,
|
type: Array as PropType<tipoEliColuna<any>[]>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -122,58 +161,12 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
setup(props, { emit }) {
|
setup(props, { emit }) {
|
||||||
const visiveisLocal = ref<string[]>([])
|
const ordenacao = useColunasOrdenacao({
|
||||||
const invisiveisLocal = ref<string[]>([])
|
aberto: computed(() => props.aberto),
|
||||||
|
rotulosColunas: computed(() => props.rotulosColunas),
|
||||||
const itemSendoArrastado = ref<string | null>(null)
|
configInicial: computed(() => props.configInicial),
|
||||||
|
colunas: computed(() => props.colunas),
|
||||||
function sincronizarEstado() {
|
})
|
||||||
const todos = props.rotulosColunas
|
|
||||||
const configTemDados =
|
|
||||||
(props.configInicial.visiveis?.length ?? 0) > 0 ||
|
|
||||||
(props.configInicial.invisiveis?.length ?? 0) > 0
|
|
||||||
|
|
||||||
const invisiveisPadraoSet = new Set(
|
|
||||||
props.colunas.filter((c) => c.visivel === false).map((c) => c.rotulo),
|
|
||||||
)
|
|
||||||
|
|
||||||
const invisiveisSet = configTemDados
|
|
||||||
? new Set(props.configInicial.invisiveis ?? [])
|
|
||||||
: invisiveisPadraoSet
|
|
||||||
|
|
||||||
const baseVisiveis = todos.filter((r) => !invisiveisSet.has(r))
|
|
||||||
|
|
||||||
// ordenação: aplica ordem salva (visiveis) e adiciona novas ao final
|
|
||||||
const ordemSalva = props.configInicial.visiveis ?? []
|
|
||||||
const setVis = new Set(baseVisiveis)
|
|
||||||
const ordenadas: string[] = []
|
|
||||||
|
|
||||||
for (const r of ordemSalva) {
|
|
||||||
if (setVis.has(r)) ordenadas.push(r)
|
|
||||||
}
|
|
||||||
for (const r of baseVisiveis) {
|
|
||||||
if (!ordenadas.includes(r)) ordenadas.push(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
visiveisLocal.value = ordenadas
|
|
||||||
|
|
||||||
// invisíveis: somente as que existem na tabela
|
|
||||||
invisiveisLocal.value = todos.filter((r) => invisiveisSet.has(r))
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() =>
|
|
||||||
[
|
|
||||||
props.aberto,
|
|
||||||
props.rotulosColunas,
|
|
||||||
props.configInicial,
|
|
||||||
props.colunas,
|
|
||||||
] as const,
|
|
||||||
() => {
|
|
||||||
if (props.aberto) sincronizarEstado()
|
|
||||||
},
|
|
||||||
{ deep: true, immediate: true },
|
|
||||||
)
|
|
||||||
|
|
||||||
function emitFechar() {
|
function emitFechar() {
|
||||||
emit("fechar")
|
emit("fechar")
|
||||||
|
|
@ -181,242 +174,17 @@ export default defineComponent({
|
||||||
|
|
||||||
function emitSalvar() {
|
function emitSalvar() {
|
||||||
emit("salvar", {
|
emit("salvar", {
|
||||||
visiveis: [...visiveisLocal.value],
|
visiveis: [...ordenacao.visiveisLocal.value],
|
||||||
invisiveis: [...invisiveisLocal.value],
|
invisiveis: [...ordenacao.invisiveisLocal.value],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function onDragStart(e: DragEvent, rotulo: string) {
|
|
||||||
itemSendoArrastado.value = rotulo
|
|
||||||
if (e.dataTransfer) {
|
|
||||||
e.dataTransfer.setData("text/plain", rotulo)
|
|
||||||
e.dataTransfer.effectAllowed = "move"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onDragEnd() {
|
|
||||||
itemSendoArrastado.value = null
|
|
||||||
}
|
|
||||||
|
|
||||||
function onDragOverItem(alvoRotulo: string, destino: OrigemLista) {
|
|
||||||
const draggedRotulo = itemSendoArrastado.value
|
|
||||||
if (!draggedRotulo || draggedRotulo === alvoRotulo) return
|
|
||||||
|
|
||||||
const origemVisIdx = visiveisLocal.value.indexOf(draggedRotulo)
|
|
||||||
const origemInvIdx = invisiveisLocal.value.indexOf(draggedRotulo)
|
|
||||||
const origem =
|
|
||||||
origemVisIdx >= 0 ? "visiveis" : origemInvIdx >= 0 ? "invisiveis" : null
|
|
||||||
if (!origem) return
|
|
||||||
|
|
||||||
const origemArr =
|
|
||||||
origem === "visiveis" ? visiveisLocal.value : invisiveisLocal.value
|
|
||||||
const destinoArr =
|
|
||||||
destino === "visiveis" ? visiveisLocal.value : invisiveisLocal.value
|
|
||||||
|
|
||||||
const dragIdx = origemArr.indexOf(draggedRotulo)
|
|
||||||
const alvoIdx = destinoArr.indexOf(alvoRotulo)
|
|
||||||
|
|
||||||
origemArr.splice(dragIdx, 1)
|
|
||||||
destinoArr.splice(alvoIdx, 0, draggedRotulo)
|
|
||||||
}
|
|
||||||
|
|
||||||
function onDragOverLista(destino: OrigemLista) {
|
|
||||||
const draggedRotulo = itemSendoArrastado.value
|
|
||||||
if (!draggedRotulo) return
|
|
||||||
|
|
||||||
const destinoArr =
|
|
||||||
destino === "visiveis" ? visiveisLocal.value : invisiveisLocal.value
|
|
||||||
if (destinoArr[destinoArr.length - 1] === draggedRotulo) return
|
|
||||||
|
|
||||||
const origemVisIdx = visiveisLocal.value.indexOf(draggedRotulo)
|
|
||||||
const origemInvIdx = invisiveisLocal.value.indexOf(draggedRotulo)
|
|
||||||
if (origemVisIdx >= 0) visiveisLocal.value.splice(origemVisIdx, 1)
|
|
||||||
if (origemInvIdx >= 0) invisiveisLocal.value.splice(origemInvIdx, 1)
|
|
||||||
|
|
||||||
destinoArr.push(draggedRotulo)
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
visiveisLocal,
|
...ordenacao,
|
||||||
invisiveisLocal,
|
|
||||||
itemSendoArrastado,
|
|
||||||
emitFechar,
|
emitFechar,
|
||||||
emitSalvar,
|
emitSalvar,
|
||||||
onDragStart,
|
|
||||||
onDragEnd,
|
|
||||||
onDragOverItem,
|
|
||||||
onDragOverLista,
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
|
||||||
.eli-tabela-modal-colunas__overlay {
|
|
||||||
position: fixed;
|
|
||||||
inset: 0;
|
|
||||||
background: rgba(15, 23, 42, 0.35);
|
|
||||||
z-index: 4000;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eli-tabela-modal-colunas__modal {
|
|
||||||
width: min(860px, 100%);
|
|
||||||
background: #fff;
|
|
||||||
border-radius: 14px;
|
|
||||||
border: 1px solid rgba(15, 23, 42, 0.1);
|
|
||||||
box-shadow: 0 18px 60px rgba(15, 23, 42, 0.25);
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eli-tabela-modal-colunas__header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: 14px 16px;
|
|
||||||
border-bottom: 1px solid rgba(15, 23, 42, 0.08);
|
|
||||||
}
|
|
||||||
|
|
||||||
.eli-tabela-modal-colunas__titulo {
|
|
||||||
font-size: 1rem;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eli-tabela-modal-colunas__fechar {
|
|
||||||
width: 34px;
|
|
||||||
height: 34px;
|
|
||||||
border-radius: 10px;
|
|
||||||
border: none;
|
|
||||||
background: transparent;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 22px;
|
|
||||||
line-height: 1;
|
|
||||||
color: rgba(15, 23, 42, 0.8);
|
|
||||||
}
|
|
||||||
|
|
||||||
.eli-tabela-modal-colunas__fechar:hover,
|
|
||||||
.eli-tabela-modal-colunas__fechar:focus-visible {
|
|
||||||
background: rgba(15, 23, 42, 0.06);
|
|
||||||
}
|
|
||||||
|
|
||||||
.eli-tabela-modal-colunas__conteudo {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
gap: 12px;
|
|
||||||
padding: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eli-tabela-modal-colunas__coluna-titulo {
|
|
||||||
font-weight: 600;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eli-tabela-modal-colunas__lista {
|
|
||||||
min-height: 260px;
|
|
||||||
border: 1px solid rgba(15, 23, 42, 0.12);
|
|
||||||
border-radius: 12px;
|
|
||||||
padding: 10px;
|
|
||||||
background: rgba(15, 23, 42, 0.01);
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eli-tabela-modal-colunas__item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
padding: 10px 10px;
|
|
||||||
border-radius: 10px;
|
|
||||||
border: 1px solid rgba(15, 23, 42, 0.08);
|
|
||||||
background: #fff;
|
|
||||||
cursor: grab;
|
|
||||||
user-select: none;
|
|
||||||
transition: box-shadow 0.15s, opacity 0.15s, transform 0.15s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eli-tabela-modal-colunas__item + .eli-tabela-modal-colunas__item {
|
|
||||||
margin-top: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eli-tabela-modal-colunas__item:active {
|
|
||||||
cursor: grabbing;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eli-tabela-modal-colunas__item--arrastando {
|
|
||||||
opacity: 0.1;
|
|
||||||
background: #f8fafc;
|
|
||||||
border-style: dashed;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Animações de reposicionamento e listas do Vue TransitionGroup */
|
|
||||||
.list-move,
|
|
||||||
.list-enter-active,
|
|
||||||
.list-leave-active {
|
|
||||||
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.list-enter-from,
|
|
||||||
.list-leave-to {
|
|
||||||
opacity: 0;
|
|
||||||
transform: scale(0.95);
|
|
||||||
}
|
|
||||||
|
|
||||||
.list-leave-active {
|
|
||||||
/* position absolute para suavizar a animação de saída de outras listas */
|
|
||||||
position: absolute;
|
|
||||||
width: calc(100% - 20px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.eli-tabela-modal-colunas__item-handle {
|
|
||||||
color: rgba(15, 23, 42, 0.55);
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eli-tabela-modal-colunas__item-texto {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eli-tabela-modal-colunas__footer {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
gap: 8px;
|
|
||||||
padding: 14px 16px;
|
|
||||||
border-top: 1px solid rgba(15, 23, 42, 0.08);
|
|
||||||
}
|
|
||||||
|
|
||||||
.eli-tabela-modal-colunas__botao {
|
|
||||||
height: 34px;
|
|
||||||
padding: 0 14px;
|
|
||||||
border-radius: 10px;
|
|
||||||
border: 1px solid rgba(15, 23, 42, 0.12);
|
|
||||||
background: #fff;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eli-tabela-modal-colunas__botao--sec:hover,
|
|
||||||
.eli-tabela-modal-colunas__botao--sec:focus-visible {
|
|
||||||
background: rgba(15, 23, 42, 0.06);
|
|
||||||
}
|
|
||||||
|
|
||||||
.eli-tabela-modal-colunas__botao--prim {
|
|
||||||
border: none;
|
|
||||||
background: rgba(37, 99, 235, 0.95);
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eli-tabela-modal-colunas__botao--prim:hover,
|
|
||||||
.eli-tabela-modal-colunas__botao--prim:focus-visible {
|
|
||||||
background: rgba(37, 99, 235, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 720px) {
|
|
||||||
.eli-tabela-modal-colunas__conteudo {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
||||||
443
src/componentes/EliTabela/EliTabelaModalFiltroAvancado.css
Normal file
443
src/componentes/EliTabela/EliTabelaModalFiltroAvancado.css
Normal file
|
|
@ -0,0 +1,443 @@
|
||||||
|
.eli-tabela-modal-filtro__overlay {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(15, 23, 42, 0.35);
|
||||||
|
z-index: 4000;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-filtro__modal {
|
||||||
|
width: min(980px, 100%);
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 14px;
|
||||||
|
border: 1px solid rgba(15, 23, 42, 0.1);
|
||||||
|
box-shadow: 0 18px 60px rgba(15, 23, 42, 0.25);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Header ──────────────────────────────────── */
|
||||||
|
.eli-tabela-modal-filtro__header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 14px 20px;
|
||||||
|
border-bottom: 1px solid rgba(15, 23, 42, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-filtro__header-titulo {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-filtro__titulo {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0;
|
||||||
|
color: rgba(15, 23, 42, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-filtro__header-icone {
|
||||||
|
color: var(--cor-eli-primaria);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-filtro__contador {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--cor-eli-primaria);
|
||||||
|
background: var(--cor-eli-primaria-bg);
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 10px;
|
||||||
|
letter-spacing: 0.01em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-filtro__fechar {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 34px;
|
||||||
|
height: 34px;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
color: rgba(15, 23, 42, 0.55);
|
||||||
|
transition: background 0.15s, color 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-filtro__fechar:hover,
|
||||||
|
.eli-tabela-modal-filtro__fechar:focus-visible {
|
||||||
|
background: rgba(15, 23, 42, 0.06);
|
||||||
|
color: rgba(15, 23, 42, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Conteúdo ────────────────────────────────── */
|
||||||
|
.eli-tabela-modal-filtro__conteudo {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-filtro__vazio {
|
||||||
|
opacity: 0.65;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
padding: 12px 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Estado vazio orientado ──────────────────── */
|
||||||
|
.eli-tabela-modal-filtro__estado-vazio {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 28px 16px;
|
||||||
|
color: rgba(15, 23, 42, 0.35);
|
||||||
|
border: 1px dashed rgba(15, 23, 42, 0.12);
|
||||||
|
border-radius: 10px;
|
||||||
|
background: rgba(15, 23, 42, 0.015);
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-filtro__estado-vazio span {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Lista de filtros (2 colunas) ────────────── */
|
||||||
|
.eli-tabela-modal-filtro__lista {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-filtro__linha {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border: 1px solid rgba(15, 23, 42, 0.08);
|
||||||
|
border-radius: 10px;
|
||||||
|
background: rgba(15, 23, 42, 0.015);
|
||||||
|
transition: border-color 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-filtro__linha:hover {
|
||||||
|
border-color: var(--cor-eli-primaria-clara);
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-filtro__linha-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-filtro__rotulo-filtro {
|
||||||
|
font-size: 0.82rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: rgba(15, 23, 42, 0.65);
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-filtro__id-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 32px;
|
||||||
|
height: 24px;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: var(--cor-eli-primaria-bg);
|
||||||
|
color: var(--cor-eli-primaria);
|
||||||
|
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
|
||||||
|
font-size: 0.72rem;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.01em;
|
||||||
|
cursor: default;
|
||||||
|
user-select: none;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-filtro__entrada {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-filtro__remover {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 26px;
|
||||||
|
height: 26px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
color: rgba(15, 23, 42, 0.35);
|
||||||
|
transition: background 0.15s, color 0.15s;
|
||||||
|
margin-left: auto;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-filtro__remover:hover,
|
||||||
|
.eli-tabela-modal-filtro__remover:focus-visible {
|
||||||
|
background: rgba(239, 68, 68, 0.08);
|
||||||
|
color: rgba(220, 38, 38, 0.9);
|
||||||
|
border-color: rgba(239, 68, 68, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Rodapé: select + fórmula lado a lado ────── */
|
||||||
|
.eli-tabela-modal-filtro__rodape-filtros {
|
||||||
|
margin-top: 14px;
|
||||||
|
padding-top: 14px;
|
||||||
|
border-top: 1px solid rgba(15, 23, 42, 0.07);
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-filtro__acoes {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-filtro__select {
|
||||||
|
height: 36px;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 1px solid rgba(15, 23, 42, 0.12);
|
||||||
|
padding: 0 10px;
|
||||||
|
background: #fff;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: rgba(15, 23, 42, 0.85);
|
||||||
|
transition: border-color 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-filtro__select:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--cor-eli-primaria);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Fórmula de combinação ───────────────────── */
|
||||||
|
.eli-tabela-modal-filtro__formula-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-filtro__formula-label {
|
||||||
|
font-size: 0.82rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: rgba(15, 23, 42, 0.55);
|
||||||
|
white-space: nowrap;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.eli-tabela-modal-filtro__formula-ajuda {
|
||||||
|
position: relative;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
color: rgba(15, 23, 42, 0.35);
|
||||||
|
cursor: help;
|
||||||
|
transition: color 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-filtro__formula-ajuda:hover,
|
||||||
|
.eli-tabela-modal-filtro__formula-ajuda:focus-visible {
|
||||||
|
color: var(--cor-eli-primaria);
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-filtro__formula-ajuda:hover .eli-tabela-modal-filtro__formula-tooltip,
|
||||||
|
.eli-tabela-modal-filtro__formula-ajuda:focus-visible .eli-tabela-modal-filtro__formula-tooltip {
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: auto;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-filtro__formula-tooltip {
|
||||||
|
position: absolute;
|
||||||
|
bottom: calc(100% + 10px);
|
||||||
|
right: 0;
|
||||||
|
width: 280px;
|
||||||
|
background: #0f172a;
|
||||||
|
color: #e2e8f0;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 14px 16px;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
line-height: 1.55;
|
||||||
|
z-index: 100;
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
transform: translateY(4px);
|
||||||
|
transition: opacity 0.18s ease, transform 0.18s ease;
|
||||||
|
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-filtro__formula-tooltip::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
right: 6px;
|
||||||
|
border: 6px solid transparent;
|
||||||
|
border-top-color: #0f172a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-filtro__formula-tooltip strong {
|
||||||
|
font-size: 0.82rem;
|
||||||
|
color: var(--cor-eli-fundo);
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-filtro__formula-tooltip p {
|
||||||
|
margin: 6px 0 4px;
|
||||||
|
color: #94a3b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-filtro__formula-tooltip ul {
|
||||||
|
margin: 4px 0 8px 14px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-filtro__formula-tooltip li {
|
||||||
|
margin-bottom: 3px;
|
||||||
|
color: #cbd5e1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-filtro__formula-tooltip code {
|
||||||
|
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
|
||||||
|
color: #7dd3fc;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
background: rgba(255, 255, 255, 0.07);
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 1px 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-filtro__formula-input {
|
||||||
|
width: 220px;
|
||||||
|
height: 36px;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 1px solid rgba(15, 23, 42, 0.12);
|
||||||
|
padding: 0 10px;
|
||||||
|
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
background: var(--cor-eli-fundo);
|
||||||
|
transition: border-color 0.15s, background 0.15s;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-filtro__formula-input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--cor-eli-primaria);
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-filtro__formula-tooltip-nota {
|
||||||
|
margin-top: 8px;
|
||||||
|
opacity: 0.75;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Footer ──────────────────────────────────── */
|
||||||
|
.eli-tabela-modal-filtro__footer {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 14px 20px;
|
||||||
|
border-top: 1px solid rgba(15, 23, 42, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-filtro__footer-spacer {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Botões ──────────────────────────────────── */
|
||||||
|
.eli-tabela-modal-filtro__botao {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
height: 36px;
|
||||||
|
padding: 0 16px;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 1px solid rgba(15, 23, 42, 0.12);
|
||||||
|
background: #fff;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: background 0.15s, color 0.15s, border-color 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-filtro__botao:disabled {
|
||||||
|
opacity: 0.45;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-filtro__botao--sec:hover:not(:disabled),
|
||||||
|
.eli-tabela-modal-filtro__botao--sec:focus-visible {
|
||||||
|
background: rgba(15, 23, 42, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-filtro__botao--prim {
|
||||||
|
border: none;
|
||||||
|
background: var(--cor-eli-primaria);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-filtro__botao--prim:hover:not(:disabled),
|
||||||
|
.eli-tabela-modal-filtro__botao--prim:focus-visible {
|
||||||
|
background: var(--cor-eli-primaria-escura);
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-filtro__botao--perigo {
|
||||||
|
color: rgba(220, 38, 38, 0.85);
|
||||||
|
border-color: rgba(220, 38, 38, 0.18);
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-filtro__botao--perigo:hover:not(:disabled),
|
||||||
|
.eli-tabela-modal-filtro__botao--perigo:focus-visible {
|
||||||
|
background: rgba(239, 68, 68, 0.06);
|
||||||
|
border-color: rgba(220, 38, 38, 0.3);
|
||||||
|
color: rgba(220, 38, 38, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Responsivo ──────────────────────────────── */
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.eli-tabela-modal-filtro__lista {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
.eli-tabela-modal-filtro__rodape-filtros {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
.eli-tabela-modal-filtro__acoes {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Transição de abertura/fechamento ────────── */
|
||||||
|
.eli-tabela-modal-filtro-enter-active,
|
||||||
|
.eli-tabela-modal-filtro-leave-active {
|
||||||
|
transition: opacity 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-filtro-enter-active .eli-tabela-modal-filtro__modal,
|
||||||
|
.eli-tabela-modal-filtro-leave-active .eli-tabela-modal-filtro__modal {
|
||||||
|
transition: transform 0.2s ease, opacity 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-filtro-enter-from,
|
||||||
|
.eli-tabela-modal-filtro-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela-modal-filtro-enter-from .eli-tabela-modal-filtro__modal,
|
||||||
|
.eli-tabela-modal-filtro-leave-to .eli-tabela-modal-filtro__modal {
|
||||||
|
transform: scale(0.96);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -1,403 +1,182 @@
|
||||||
<template>
|
<template>
|
||||||
<div v-if="aberto" class="eli-tabela-modal-filtro__overlay" role="presentation" @click.self="emitFechar">
|
<Transition name="eli-tabela-modal-filtro">
|
||||||
<div class="eli-tabela-modal-filtro__modal" role="dialog" aria-modal="true" aria-label="Filtro avançado">
|
<div v-if="aberto" class="eli-tabela-modal-filtro__overlay" role="presentation" @click.self="emitFechar">
|
||||||
<header class="eli-tabela-modal-filtro__header">
|
<div class="eli-tabela-modal-filtro__modal" role="dialog" aria-modal="true" aria-label="Filtro avançado">
|
||||||
<h3 class="eli-tabela-modal-filtro__titulo">Filtro avançado</h3>
|
<header class="eli-tabela-modal-filtro__header">
|
||||||
<button type="button" class="eli-tabela-modal-filtro__fechar" aria-label="Fechar" @click="emitFechar">
|
<div class="eli-tabela-modal-filtro__header-titulo">
|
||||||
×
|
<Filter class="eli-tabela-modal-filtro__header-icone" :size="18" :stroke-width="2" />
|
||||||
</button>
|
<h3 class="eli-tabela-modal-filtro__titulo">Filtro avançado</h3>
|
||||||
</header>
|
<span v-if="linhas.length" class="eli-tabela-modal-filtro__contador">
|
||||||
|
{{ linhas.length }} {{ linhas.length === 1 ? 'filtro' : 'filtros' }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="eli-tabela-modal-filtro__fechar" aria-label="Fechar" @click="emitFechar">
|
||||||
|
<X :size="18" :stroke-width="2" />
|
||||||
|
</button>
|
||||||
|
</header>
|
||||||
|
|
||||||
<div class="eli-tabela-modal-filtro__conteudo">
|
<div class="eli-tabela-modal-filtro__conteudo">
|
||||||
<div v-if="!filtrosBase.length" class="eli-tabela-modal-filtro__vazio">
|
<div v-if="!filtrosBase.length" class="eli-tabela-modal-filtro__vazio">
|
||||||
Nenhum filtro configurado na tabela.
|
Nenhum filtro configurado na tabela.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else class="eli-tabela-modal-filtro__lista">
|
<div v-else>
|
||||||
<div v-for="(linha, idx) in linhas" :key="String(linha.chave)" class="eli-tabela-modal-filtro__linha">
|
<!-- Lista de filtros ativos -->
|
||||||
<div class="eli-tabela-modal-filtro__entrada">
|
<div v-if="linhas.length" class="eli-tabela-modal-filtro__lista">
|
||||||
<component
|
<div
|
||||||
:is="componenteEntrada(linha.entrada)"
|
v-for="(linha, idx) in linhas"
|
||||||
:value="linha.valor"
|
:key="String(linha.chave)"
|
||||||
@update:value="(v: any) => (linha.valor = v)"
|
class="eli-tabela-modal-filtro__linha"
|
||||||
@input="(v: any) => (linha.valor = v)"
|
>
|
||||||
:opcoes="opcoesEntrada(linha.entrada)"
|
<div class="eli-tabela-modal-filtro__linha-header">
|
||||||
density="compact"
|
<span
|
||||||
/>
|
class="eli-tabela-modal-filtro__id-badge"
|
||||||
|
:title="`Identificador na fórmula: {c${idx + 1}}`"
|
||||||
|
>
|
||||||
|
c{{ idx + 1 }}
|
||||||
|
</span>
|
||||||
|
<span class="eli-tabela-modal-filtro__rotulo-filtro">{{ rotuloDoFiltroAtivo(linha.chave) }}</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="eli-tabela-modal-filtro__remover"
|
||||||
|
title="Remover filtro"
|
||||||
|
aria-label="Remover filtro"
|
||||||
|
@click="remover(idx)"
|
||||||
|
>
|
||||||
|
<Trash2 :size="14" :stroke-width="2" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="eli-tabela-modal-filtro__entrada">
|
||||||
|
<component
|
||||||
|
:is="componenteEntrada(linha.entrada)"
|
||||||
|
v-bind="({ value: linha.valor, opcoes: opcoesEntrada(linha.entrada), density: 'compact' } as Record<string, unknown>)"
|
||||||
|
@update:value="(v: unknown) => (linha.valor = v)"
|
||||||
|
@input="(v: unknown) => (linha.valor = v)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<!-- Estado vazio orientado -->
|
||||||
type="button"
|
<div v-else class="eli-tabela-modal-filtro__estado-vazio">
|
||||||
class="eli-tabela-modal-filtro__remover"
|
<Filter :size="32" :stroke-width="1.5" />
|
||||||
title="Remover"
|
<span>Selecione um filtro abaixo para começar</span>
|
||||||
aria-label="Remover"
|
</div>
|
||||||
@click="remover(idx)"
|
|
||||||
>
|
<!-- Rodapé: adicionar filtro + fórmula lado a lado -->
|
||||||
×
|
<div class="eli-tabela-modal-filtro__rodape-filtros">
|
||||||
</button>
|
<div class="eli-tabela-modal-filtro__acoes">
|
||||||
|
<select
|
||||||
|
v-model="chaveParaAdicionar"
|
||||||
|
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.chave)" :value="String(o.chave)">
|
||||||
|
{{ rotuloDoFiltro(o) }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="eli-tabela-modal-filtro__botao eli-tabela-modal-filtro__botao--prim"
|
||||||
|
@click="adicionar"
|
||||||
|
:disabled="!chaveParaAdicionar"
|
||||||
|
>
|
||||||
|
<Plus :size="15" :stroke-width="2.5" />
|
||||||
|
Adicionar
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Fórmula: só aparece com 2+ filtros (com 1 filtro, fórmula não faz sentido) -->
|
||||||
|
<div v-if="linhas.length >= 2" class="eli-tabela-modal-filtro__formula-container">
|
||||||
|
<label class="eli-tabela-modal-filtro__formula-label">Fórmula</label>
|
||||||
|
<input
|
||||||
|
v-model="formulaCustomizada"
|
||||||
|
type="text"
|
||||||
|
class="eli-tabela-modal-filtro__formula-input"
|
||||||
|
:placeholder="'Ex: ' + formulaGerada"
|
||||||
|
/>
|
||||||
|
<span class="eli-tabela-modal-filtro__formula-ajuda" tabindex="0" aria-label="Ajuda sobre fórmulas">
|
||||||
|
<CircleHelp :size="15" :stroke-width="2" />
|
||||||
|
<div class="eli-tabela-modal-filtro__formula-tooltip" role="tooltip">
|
||||||
|
<strong>Como usar a fórmula</strong>
|
||||||
|
<p>Use os identificadores <code>{c1}</code>, <code>{c2}</code>, <code>{c3}</code>… correspondentes a cada filtro acima:</p>
|
||||||
|
<ul>
|
||||||
|
<li><code>E</code> — ambos devem ser verdadeiros</li>
|
||||||
|
<li><code>OU</code> — pelo menos um deve ser verdadeiro</li>
|
||||||
|
<li><code>NAO</code> — negação de uma condição</li>
|
||||||
|
<li><code>( )</code> — agrupa condições</li>
|
||||||
|
</ul>
|
||||||
|
<p><strong>Exemplo:</strong></p>
|
||||||
|
<code>{c1} OU ({c2} E NAO {c3})</code>
|
||||||
|
<p class="eli-tabela-modal-filtro__formula-tooltip-nota">Deixe em branco para combinar todos com E.</p>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="eli-tabela-modal-filtro__acoes">
|
<footer class="eli-tabela-modal-filtro__footer">
|
||||||
<select
|
|
||||||
v-model="chaveParaAdicionar"
|
|
||||||
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.chave)" :value="String(o.chave)">
|
|
||||||
{{ rotuloDoFiltro(o) }}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="eli-tabela-modal-filtro__botao"
|
class="eli-tabela-modal-filtro__botao eli-tabela-modal-filtro__botao--perigo"
|
||||||
@click="adicionar"
|
:disabled="!linhas.length"
|
||||||
:disabled="!chaveParaAdicionar"
|
@click="emitLimpar"
|
||||||
>
|
>
|
||||||
Adicionar
|
Limpar
|
||||||
</button>
|
</button>
|
||||||
</div>
|
<div class="eli-tabela-modal-filtro__footer-spacer"></div>
|
||||||
|
<button type="button" class="eli-tabela-modal-filtro__botao eli-tabela-modal-filtro__botao--sec" @click="emitFechar">
|
||||||
|
Cancelar
|
||||||
|
</button>
|
||||||
|
<button type="button" class="eli-tabela-modal-filtro__botao eli-tabela-modal-filtro__botao--prim" @click="emitSalvar">
|
||||||
|
Aplicar
|
||||||
|
</button>
|
||||||
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer class="eli-tabela-modal-filtro__footer">
|
|
||||||
<button type="button" class="eli-tabela-modal-filtro__botao eli-tabela-modal-filtro__botao--sec" @click="emitLimpar">
|
|
||||||
Limpar
|
|
||||||
</button>
|
|
||||||
<button type="button" class="eli-tabela-modal-filtro__botao eli-tabela-modal-filtro__botao--sec" @click="emitFechar">
|
|
||||||
Cancelar
|
|
||||||
</button>
|
|
||||||
<button type="button" class="eli-tabela-modal-filtro__botao eli-tabela-modal-filtro__botao--prim" @click="emitSalvar">
|
|
||||||
Aplicar
|
|
||||||
</button>
|
|
||||||
</footer>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Transition>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent, type PropType, ref, watch } from "vue"
|
import { CircleHelp, Filter, Plus, Trash2, X } from "@lucide/vue"
|
||||||
import {
|
import { computed, defineComponent, type PropType } from "vue"
|
||||||
EliEntradaDataHora,
|
import type { FiltroBase } from "./useFiltroAvancado"
|
||||||
EliEntradaNumero,
|
// biome-ignore lint/correctness/noUnusedImports: rotuloDoFiltro é usado no template Vue (invisível ao linter estático)
|
||||||
EliEntradaTexto,
|
import { rotuloDoFiltro, useFiltroAvancado } from "./useFiltroAvancado"
|
||||||
} from "../EliEntrada"
|
import "./EliTabelaModalFiltroAvancado.css"
|
||||||
import type { ComponenteEntrada } from "../EliEntrada/tiposEntradas"
|
|
||||||
import type { tipoEliTabelaConsulta } from "./types-eli-tabela"
|
|
||||||
|
|
||||||
type FiltroBase<T> = NonNullable<
|
|
||||||
tipoEliTabelaConsulta<T>["filtroAvancado"]
|
|
||||||
>[number]
|
|
||||||
|
|
||||||
type LinhaFiltro = {
|
|
||||||
chave: string
|
|
||||||
entrada: ComponenteEntrada
|
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: dynamic value
|
|
||||||
valor: any
|
|
||||||
}
|
|
||||||
|
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: dynamic typing needed
|
|
||||||
function rotuloDoFiltro(f: FiltroBase<any>) {
|
|
||||||
return f.rotulo
|
|
||||||
}
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "EliTabelaModalFiltroAvancado",
|
name: "EliTabelaModalFiltroAvancado",
|
||||||
|
components: { CircleHelp, Filter, Plus, Trash2, X },
|
||||||
props: {
|
props: {
|
||||||
aberto: { type: Boolean, required: true },
|
aberto: { type: Boolean, required: true },
|
||||||
filtrosBase: {
|
filtrosBase: { type: Array as PropType<FiltroBase<string>[]>, required: true },
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: generic component
|
|
||||||
type: Array as PropType<Array<FiltroBase<any>>>,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
modelo: {
|
modelo: {
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: dynamic model
|
/** Linhas persistidas do pai — normalizadas internamente via normalizarModelo() */
|
||||||
type: Array as PropType<Array<any>>,
|
type: Array as PropType<Array<{ chave: string; valor: unknown }>>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
modeloFormula: { type: String, default: "" },
|
||||||
},
|
},
|
||||||
emits: {
|
emits: {
|
||||||
fechar: () => true,
|
fechar: () => true,
|
||||||
limpar: () => true,
|
limpar: () => true,
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: dynamic lines
|
/** Dado serializável emitido pelo botão Aplicar */
|
||||||
salvar: (_linhas: any[]) => true,
|
salvar: (_linhas: Array<{ chave: string; valor: unknown }>, _formula?: string) => true,
|
||||||
},
|
},
|
||||||
setup(props, { emit }) {
|
setup(props, { emit }) {
|
||||||
const linhas = ref<Array<LinhaFiltro>>([])
|
return useFiltroAvancado({
|
||||||
|
aberto: computed(() => props.aberto),
|
||||||
const chaveParaAdicionar = ref<string>("")
|
filtrosBase: computed(() => props.filtrosBase),
|
||||||
|
modelo: computed(() => props.modelo),
|
||||||
const chavesDisponiveis = computed(() =>
|
modeloFormula: computed(() => props.modeloFormula),
|
||||||
(props.filtrosBase ?? []).map((b) => String(b.chave)),
|
emit,
|
||||||
)
|
|
||||||
|
|
||||||
const opcoesParaAdicionar = computed(() => {
|
|
||||||
const usadas = new Set(linhas.value.map((l) => String(l.chave)))
|
|
||||||
return (props.filtrosBase ?? []).filter(
|
|
||||||
(b) => !usadas.has(String(b.chave)),
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
function componenteEntrada(entrada: ComponenteEntrada) {
|
|
||||||
const tipo = entrada?.[0]
|
|
||||||
if (tipo === "numero") return EliEntradaNumero
|
|
||||||
if (tipo === "dataHora") return EliEntradaDataHora
|
|
||||||
return EliEntradaTexto
|
|
||||||
}
|
|
||||||
|
|
||||||
function opcoesEntrada(entrada: ComponenteEntrada) {
|
|
||||||
// o rótulo vem do próprio componente (entrada[1].rotulo)
|
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: dynamic options
|
|
||||||
return (entrada?.[1] as any) ?? { rotulo: "" }
|
|
||||||
}
|
|
||||||
|
|
||||||
function valorInicialPorEntrada(entrada: ComponenteEntrada) {
|
|
||||||
const tipo = entrada?.[0]
|
|
||||||
if (tipo === "numero") return null
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizarModelo() {
|
|
||||||
const base = props.filtrosBase ?? []
|
|
||||||
const modelo = Array.isArray(props.modelo) ? props.modelo : []
|
|
||||||
linhas.value = modelo
|
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: dynamic model item
|
|
||||||
.map((m: any) => {
|
|
||||||
const baseItem =
|
|
||||||
base.find((b) => String(b.chave) === String(m.chave)) ?? base[0]
|
|
||||||
|
|
||||||
if (!baseItem) return null
|
|
||||||
|
|
||||||
const entrada = (baseItem.entrada ?? m.entrada) as ComponenteEntrada
|
|
||||||
const chave = String(baseItem.chave ?? m.chave)
|
|
||||||
const val = m.valor ?? valorInicialPorEntrada(entrada)
|
|
||||||
|
|
||||||
return {
|
|
||||||
chave: chave,
|
|
||||||
entrada,
|
|
||||||
valor: val,
|
|
||||||
} as LinhaFiltro
|
|
||||||
})
|
|
||||||
.filter(Boolean) as LinhaFiltro[]
|
|
||||||
|
|
||||||
// se algum filtro mudou a chave para valor inválido, ajusta
|
|
||||||
linhas.value = linhas.value.filter((l) =>
|
|
||||||
chavesDisponiveis.value.includes(String(l.chave)),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => [props.aberto, props.filtrosBase, props.modelo] as const,
|
|
||||||
() => {
|
|
||||||
if (props.aberto) normalizarModelo()
|
|
||||||
},
|
|
||||||
{ deep: true, immediate: true },
|
|
||||||
)
|
|
||||||
|
|
||||||
function adicionar() {
|
|
||||||
if (!chaveParaAdicionar.value) return
|
|
||||||
const b0 = (props.filtrosBase ?? []).find(
|
|
||||||
(b) => String(b.chave) === String(chaveParaAdicionar.value),
|
|
||||||
)
|
|
||||||
if (!b0) return
|
|
||||||
// evita repetição
|
|
||||||
if (linhas.value.some((l) => String(l.chave) === String(b0.chave))) return
|
|
||||||
|
|
||||||
linhas.value.push({
|
|
||||||
chave: String(b0.chave),
|
|
||||||
entrada: b0.entrada,
|
|
||||||
valor: valorInicialPorEntrada(b0.entrada),
|
|
||||||
})
|
|
||||||
|
|
||||||
chaveParaAdicionar.value = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
function remover(idx: number) {
|
|
||||||
linhas.value.splice(idx, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
function emitFechar() {
|
|
||||||
emit("fechar")
|
|
||||||
}
|
|
||||||
|
|
||||||
function emitLimpar() {
|
|
||||||
emit("limpar")
|
|
||||||
}
|
|
||||||
|
|
||||||
function emitSalvar() {
|
|
||||||
emit(
|
|
||||||
"salvar",
|
|
||||||
linhas.value.map((l) => ({
|
|
||||||
chave: l.chave,
|
|
||||||
valor: l.valor,
|
|
||||||
})),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
linhas,
|
|
||||||
opcoesParaAdicionar,
|
|
||||||
chaveParaAdicionar,
|
|
||||||
componenteEntrada,
|
|
||||||
opcoesEntrada,
|
|
||||||
adicionar,
|
|
||||||
remover,
|
|
||||||
emitFechar,
|
|
||||||
emitSalvar,
|
|
||||||
emitLimpar,
|
|
||||||
rotuloDoFiltro,
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
|
||||||
.eli-tabela-modal-filtro__overlay {
|
|
||||||
position: fixed;
|
|
||||||
inset: 0;
|
|
||||||
background: rgba(15, 23, 42, 0.35);
|
|
||||||
z-index: 4000;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eli-tabela-modal-filtro__modal {
|
|
||||||
width: min(980px, 100%);
|
|
||||||
background: #fff;
|
|
||||||
border-radius: 14px;
|
|
||||||
border: 1px solid rgba(15, 23, 42, 0.1);
|
|
||||||
box-shadow: 0 18px 60px rgba(15, 23, 42, 0.25);
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eli-tabela-modal-filtro__header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: 14px 16px;
|
|
||||||
border-bottom: 1px solid rgba(15, 23, 42, 0.08);
|
|
||||||
}
|
|
||||||
|
|
||||||
.eli-tabela-modal-filtro__titulo {
|
|
||||||
font-size: 1rem;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eli-tabela-modal-filtro__fechar {
|
|
||||||
width: 34px;
|
|
||||||
height: 34px;
|
|
||||||
border-radius: 10px;
|
|
||||||
border: none;
|
|
||||||
background: transparent;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 22px;
|
|
||||||
line-height: 1;
|
|
||||||
color: rgba(15, 23, 42, 0.8);
|
|
||||||
}
|
|
||||||
|
|
||||||
.eli-tabela-modal-filtro__fechar:hover,
|
|
||||||
.eli-tabela-modal-filtro__fechar:focus-visible {
|
|
||||||
background: rgba(15, 23, 42, 0.06);
|
|
||||||
}
|
|
||||||
|
|
||||||
.eli-tabela-modal-filtro__conteudo {
|
|
||||||
padding: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eli-tabela-modal-filtro__vazio {
|
|
||||||
opacity: 0.75;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eli-tabela-modal-filtro__lista {
|
|
||||||
display: grid;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eli-tabela-modal-filtro__linha {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 34px;
|
|
||||||
gap: 10px;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eli-tabela-modal-filtro__select {
|
|
||||||
height: 34px;
|
|
||||||
border-radius: 10px;
|
|
||||||
border: 1px solid rgba(15, 23, 42, 0.12);
|
|
||||||
padding: 0 10px;
|
|
||||||
background: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eli-tabela-modal-filtro__entrada {
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eli-tabela-modal-filtro__remover {
|
|
||||||
width: 34px;
|
|
||||||
height: 34px;
|
|
||||||
border-radius: 10px;
|
|
||||||
border: 1px solid rgba(15, 23, 42, 0.12);
|
|
||||||
background: #fff;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 18px;
|
|
||||||
line-height: 1;
|
|
||||||
color: rgba(15, 23, 42, 0.8);
|
|
||||||
}
|
|
||||||
|
|
||||||
.eli-tabela-modal-filtro__remover:hover,
|
|
||||||
.eli-tabela-modal-filtro__remover:focus-visible {
|
|
||||||
background: rgba(15, 23, 42, 0.06);
|
|
||||||
}
|
|
||||||
|
|
||||||
.eli-tabela-modal-filtro__acoes {
|
|
||||||
margin-top: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eli-tabela-modal-filtro__footer {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
gap: 8px;
|
|
||||||
padding: 14px 16px;
|
|
||||||
border-top: 1px solid rgba(15, 23, 42, 0.08);
|
|
||||||
}
|
|
||||||
|
|
||||||
.eli-tabela-modal-filtro__botao {
|
|
||||||
height: 34px;
|
|
||||||
padding: 0 14px;
|
|
||||||
border-radius: 10px;
|
|
||||||
border: 1px solid rgba(15, 23, 42, 0.12);
|
|
||||||
background: #fff;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eli-tabela-modal-filtro__botao:disabled {
|
|
||||||
opacity: 0.55;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eli-tabela-modal-filtro__botao--sec:hover,
|
|
||||||
.eli-tabela-modal-filtro__botao--sec:focus-visible {
|
|
||||||
background: rgba(15, 23, 42, 0.06);
|
|
||||||
}
|
|
||||||
|
|
||||||
.eli-tabela-modal-filtro__botao--prim {
|
|
||||||
border: none;
|
|
||||||
background: rgba(37, 99, 235, 0.95);
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eli-tabela-modal-filtro__botao--prim:hover,
|
|
||||||
.eli-tabela-modal-filtro__botao--prim:focus-visible {
|
|
||||||
background: rgba(37, 99, 235, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 880px) {
|
|
||||||
.eli-tabela-modal-filtro__linha {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
||||||
87
src/componentes/EliTabela/EliTabelaPaginacao.css
Normal file
87
src/componentes/EliTabela/EliTabelaPaginacao.css
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
.eli-tabela__paginacao {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 12px;
|
||||||
|
margin-top: 12px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela__pagina-botao {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 6px 14px;
|
||||||
|
border-radius: 9999px;
|
||||||
|
border: 1px solid rgba(15, 23, 42, 0.12);
|
||||||
|
background: var(--cor-eli-superficie);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: rgba(15, 23, 42, 0.82);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s ease, border-color 0.2s ease,
|
||||||
|
color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela__pagina-botao:hover,
|
||||||
|
.eli-tabela__pagina-botao:focus-visible {
|
||||||
|
background-color: var(--cor-eli-primaria-bg);
|
||||||
|
border-color: var(--cor-eli-primaria-clara);
|
||||||
|
color: var(--cor-eli-primaria-escura);
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela__pagina-botao:focus-visible {
|
||||||
|
outline: 2px solid var(--cor-eli-primaria-clara);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela__pagina-botao:disabled {
|
||||||
|
cursor: default;
|
||||||
|
opacity: 0.5;
|
||||||
|
background: rgba(148, 163, 184, 0.08);
|
||||||
|
border-color: rgba(148, 163, 184, 0.18);
|
||||||
|
color: rgba(71, 85, 105, 0.75);
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela__pagina-botao--ativo {
|
||||||
|
background: var(--cor-eli-primaria-bg);
|
||||||
|
border-color: var(--cor-eli-primaria);
|
||||||
|
color: var(--cor-eli-primaria-escura);
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela__pagina-ellipsis {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 32px;
|
||||||
|
color: var(--cor-eli-neutro);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela__acao-inferior {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
height: 34px;
|
||||||
|
padding: 0 14px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid var(--cor-eli-primaria);
|
||||||
|
background: var(--cor-eli-superficie);
|
||||||
|
color: var(--cor-eli-primaria);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 1;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s ease, color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela__acao-inferior:hover,
|
||||||
|
.eli-tabela__acao-inferior:focus-visible {
|
||||||
|
background-color: var(--cor-eli-primaria-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela__acao-inferior:focus-visible {
|
||||||
|
outline: 2px solid var(--cor-eli-primaria);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
@ -77,7 +77,7 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent, type PropType } from "vue"
|
import { computed, defineComponent, type PropType } from "vue"
|
||||||
|
import "./EliTabelaPaginacao.css"
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "EliTabelaPaginacao",
|
name: "EliTabelaPaginacao",
|
||||||
props: {
|
props: {
|
||||||
|
|
@ -209,9 +209,7 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
|
|
||||||
const anteriorDesabilitado = computed(() => props.pagina <= 1)
|
const anteriorDesabilitado = computed(() => props.pagina <= 1)
|
||||||
const proximaDesabilitada = computed(
|
const proximaDesabilitada = computed(() => props.pagina >= props.totalPaginas)
|
||||||
() => props.pagina >= props.totalPaginas,
|
|
||||||
)
|
|
||||||
const paginaAtual = computed(() => props.pagina)
|
const paginaAtual = computed(() => props.pagina)
|
||||||
const totalPaginasExibidas = computed(() => props.totalPaginas)
|
const totalPaginasExibidas = computed(() => props.totalPaginas)
|
||||||
|
|
||||||
|
|
@ -227,92 +225,3 @@ export default defineComponent({
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
|
||||||
.eli-tabela__paginacao {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: flex-end;
|
|
||||||
gap: 12px;
|
|
||||||
margin-top: 12px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eli-tabela__pagina-botao {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 6px;
|
|
||||||
padding: 6px 14px;
|
|
||||||
border-radius: 9999px;
|
|
||||||
border: 1px solid rgba(15, 23, 42, 0.12);
|
|
||||||
background: #ffffff;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
font-weight: 500;
|
|
||||||
color: rgba(15, 23, 42, 0.82);
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background-color 0.2s ease, border-color 0.2s ease,
|
|
||||||
color 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eli-tabela__pagina-botao:hover,
|
|
||||||
.eli-tabela__pagina-botao:focus-visible {
|
|
||||||
background-color: rgba(37, 99, 235, 0.08);
|
|
||||||
border-color: rgba(37, 99, 235, 0.4);
|
|
||||||
color: rgba(37, 99, 235, 0.95);
|
|
||||||
}
|
|
||||||
|
|
||||||
.eli-tabela__pagina-botao:focus-visible {
|
|
||||||
outline: 2px solid rgba(37, 99, 235, 0.45);
|
|
||||||
outline-offset: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eli-tabela__pagina-botao:disabled {
|
|
||||||
cursor: default;
|
|
||||||
opacity: 0.5;
|
|
||||||
background: rgba(148, 163, 184, 0.08);
|
|
||||||
border-color: rgba(148, 163, 184, 0.18);
|
|
||||||
color: rgba(71, 85, 105, 0.75);
|
|
||||||
}
|
|
||||||
|
|
||||||
.eli-tabela__pagina-botao--ativo {
|
|
||||||
background: rgba(37, 99, 235, 0.12);
|
|
||||||
border-color: rgba(37, 99, 235, 0.4);
|
|
||||||
color: rgba(37, 99, 235, 0.95);
|
|
||||||
}
|
|
||||||
|
|
||||||
.eli-tabela__pagina-ellipsis {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 32px;
|
|
||||||
color: rgba(107, 114, 128, 0.85);
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eli-tabela__acao-inferior {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
height: 34px;
|
|
||||||
padding: 0 14px;
|
|
||||||
border-radius: 8px;
|
|
||||||
border: 1px solid #16a34a; /* default green */
|
|
||||||
background: #ffffff;
|
|
||||||
color: #16a34a;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
font-weight: 500;
|
|
||||||
line-height: 1;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background-color 0.2s ease, color 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eli-tabela__acao-inferior:hover,
|
|
||||||
.eli-tabela__acao-inferior:focus-visible {
|
|
||||||
background-color: #f0fdf4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eli-tabela__acao-inferior:focus-visible {
|
|
||||||
outline: 2px solid #16a34a;
|
|
||||||
outline-offset: 2px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
||||||
|
|
@ -29,15 +29,11 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const tipo = computed(() => props.celula[0] as tipoTabelaCelula)
|
const tipo = computed(() => props.celula[0] as tipoTabelaCelula)
|
||||||
const dados = computed(
|
const dados = computed(() => props.celula[1] as tiposTabelaCelulas[tipoTabelaCelula])
|
||||||
() => props.celula[1] as tiposTabelaCelulas[tipoTabelaCelula],
|
|
||||||
)
|
|
||||||
|
|
||||||
// Observação: mantemos o registry tipado, mas o TS do template não consegue
|
// Observação: mantemos o registry tipado, mas o TS do template não consegue
|
||||||
// fazer narrowing do componente com base em `tipo`, então tipamos como `Component`.
|
// fazer narrowing do componente com base em `tipo`, então tipamos como `Component`.
|
||||||
const Componente = computed(
|
const Componente = computed(() => registryTabelaCelulas[tipo.value] as unknown as Component)
|
||||||
() => registryTabelaCelulas[tipo.value] as unknown as Component,
|
|
||||||
)
|
|
||||||
|
|
||||||
const dadosParaComponente = computed(() => dados.value)
|
const dadosParaComponente = computed(() => dados.value)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,13 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { dayjsbr } from "p-comuns"
|
import dayjs from "dayjs"
|
||||||
|
import relativeTime from "dayjs/plugin/relativeTime"
|
||||||
|
import "dayjs/locale/pt-br"
|
||||||
|
|
||||||
|
dayjs.extend(relativeTime)
|
||||||
|
dayjs.locale("pt-br")
|
||||||
|
|
||||||
import { computed, defineComponent, type PropType } from "vue"
|
import { computed, defineComponent, type PropType } from "vue"
|
||||||
import type { tiposTabelaCelulas } from "./tiposTabelaCelulas"
|
import type { tiposTabelaCelulas } from "./tiposTabelaCelulas"
|
||||||
|
|
||||||
|
|
@ -24,27 +30,27 @@ export default defineComponent({
|
||||||
required: false,
|
required: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
setup({ dados }) {
|
setup(props) {
|
||||||
const textoData = computed(() => {
|
const textoData = computed(() => {
|
||||||
const valorIso = dados?.valor
|
const valorIso = props.dados?.valor
|
||||||
if (!valorIso) return ""
|
if (!valorIso) return ""
|
||||||
|
|
||||||
const formato = dados?.formato ?? "data"
|
const formato = props.dados?.formato ?? "data"
|
||||||
|
|
||||||
if (formato === "relativo") {
|
if (formato === "relativo") {
|
||||||
return dayjsbr(valorIso).fromNow()
|
return dayjs(valorIso).fromNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (formato === "data_hora") {
|
if (formato === "data_hora") {
|
||||||
// Padrão pt-BR simples (sem depender de locale do dayjsbr)
|
// Padrão pt-BR simples (sem depender de locale do dayjsbr)
|
||||||
return dayjsbr(valorIso).format("DD/MM/YYYY HH:mm")
|
return dayjs(valorIso).format("DD/MM/YYYY HH:mm")
|
||||||
}
|
}
|
||||||
|
|
||||||
// formato === "data"
|
// formato === "data"
|
||||||
return dayjsbr(valorIso).format("DD/MM/YYYY")
|
return dayjs(valorIso).format("DD/MM/YYYY")
|
||||||
})
|
})
|
||||||
|
|
||||||
return { dados, textoData }
|
return { textoData }
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -56,7 +62,7 @@ export default defineComponent({
|
||||||
color: #2563eb;
|
color: #2563eb;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
text-decoration-color: rgba(37, 99, 235, 0.55);
|
text-decoration-color: var(--cor-eli-primaria-clara);
|
||||||
text-underline-offset: 2px;
|
text-underline-offset: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -66,7 +72,7 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
|
|
||||||
.eli-tabela__celula-link:focus-visible {
|
.eli-tabela__celula-link:focus-visible {
|
||||||
outline: 2px solid rgba(37, 99, 235, 0.45);
|
outline: 2px solid var(--cor-eli-primaria-clara);
|
||||||
outline-offset: 2px;
|
outline-offset: 2px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,12 +35,12 @@ export default defineComponent({
|
||||||
type: Object as PropType<tiposTabelaCelulas["numero"]>,
|
type: Object as PropType<tiposTabelaCelulas["numero"]>,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
setup({ dados }) {
|
setup(props) {
|
||||||
const textoNumero = computed(() => {
|
const textoNumero = computed(() => {
|
||||||
// Mantemos o comportamento anterior (trocar "." por ","), mas agora suportamos prefixo/sufixo.
|
// Mantemos o comportamento anterior (trocar "." por ","), mas agora suportamos prefixo/sufixo.
|
||||||
const numero = String(dados?.numero).replace(".", ",")
|
const numero = String(props.dados?.numero).replace(".", ",")
|
||||||
const prefixo = dados?.prefixo?.trim()
|
const prefixo = props.dados?.prefixo?.trim()
|
||||||
const sufixo = dados?.sufixo?.trim()
|
const sufixo = props.dados?.sufixo?.trim()
|
||||||
|
|
||||||
const inicio = prefixo ? `${prefixo} ` : ""
|
const inicio = prefixo ? `${prefixo} ` : ""
|
||||||
const fim = sufixo ? ` ${sufixo}` : ""
|
const fim = sufixo ? ` ${sufixo}` : ""
|
||||||
|
|
@ -48,7 +48,7 @@ export default defineComponent({
|
||||||
return `${inicio}${numero}${fim}`
|
return `${inicio}${numero}${fim}`
|
||||||
})
|
})
|
||||||
|
|
||||||
return { dados, textoNumero }
|
return { textoNumero }
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -60,7 +60,7 @@ export default defineComponent({
|
||||||
color: #2563eb;
|
color: #2563eb;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
text-decoration-color: rgba(37, 99, 235, 0.55);
|
text-decoration-color: var(--cor-eli-primaria-clara);
|
||||||
text-underline-offset: 2px;
|
text-underline-offset: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -70,7 +70,7 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
|
|
||||||
.eli-tabela__celula-link:focus-visible {
|
.eli-tabela__celula-link:focus-visible {
|
||||||
outline: 2px solid rgba(37, 99, 235, 0.45);
|
outline: 2px solid var(--cor-eli-primaria-clara);
|
||||||
outline-offset: 2px;
|
outline-offset: 2px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,9 +37,6 @@ export default defineComponent({
|
||||||
required: false,
|
required: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
setup({ dados }) {
|
|
||||||
return { dados }
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,9 +26,6 @@ export default defineComponent({
|
||||||
return {}
|
return {}
|
||||||
},
|
},
|
||||||
methods: {},
|
methods: {},
|
||||||
setup({ dados }) {
|
|
||||||
return { dados }
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -39,7 +36,7 @@ export default defineComponent({
|
||||||
color: #2563eb;
|
color: #2563eb;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
text-decoration-color: rgba(37, 99, 235, 0.55);
|
text-decoration-color: var(--cor-eli-primaria-clara);
|
||||||
text-underline-offset: 2px;
|
text-underline-offset: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -49,7 +46,7 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
|
|
||||||
.eli-tabela__celula-link:focus-visible {
|
.eli-tabela__celula-link:focus-visible {
|
||||||
outline: 2px solid rgba(37, 99, 235, 0.45);
|
outline: 2px solid var(--cor-eli-primaria-clara);
|
||||||
outline-offset: 2px;
|
outline-offset: 2px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,6 @@ export default defineComponent({
|
||||||
type: Object as PropType<tiposTabelaCelulas["textoTruncado"]>,
|
type: Object as PropType<tiposTabelaCelulas["textoTruncado"]>,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
setup({ dados }) {
|
|
||||||
return { dados }
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -45,7 +42,7 @@ export default defineComponent({
|
||||||
color: #2563eb;
|
color: #2563eb;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
text-decoration-color: rgba(37, 99, 235, 0.55);
|
text-decoration-color: var(--cor-eli-primaria-clara);
|
||||||
text-underline-offset: 2px;
|
text-underline-offset: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -55,7 +52,7 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
|
|
||||||
.eli-tabela__celula-link:focus-visible {
|
.eli-tabela__celula-link:focus-visible {
|
||||||
outline: 2px solid rgba(37, 99, 235, 0.45);
|
outline: 2px solid var(--cor-eli-primaria-clara);
|
||||||
outline-offset: 2px;
|
outline-offset: 2px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,16 @@ import EliTabelaCelulaTextoSimples from "./EliTabelaCelulaTextoSimples.vue"
|
||||||
import EliTabelaCelulaTextoTruncado from "./EliTabelaCelulaTextoTruncado.vue"
|
import EliTabelaCelulaTextoTruncado from "./EliTabelaCelulaTextoTruncado.vue"
|
||||||
import type { tipoTabelaCelula } from "./tiposTabelaCelulas"
|
import type { tipoTabelaCelula } from "./tiposTabelaCelulas"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mapa (dispatch table) que associa cada `tipoTabelaCelula` ao seu componente Vue de renderização.
|
||||||
|
* Usado por `EliTabelaCelula.vue` para resolver dinamicamente qual componente renderizar
|
||||||
|
* a partir do tipo retornado por `tipoEliColuna.celula(linha)`.
|
||||||
|
*
|
||||||
|
* Adicionar um novo tipo de célula requer:
|
||||||
|
* 1. Criar o componente `EliTabelaCelula<Tipo>.vue`
|
||||||
|
* 2. Adicionar a chave em `tipoTabelaCelula` (tiposTabelaCelulas.ts)
|
||||||
|
* 3. Registrar aqui no mapa
|
||||||
|
*/
|
||||||
export const registryTabelaCelulas = {
|
export const registryTabelaCelulas = {
|
||||||
textoSimples: EliTabelaCelulaTextoSimples,
|
textoSimples: EliTabelaCelulaTextoSimples,
|
||||||
textoTruncado: EliTabelaCelulaTextoTruncado,
|
textoTruncado: EliTabelaCelulaTextoTruncado,
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,18 @@
|
||||||
export type EliTabelaColunasConfig = {
|
export type EliTabelaColunasConfig = {
|
||||||
/** Rotulos das colunas visiveis (em ordem). */
|
/** Rótulos das colunas visíveis (em ordem). */
|
||||||
visiveis: string[]
|
visiveis: string[]
|
||||||
/** Rotulos das colunas invisiveis. */
|
/** Rótulos das colunas invisíveis. */
|
||||||
invisiveis: string[]
|
invisiveis: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
const STORAGE_PREFIX = "eli:tabela"
|
const STORAGE_PREFIX = "eli:tabela"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retorna a chave de `localStorage` utilizada para persistir a configuração de colunas de uma tabela.
|
||||||
|
*
|
||||||
|
* @param nomeTabela - Identificador único da tabela (campo `nome` de `tipoEliTabelaConsulta`)
|
||||||
|
* @returns Chave no formato `eli:tabela:<nomeTabela>:colunas`
|
||||||
|
*/
|
||||||
export function storageKeyColunas(nomeTabela: string) {
|
export function storageKeyColunas(nomeTabela: string) {
|
||||||
return `${STORAGE_PREFIX}:${nomeTabela}:colunas`
|
return `${STORAGE_PREFIX}:${nomeTabela}:colunas`
|
||||||
}
|
}
|
||||||
|
|
@ -26,9 +32,14 @@ function normalizarConfig(valor: unknown): EliTabelaColunasConfig {
|
||||||
return { visiveis, invisiveis }
|
return { visiveis, invisiveis }
|
||||||
}
|
}
|
||||||
|
|
||||||
export function carregarConfigColunas(
|
/**
|
||||||
nomeTabela: string,
|
* Carrega do `localStorage` a configuração de visibilidade e ordem das colunas de uma tabela.
|
||||||
): EliTabelaColunasConfig {
|
* Em caso de dado corrompido ou ausente, retorna configuração vazia (sem colunas fixadas).
|
||||||
|
*
|
||||||
|
* @param nomeTabela - Identificador único da tabela
|
||||||
|
* @returns Configuração salva ou `{ visiveis: [], invisiveis: [] }` como fallback seguro
|
||||||
|
*/
|
||||||
|
export function carregarConfigColunas(nomeTabela: string): EliTabelaColunasConfig {
|
||||||
try {
|
try {
|
||||||
const raw = window.localStorage.getItem(storageKeyColunas(nomeTabela))
|
const raw = window.localStorage.getItem(storageKeyColunas(nomeTabela))
|
||||||
if (!raw) return { visiveis: [], invisiveis: [] }
|
if (!raw) return { visiveis: [], invisiveis: [] }
|
||||||
|
|
@ -38,24 +49,34 @@ export function carregarConfigColunas(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function salvarConfigColunas(
|
/**
|
||||||
nomeTabela: string,
|
* Persiste no `localStorage` a configuração de visibilidade e ordem das colunas.
|
||||||
config: EliTabelaColunasConfig,
|
* Falhas de armazenamento (modo privado, storage cheio) são silenciadas.
|
||||||
) {
|
*
|
||||||
|
* @param nomeTabela - Identificador único da tabela
|
||||||
|
* @param config - Configuração com listas de colunas visíveis e invisíveis
|
||||||
|
*/
|
||||||
|
export function salvarConfigColunas(nomeTabela: string, config: EliTabelaColunasConfig) {
|
||||||
try {
|
try {
|
||||||
window.localStorage.setItem(
|
window.localStorage.setItem(
|
||||||
storageKeyColunas(nomeTabela),
|
storageKeyColunas(nomeTabela),
|
||||||
JSON.stringify(normalizarConfig(config)),
|
JSON.stringify(normalizarConfig(config)),
|
||||||
)
|
)
|
||||||
} catch {
|
} catch {
|
||||||
// ignore
|
// Silencia erros de storage (modo privado, quota excedida)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove do `localStorage` a configuração de colunas salva para uma tabela.
|
||||||
|
* Usado para restaurar o estado padrão de visibilidade definido nas props.
|
||||||
|
*
|
||||||
|
* @param nomeTabela - Identificador único da tabela
|
||||||
|
*/
|
||||||
export function limparConfigColunas(nomeTabela: string) {
|
export function limparConfigColunas(nomeTabela: string) {
|
||||||
try {
|
try {
|
||||||
window.localStorage.removeItem(storageKeyColunas(nomeTabela))
|
window.localStorage.removeItem(storageKeyColunas(nomeTabela))
|
||||||
} catch {
|
} catch {
|
||||||
// ignore
|
// Silencia erros de storage
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,43 +1,77 @@
|
||||||
export type EliTabelaFiltroAvancadoSalvo<T> = Array<{
|
export type EliTabelaFiltroAvancadoSalvo<T> = {
|
||||||
coluna: keyof T
|
linhas: Array<{
|
||||||
|
chave: keyof T
|
||||||
valor: T[keyof T]
|
/** Valor vindo de JSON.parse — tipo desconhecido intencionalmente; faça narrowing no consumidor. */
|
||||||
}>
|
valor: unknown
|
||||||
|
}>
|
||||||
|
formula?: string
|
||||||
|
}
|
||||||
|
|
||||||
function key(nomeTabela: string) {
|
function key(nomeTabela: string) {
|
||||||
return `eli_tabela:${nomeTabela}:filtro_avancado`
|
return `eli_tabela:${nomeTabela}:filtro_avancado`
|
||||||
}
|
}
|
||||||
|
|
||||||
export function carregarFiltroAvancado<T>(
|
/**
|
||||||
nomeTabela: string,
|
* Carrega do `localStorage` as linhas de filtro avançado salvas para uma tabela.
|
||||||
): EliTabelaFiltroAvancadoSalvo<T> {
|
* Trata dois formatos legados: array direto de filtros (v1) e objeto `{ linhas, formula }` (v2).
|
||||||
|
* Em caso de dado ausente, corrompido ou formato desconhecido, retorna estado vazio.
|
||||||
|
*
|
||||||
|
* @param nomeTabela - Identificador único da tabela (campo `nome` de `tipoEliTabelaConsulta`)
|
||||||
|
* @returns Estado de filtros salvo, ou `{ linhas: [], formula: '' }` como fallback seguro
|
||||||
|
*/
|
||||||
|
export function carregarFiltroAvancado<T>(nomeTabela: string): EliTabelaFiltroAvancadoSalvo<T> {
|
||||||
|
const vazio: EliTabelaFiltroAvancadoSalvo<T> = { linhas: [], formula: "" }
|
||||||
try {
|
try {
|
||||||
const raw = localStorage.getItem(key(nomeTabela))
|
const raw = localStorage.getItem(key(nomeTabela))
|
||||||
if (!raw) return [] as unknown as EliTabelaFiltroAvancadoSalvo<T>
|
if (!raw) return vazio
|
||||||
|
|
||||||
const parsed = JSON.parse(raw)
|
const parsed = JSON.parse(raw)
|
||||||
return Array.isArray(parsed)
|
if (Array.isArray(parsed)) {
|
||||||
? (parsed as EliTabelaFiltroAvancadoSalvo<T>)
|
return { linhas: parsed, formula: "" } as EliTabelaFiltroAvancadoSalvo<T>
|
||||||
: ([] as unknown as EliTabelaFiltroAvancadoSalvo<T>)
|
}
|
||||||
|
|
||||||
|
if (parsed && typeof parsed === "object") {
|
||||||
|
return {
|
||||||
|
linhas: Array.isArray(parsed.linhas) ? parsed.linhas : [],
|
||||||
|
formula: typeof parsed.formula === "string" ? parsed.formula : "",
|
||||||
|
} as EliTabelaFiltroAvancadoSalvo<T>
|
||||||
|
}
|
||||||
|
|
||||||
|
return vazio
|
||||||
} catch {
|
} catch {
|
||||||
return [] as unknown as EliTabelaFiltroAvancadoSalvo<T>
|
return vazio
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Persiste no `localStorage` o estado do filtro avançado de uma tabela.
|
||||||
|
* Salva tanto as linhas de filtro quanto a fórmula booleana customizada.
|
||||||
|
* Falhas de armazenamento são silenciadas para não interromper o fluxo do usuário.
|
||||||
|
*
|
||||||
|
* @param nomeTabela - Identificador único da tabela
|
||||||
|
* @param filtros - Estado completo dos filtros a persistir
|
||||||
|
*/
|
||||||
export function salvarFiltroAvancado<T>(
|
export function salvarFiltroAvancado<T>(
|
||||||
nomeTabela: string,
|
nomeTabela: string,
|
||||||
filtros: EliTabelaFiltroAvancadoSalvo<T>,
|
filtros: EliTabelaFiltroAvancadoSalvo<T>,
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
localStorage.setItem(key(nomeTabela), JSON.stringify(filtros ?? []))
|
localStorage.setItem(key(nomeTabela), JSON.stringify(filtros ?? { linhas: [] }))
|
||||||
} catch {
|
} catch {
|
||||||
// ignore
|
// Silencia erros de storage (modo privado, quota excedida)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove do `localStorage` os filtros avançados salvos para uma tabela.
|
||||||
|
* Chamado ao executar "Limpar Filtros" no modal de filtro avançado.
|
||||||
|
*
|
||||||
|
* @param nomeTabela - Identificador único da tabela
|
||||||
|
*/
|
||||||
export function limparFiltroAvancado(nomeTabela: string) {
|
export function limparFiltroAvancado(nomeTabela: string) {
|
||||||
try {
|
try {
|
||||||
localStorage.removeItem(key(nomeTabela))
|
localStorage.removeItem(key(nomeTabela))
|
||||||
} catch {
|
} catch {
|
||||||
// ignore
|
// Silencia erros de storage
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,9 @@
|
||||||
import type { LucideIcon } from "@lucide/vue"
|
import type { LucideIcon } from "@lucide/vue"
|
||||||
import type { tipoFiltro26 } from "p-comuns"
|
import type { tipoFiltroDeltaColunaTipada } 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 { tiposTabelaCelulas, tipoTabelaCelula } from "./celulas/tiposTabelaCelulas"
|
||||||
tiposTabelaCelulas,
|
|
||||||
tipoTabelaCelula,
|
|
||||||
} from "./celulas/tiposTabelaCelulas"
|
|
||||||
|
|
||||||
export type tipoComponenteCelulaBase<T extends tipoTabelaCelula> = readonly [
|
export type tipoComponenteCelulaBase<T extends tipoTabelaCelula> = readonly [
|
||||||
T,
|
T,
|
||||||
|
|
@ -19,6 +14,20 @@ export type tipoComponenteCelula = {
|
||||||
[K in tipoTabelaCelula]: tipoComponenteCelulaBase<K>
|
[K in tipoTabelaCelula]: tipoComponenteCelulaBase<K>
|
||||||
}[tipoTabelaCelula]
|
}[tipoTabelaCelula]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cria uma tupla tipada `[tipo, dados]` que representa o conteúdo de uma célula da tabela.
|
||||||
|
* Funciona como type-safe builder: o TypeScript infere o tipo exato de `dados` a partir de `tipo`,
|
||||||
|
* garantindo que não seja possível passar dados incorretos para um tipo de célula.
|
||||||
|
*
|
||||||
|
* Uso esperado dentro de `tipoEliColuna.celula`:
|
||||||
|
* ```ts
|
||||||
|
* celula: (linha) => celulaTabela("textoSimples", { texto: linha.nome })
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @param tipo - Identificador do tipo de célula (chave de `tiposTabelaCelulas`)
|
||||||
|
* @param dados - Dados tipados para o tipo de célula escolhido
|
||||||
|
* @returns Tupla `[tipo, dados]` consumida pelo `EliTabelaCelula.vue`
|
||||||
|
*/
|
||||||
export const celulaTabela = <T extends tipoTabelaCelula>(
|
export const celulaTabela = <T extends tipoTabelaCelula>(
|
||||||
tipo: T,
|
tipo: T,
|
||||||
dados: tiposTabelaCelulas[T],
|
dados: tiposTabelaCelulas[T],
|
||||||
|
|
@ -72,7 +81,7 @@ export type tipoEliTabelaAcao<T> = {
|
||||||
}
|
}
|
||||||
|
|
||||||
export type parametrosConsulta<T> = {
|
export type parametrosConsulta<T> = {
|
||||||
filtros?: tipoFiltro26<T>[]
|
filtros?: tipoFiltroDeltaColunaTipada<Extract<keyof T, string>>[]
|
||||||
coluna_ordem?: keyof T
|
coluna_ordem?: keyof T
|
||||||
direcao_ordem?: "asc" | "desc"
|
direcao_ordem?: "asc" | "desc"
|
||||||
offSet?: number
|
offSet?: number
|
||||||
|
|
@ -152,7 +161,7 @@ export type tipoEliTabelaConsulta<T> = {
|
||||||
/** Rótulo exibido no select de adicionar filtro */
|
/** Rótulo exibido no select de adicionar filtro */
|
||||||
rotulo: string
|
rotulo: string
|
||||||
/** vai gerar filtro e mescla com os filtros já existentes */
|
/** vai gerar filtro e mescla com os filtros já existentes */
|
||||||
filtro: (valor: unknown) => tipoFiltro26<T>
|
filtro: (valor: unknown) => tipoFiltroDeltaColunaTipada<Extract<keyof T, string>> | undefined
|
||||||
/** componente de entrada que vai receber o valor para o filtro */
|
/** componente de entrada que vai receber o valor para o filtro */
|
||||||
entrada: ComponenteEntrada
|
entrada: ComponenteEntrada
|
||||||
}[]
|
}[]
|
||||||
|
|
|
||||||
139
src/componentes/EliTabela/useColunasOrdenacao.ts
Normal file
139
src/componentes/EliTabela/useColunasOrdenacao.ts
Normal file
|
|
@ -0,0 +1,139 @@
|
||||||
|
/**
|
||||||
|
* useColunasOrdenacao
|
||||||
|
* Composable que encapsula a lógica de drag-and-drop e sincronização de estado
|
||||||
|
* do modal de configuração de colunas da EliTabela.
|
||||||
|
*
|
||||||
|
* Responsável por:
|
||||||
|
* - Sincronizar colunas visíveis/invisíveis com a configuração persistida
|
||||||
|
* - Aplicar ordem salva e adicionar novas colunas ao final
|
||||||
|
* - Gerenciar eventos de arrastar-e-soltar entre as duas listas
|
||||||
|
*
|
||||||
|
* Extraído de `EliTabelaModalColunas.vue` para manter o componente abaixo de 250 linhas.
|
||||||
|
*/
|
||||||
|
import { type Ref, ref, watch } from "vue"
|
||||||
|
import type { EliTabelaColunasConfig } from "./colunasStorage"
|
||||||
|
import type { tipoEliColuna } from "./types-eli-tabela"
|
||||||
|
|
||||||
|
type OrigemLista = "visiveis" | "invisiveis"
|
||||||
|
|
||||||
|
interface UseColunasOrdenacaoOptions {
|
||||||
|
aberto: Ref<boolean>
|
||||||
|
rotulosColunas: Ref<string[]>
|
||||||
|
configInicial: Ref<EliTabelaColunasConfig>
|
||||||
|
// biome-ignore lint/suspicious/noExplicitAny: colunas genéricas — tipo parametrizado no pai
|
||||||
|
colunas: Ref<tipoEliColuna<any>[]>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Composable de drag-and-drop para reordenação e alternância de visibilidade de colunas.
|
||||||
|
* Sincroniza automaticamente quando `aberto`, `rotulosColunas`, `configInicial` ou `colunas` mudam.
|
||||||
|
*
|
||||||
|
* @param options.aberto - Ref que controla se o modal está aberto (gatilho de sincronização)
|
||||||
|
* @param options.rotulosColunas - Ref com todos os rótulos de colunas da tabela
|
||||||
|
* @param options.configInicial - Ref com a configuração persistida (visíveis + invisíveis)
|
||||||
|
* @param options.colunas - Ref com a definição completa das colunas (para ler `visivel: false` padrão)
|
||||||
|
* @returns Estado reativo e handlers de drag-and-drop prontos para o template
|
||||||
|
*/
|
||||||
|
export function useColunasOrdenacao({
|
||||||
|
aberto,
|
||||||
|
rotulosColunas,
|
||||||
|
configInicial,
|
||||||
|
colunas,
|
||||||
|
}: UseColunasOrdenacaoOptions) {
|
||||||
|
const visiveisLocal = ref<string[]>([])
|
||||||
|
const invisiveisLocal = ref<string[]>([])
|
||||||
|
const itemSendoArrastado = ref<string | null>(null)
|
||||||
|
|
||||||
|
function sincronizarEstado() {
|
||||||
|
const todos = rotulosColunas.value
|
||||||
|
const configTemDados =
|
||||||
|
(configInicial.value.visiveis?.length ?? 0) > 0 ||
|
||||||
|
(configInicial.value.invisiveis?.length ?? 0) > 0
|
||||||
|
|
||||||
|
const invisiveisPadraoSet = new Set(
|
||||||
|
colunas.value.filter((c) => c.visivel === false).map((c) => c.rotulo),
|
||||||
|
)
|
||||||
|
const invisiveisSet = configTemDados
|
||||||
|
? new Set(configInicial.value.invisiveis ?? [])
|
||||||
|
: invisiveisPadraoSet
|
||||||
|
|
||||||
|
const baseVisiveis = todos.filter((r) => !invisiveisSet.has(r))
|
||||||
|
const ordemSalva = configInicial.value.visiveis ?? []
|
||||||
|
const setVis = new Set(baseVisiveis)
|
||||||
|
const ordenadas: string[] = []
|
||||||
|
|
||||||
|
for (const r of ordemSalva) {
|
||||||
|
if (setVis.has(r)) ordenadas.push(r)
|
||||||
|
}
|
||||||
|
for (const r of baseVisiveis) {
|
||||||
|
if (!ordenadas.includes(r)) ordenadas.push(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
visiveisLocal.value = ordenadas
|
||||||
|
invisiveisLocal.value = todos.filter((r) => invisiveisSet.has(r))
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => [aberto.value, rotulosColunas.value, configInicial.value, colunas.value] as const,
|
||||||
|
() => {
|
||||||
|
if (aberto.value) sincronizarEstado()
|
||||||
|
},
|
||||||
|
{ deep: true, immediate: true },
|
||||||
|
)
|
||||||
|
|
||||||
|
function onDragStart(e: DragEvent, rotulo: string) {
|
||||||
|
itemSendoArrastado.value = rotulo
|
||||||
|
if (e.dataTransfer) {
|
||||||
|
e.dataTransfer.setData("text/plain", rotulo)
|
||||||
|
e.dataTransfer.effectAllowed = "move"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDragEnd() {
|
||||||
|
itemSendoArrastado.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDragOverItem(alvoRotulo: string, destino: OrigemLista) {
|
||||||
|
const draggedRotulo = itemSendoArrastado.value
|
||||||
|
if (!draggedRotulo || draggedRotulo === alvoRotulo) return
|
||||||
|
|
||||||
|
const origemVisIdx = visiveisLocal.value.indexOf(draggedRotulo)
|
||||||
|
const origemInvIdx = invisiveisLocal.value.indexOf(draggedRotulo)
|
||||||
|
const origem = origemVisIdx >= 0 ? "visiveis" : origemInvIdx >= 0 ? "invisiveis" : null
|
||||||
|
if (!origem) return
|
||||||
|
|
||||||
|
const origemArr = origem === "visiveis" ? visiveisLocal.value : invisiveisLocal.value
|
||||||
|
const destinoArr = destino === "visiveis" ? visiveisLocal.value : invisiveisLocal.value
|
||||||
|
|
||||||
|
const dragIdx = origemArr.indexOf(draggedRotulo)
|
||||||
|
const alvoIdx = destinoArr.indexOf(alvoRotulo)
|
||||||
|
|
||||||
|
origemArr.splice(dragIdx, 1)
|
||||||
|
destinoArr.splice(alvoIdx, 0, draggedRotulo)
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDragOverLista(destino: OrigemLista) {
|
||||||
|
const draggedRotulo = itemSendoArrastado.value
|
||||||
|
if (!draggedRotulo) return
|
||||||
|
|
||||||
|
const destinoArr = destino === "visiveis" ? visiveisLocal.value : invisiveisLocal.value
|
||||||
|
if (destinoArr[destinoArr.length - 1] === draggedRotulo) return
|
||||||
|
|
||||||
|
const origemVisIdx = visiveisLocal.value.indexOf(draggedRotulo)
|
||||||
|
const origemInvIdx = invisiveisLocal.value.indexOf(draggedRotulo)
|
||||||
|
if (origemVisIdx >= 0) visiveisLocal.value.splice(origemVisIdx, 1)
|
||||||
|
if (origemInvIdx >= 0) invisiveisLocal.value.splice(origemInvIdx, 1)
|
||||||
|
|
||||||
|
destinoArr.push(draggedRotulo)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
visiveisLocal,
|
||||||
|
invisiveisLocal,
|
||||||
|
itemSendoArrastado,
|
||||||
|
onDragStart,
|
||||||
|
onDragEnd,
|
||||||
|
onDragOverItem,
|
||||||
|
onDragOverLista,
|
||||||
|
}
|
||||||
|
}
|
||||||
105
src/componentes/EliTabela/useEliTabela.ts
Normal file
105
src/componentes/EliTabela/useEliTabela.ts
Normal file
|
|
@ -0,0 +1,105 @@
|
||||||
|
/**
|
||||||
|
* useEliTabela
|
||||||
|
* Orquestrador principal da EliTabela — compõe todos os sub-composables
|
||||||
|
* e retorna o estado unificado para o template do componente.
|
||||||
|
*
|
||||||
|
* Composables compostos:
|
||||||
|
* - `useEliTabelaConsulta` — carregamento, paginação, ordenação, busca
|
||||||
|
* - `useEliTabelaFiltrosAvancados` — filtros avançados, modal e persistência
|
||||||
|
* - `useEliTabelaSelecao` — seleção múltipla de linhas
|
||||||
|
* - `useEliTabelaColunas` — visibilidade, ordem e modal de colunas
|
||||||
|
* - `useEliTabelaMenu` — menu de ações por linha e posicionamento de popup
|
||||||
|
*
|
||||||
|
* @see EliTabela.vue — componente que consome este composable
|
||||||
|
*/
|
||||||
|
import { onMounted, ref, watch } from "vue"
|
||||||
|
import { carregarConfigColunas } from "./colunasStorage"
|
||||||
|
import { carregarFiltroAvancado } from "./filtroAvancadoStorage"
|
||||||
|
import type { tipoEliTabelaConsulta } from "./types-eli-tabela"
|
||||||
|
import { useEliTabelaColunas } from "./useEliTabelaColunas"
|
||||||
|
import { useEliTabelaConsulta } from "./useEliTabelaConsulta"
|
||||||
|
import { useEliTabelaFiltrosAvancados } from "./useEliTabelaFiltrosAvancados"
|
||||||
|
import { useEliTabelaMenu } from "./useEliTabelaMenu"
|
||||||
|
import { useEliTabelaSelecao } from "./useEliTabelaSelecao"
|
||||||
|
|
||||||
|
// biome-ignore lint/suspicious/noExplicitAny: dynamic table — genérico passado por prop
|
||||||
|
type TabelaProps = tipoEliTabelaConsulta<any>
|
||||||
|
|
||||||
|
export function useEliTabela(
|
||||||
|
props: { tabela: TabelaProps; selecionados: unknown[]; esconderDebug: boolean },
|
||||||
|
emit: (event: "update:selecionados", value: unknown[]) => void,
|
||||||
|
) {
|
||||||
|
// Estado compartilhado entre sub-composables (passado por ref para reatividade)
|
||||||
|
const paginaAtual = ref(1)
|
||||||
|
const valorBusca = ref<string>("")
|
||||||
|
|
||||||
|
// Filtros avançados (precisa de carregar que ainda não existe; passa proxy que usará o da consulta)
|
||||||
|
// Usamos um ref de função para quebrar a dependência circular: filtros precisam de carregar,
|
||||||
|
// e consulta precisa de filtrosAvancadosAtivos.
|
||||||
|
let _carregarProxy: () => Promise<void> = async () => {
|
||||||
|
/* será atribuído após consulta ser criada */
|
||||||
|
}
|
||||||
|
const carregarProxy = () => _carregarProxy()
|
||||||
|
|
||||||
|
const filtros = useEliTabelaFiltrosAvancados(props, paginaAtual, valorBusca, carregarProxy)
|
||||||
|
|
||||||
|
// Colunas: temAcoes vem da consulta — usamos um ref intermediário
|
||||||
|
const _temAcoesRef = ref(false)
|
||||||
|
const colunas = useEliTabelaColunas(props, _temAcoesRef)
|
||||||
|
|
||||||
|
const consulta = useEliTabelaConsulta(
|
||||||
|
props,
|
||||||
|
filtros.filtrosAvancadosAtivos,
|
||||||
|
paginaAtual,
|
||||||
|
valorBusca,
|
||||||
|
colunas.linhasExpandidas,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Conecta o proxy ao carregar real
|
||||||
|
_carregarProxy = consulta.carregar
|
||||||
|
// Sincroniza temAcoes para o composable de colunas
|
||||||
|
// biome-ignore lint/suspicious/noExplicitAny: ref sync interno
|
||||||
|
;(_temAcoesRef as any).value = consulta.temAcoes.value
|
||||||
|
watch(consulta.temAcoes, (v) => {
|
||||||
|
_temAcoesRef.value = v
|
||||||
|
})
|
||||||
|
|
||||||
|
const selecao = useEliTabelaSelecao(props, emit, consulta.linhasPaginadas)
|
||||||
|
const menu = useEliTabelaMenu(props, consulta.acoesVisiveis)
|
||||||
|
|
||||||
|
// Watch: troca de configuração de tabela reseta todos os estados e recarrega
|
||||||
|
watch(
|
||||||
|
() => props.tabela,
|
||||||
|
() => {
|
||||||
|
menu.menuAberto.value = null
|
||||||
|
consulta.colunaOrdenacao.value = null
|
||||||
|
consulta.direcaoOrdenacao.value = "asc"
|
||||||
|
valorBusca.value = ""
|
||||||
|
colunas.modalColunasAberto.value = false
|
||||||
|
filtros.modalFiltroAberto.value = false
|
||||||
|
colunas.configColunas.value = carregarConfigColunas(props.tabela.nome)
|
||||||
|
const filtrosRecarregados = carregarFiltroAvancado<Record<string, unknown>>(props.tabela.nome)
|
||||||
|
filtros.filtrosUi.value = filtrosRecarregados.linhas
|
||||||
|
colunas.linhasExpandidas.value = {}
|
||||||
|
if (paginaAtual.value !== 1) paginaAtual.value = 1
|
||||||
|
else void consulta.carregar()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
onMounted(() => void consulta.carregar())
|
||||||
|
|
||||||
|
return {
|
||||||
|
...consulta,
|
||||||
|
...filtros,
|
||||||
|
...selecao,
|
||||||
|
...colunas,
|
||||||
|
menuAberto: menu.menuAberto,
|
||||||
|
menuPopup: menu.menuPopup,
|
||||||
|
menuPopupPos: menu.menuPopupPos,
|
||||||
|
possuiAcoes: menu.possuiAcoes,
|
||||||
|
toggleMenu: menu.toggleMenu,
|
||||||
|
acoesDisponiveisPorLinha: menu.acoesDisponiveisPorLinha,
|
||||||
|
paginaAtual,
|
||||||
|
valorBusca,
|
||||||
|
}
|
||||||
|
}
|
||||||
129
src/componentes/EliTabela/useEliTabelaColunas.ts
Normal file
129
src/componentes/EliTabela/useEliTabelaColunas.ts
Normal file
|
|
@ -0,0 +1,129 @@
|
||||||
|
/**
|
||||||
|
* useEliTabelaColunas
|
||||||
|
* Gerencia visibilidade, ordenação e persistência da configuração de colunas da EliTabela.
|
||||||
|
* Controla também o modal de colunas e o estado de linhas expandidas (detalhe de colunas invisíveis).
|
||||||
|
*
|
||||||
|
* @param props - Props do componente (tabela.colunas, tabela.nome)
|
||||||
|
* @param temAcoes - Computed que indica se existem ações por linha (para totalColunas)
|
||||||
|
*/
|
||||||
|
import { computed, type Ref, ref } from "vue"
|
||||||
|
import {
|
||||||
|
carregarConfigColunas,
|
||||||
|
type EliTabelaColunasConfig,
|
||||||
|
salvarConfigColunas,
|
||||||
|
} from "./colunasStorage"
|
||||||
|
import type { tipoEliColuna } from "./types-eli-tabela"
|
||||||
|
|
||||||
|
// biome-ignore lint/suspicious/noExplicitAny: colunas dinâmicas — genérico passado por prop
|
||||||
|
type PropsColunas = { tabela: { colunas: tipoEliColuna<any>[]; nome: string } }
|
||||||
|
|
||||||
|
export function useEliTabelaColunas(props: PropsColunas, temAcoes: Ref<boolean>) {
|
||||||
|
const modalColunasAberto = ref(false)
|
||||||
|
const configColunas = ref<EliTabelaColunasConfig>(carregarConfigColunas(props.tabela.nome))
|
||||||
|
const linhasExpandidas = ref<Record<number, boolean>>({})
|
||||||
|
|
||||||
|
const rotulosColunas = computed(() => props.tabela.colunas.map((c) => c.rotulo))
|
||||||
|
|
||||||
|
const colunasInvisiveisEfetivas = computed(() => {
|
||||||
|
// biome-ignore lint/suspicious/noExplicitAny: dynamic column
|
||||||
|
const colunas = props.tabela.colunas as tipoEliColuna<any>[]
|
||||||
|
const configTemDados =
|
||||||
|
(configColunas.value.visiveis?.length ?? 0) > 0 ||
|
||||||
|
(configColunas.value.invisiveis?.length ?? 0) > 0
|
||||||
|
const invisiveisBaseRotulos = configTemDados
|
||||||
|
? (configColunas.value.invisiveis ?? [])
|
||||||
|
: colunas.filter((c) => c.visivel === false).map((c) => c.rotulo)
|
||||||
|
const invisiveisSet = new Set(invisiveisBaseRotulos)
|
||||||
|
const base = colunas.filter((c) => invisiveisSet.has(c.rotulo))
|
||||||
|
// biome-ignore lint/suspicious/noExplicitAny: dynamic map
|
||||||
|
const mapa = new Map<string, tipoEliColuna<any>>()
|
||||||
|
for (const c of base) {
|
||||||
|
if (!mapa.has(c.rotulo)) mapa.set(c.rotulo, c)
|
||||||
|
}
|
||||||
|
// biome-ignore lint/suspicious/noExplicitAny: dynamic column
|
||||||
|
const ordenadas: tipoEliColuna<any>[] = []
|
||||||
|
for (const r of invisiveisBaseRotulos) {
|
||||||
|
const c = mapa.get(r)
|
||||||
|
if (c) ordenadas.push(c)
|
||||||
|
}
|
||||||
|
for (const c of base) {
|
||||||
|
if (!ordenadas.includes(c)) ordenadas.push(c)
|
||||||
|
}
|
||||||
|
return ordenadas
|
||||||
|
})
|
||||||
|
|
||||||
|
const temColunasInvisiveis = computed(() => colunasInvisiveisEfetivas.value.length > 0)
|
||||||
|
|
||||||
|
const colunasEfetivas = computed(() => {
|
||||||
|
const colunas = props.tabela.colunas
|
||||||
|
const todosRotulos = rotulosColunas.value
|
||||||
|
const configTemDados =
|
||||||
|
(configColunas.value.visiveis?.length ?? 0) > 0 ||
|
||||||
|
(configColunas.value.invisiveis?.length ?? 0) > 0
|
||||||
|
const invisiveisBaseRotulos = configTemDados
|
||||||
|
? (configColunas.value.invisiveis ?? [])
|
||||||
|
: // biome-ignore lint/suspicious/noExplicitAny: dynamic column
|
||||||
|
(props.tabela.colunas as tipoEliColuna<any>[])
|
||||||
|
.filter((c) => c.visivel === false)
|
||||||
|
.map((c) => c.rotulo)
|
||||||
|
const invisiveisSet = new Set(invisiveisBaseRotulos)
|
||||||
|
const visiveisBaseRotulos = todosRotulos.filter((r) => !invisiveisSet.has(r))
|
||||||
|
const visiveisSet = new Set(visiveisBaseRotulos)
|
||||||
|
const ordemSalva = configTemDados ? (configColunas.value.visiveis ?? []) : []
|
||||||
|
const ordemFinal: string[] = []
|
||||||
|
for (const r of ordemSalva) {
|
||||||
|
if (visiveisSet.has(r)) ordemFinal.push(r)
|
||||||
|
}
|
||||||
|
for (const r of visiveisBaseRotulos) {
|
||||||
|
if (!ordemFinal.includes(r)) ordemFinal.push(r)
|
||||||
|
}
|
||||||
|
// biome-ignore lint/suspicious/noExplicitAny: dynamic map
|
||||||
|
const mapa = new Map<string, any>()
|
||||||
|
for (const c of colunas) {
|
||||||
|
if (!mapa.has(c.rotulo)) mapa.set(c.rotulo, c)
|
||||||
|
}
|
||||||
|
return ordemFinal.map((r) => mapa.get(r)).filter(Boolean)
|
||||||
|
})
|
||||||
|
|
||||||
|
const totalColunas = computed(
|
||||||
|
() =>
|
||||||
|
colunasEfetivas.value.length +
|
||||||
|
(temAcoes.value ? 1 : 0) +
|
||||||
|
(temColunasInvisiveis.value ? 1 : 0),
|
||||||
|
)
|
||||||
|
|
||||||
|
function abrirModalColunas() {
|
||||||
|
modalColunasAberto.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function fecharModalColunas() {
|
||||||
|
modalColunasAberto.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function salvarModalColunas(cfg: EliTabelaColunasConfig) {
|
||||||
|
configColunas.value = cfg
|
||||||
|
salvarConfigColunas(props.tabela.nome, cfg)
|
||||||
|
modalColunasAberto.value = false
|
||||||
|
linhasExpandidas.value = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
function alternarLinhaExpandida(indice: number) {
|
||||||
|
const atual = Boolean(linhasExpandidas.value[indice])
|
||||||
|
linhasExpandidas.value = { ...linhasExpandidas.value, [indice]: !atual }
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
modalColunasAberto,
|
||||||
|
configColunas,
|
||||||
|
linhasExpandidas,
|
||||||
|
rotulosColunas,
|
||||||
|
colunasEfetivas,
|
||||||
|
colunasInvisiveisEfetivas,
|
||||||
|
temColunasInvisiveis,
|
||||||
|
totalColunas,
|
||||||
|
abrirModalColunas,
|
||||||
|
fecharModalColunas,
|
||||||
|
salvarModalColunas,
|
||||||
|
alternarLinhaExpandida,
|
||||||
|
}
|
||||||
|
}
|
||||||
247
src/componentes/EliTabela/useEliTabelaConsulta.ts
Normal file
247
src/componentes/EliTabela/useEliTabelaConsulta.ts
Normal file
|
|
@ -0,0 +1,247 @@
|
||||||
|
/**
|
||||||
|
* useEliTabelaConsulta
|
||||||
|
* Gerencia o ciclo de consultas da EliTabela: carregamento, paginação, ordenação e busca.
|
||||||
|
* Usa um contador sequencial para descartar respostas de consultas antigas (race condition).
|
||||||
|
*
|
||||||
|
* @param props - Props do componente (tabela, esconderDebug)
|
||||||
|
* @param filtrosAvancadosAtivos - Computed com filtros ativos para envio à API
|
||||||
|
* @param paginaAtual - Ref da página atual (compartilhada com filtros e busca)
|
||||||
|
* @param valorBusca - Ref da busca textual (compartilhada com filtros avançados)
|
||||||
|
* @param linhasExpandidas - Ref das linhas expandidas (resetada ao carregar)
|
||||||
|
*/
|
||||||
|
import type { tipoFiltroDeltaColunaTipada } from "p-comuns"
|
||||||
|
import { codigosResposta } from "p-respostas"
|
||||||
|
import { computed, type Ref, ref, watch } from "vue"
|
||||||
|
import type { parametrosConsulta, tipoEliTabelaConsulta } from "./types-eli-tabela"
|
||||||
|
|
||||||
|
// biome-ignore lint/suspicious/noExplicitAny: genérico passado por prop
|
||||||
|
type PropsConsulta = { tabela: tipoEliTabelaConsulta<any>; esconderDebug: boolean }
|
||||||
|
|
||||||
|
export function useEliTabelaConsulta(
|
||||||
|
props: PropsConsulta,
|
||||||
|
// biome-ignore lint/suspicious/noExplicitAny: genérico
|
||||||
|
filtrosAvancadosAtivos: Ref<tipoFiltroDeltaColunaTipada<any>[]>,
|
||||||
|
paginaAtual: Ref<number>,
|
||||||
|
valorBusca: Ref<string>,
|
||||||
|
linhasExpandidas: Ref<Record<number, boolean>>,
|
||||||
|
) {
|
||||||
|
const isDev = import.meta.env.DEV && !props.esconderDebug
|
||||||
|
const carregando = ref(false)
|
||||||
|
const erro = ref<string | null>(null)
|
||||||
|
const linhas = ref<unknown[]>([])
|
||||||
|
const quantidade = ref<number>(0)
|
||||||
|
const acoesVisiveis = ref<boolean[][]>([])
|
||||||
|
const colunaOrdenacao = ref<string | null>(null)
|
||||||
|
const direcaoOrdenacao = ref<"asc" | "desc">("asc")
|
||||||
|
|
||||||
|
const tabela = computed(() => props.tabela)
|
||||||
|
const registrosPorConsulta = computed(() => {
|
||||||
|
const valor = props.tabela.registros_por_consulta
|
||||||
|
return typeof valor === "number" && valor > 0 ? Math.floor(valor) : 10
|
||||||
|
})
|
||||||
|
const totalPaginas = computed(() => {
|
||||||
|
const limite = registrosPorConsulta.value
|
||||||
|
if (!limite || limite <= 0) return 1
|
||||||
|
const total = quantidade.value ?? 0
|
||||||
|
return total ? Math.max(1, Math.ceil(total / limite)) : 1
|
||||||
|
})
|
||||||
|
const linhasPaginadas = computed(() => linhas.value ?? [])
|
||||||
|
const quantidadeFiltrada = computed(() => quantidade.value ?? 0)
|
||||||
|
const temAcoes = computed(() => (props.tabela.acoesLinha ?? []).length > 0)
|
||||||
|
const exibirBusca = computed(() => Boolean(props.tabela.mostrarCaixaDeBusca))
|
||||||
|
const acoesSuperiores = computed(() =>
|
||||||
|
(props.tabela.acoesTabela ?? []).filter((a) => a.posicao === "superior"),
|
||||||
|
)
|
||||||
|
const acoesInferiores = computed(() =>
|
||||||
|
(props.tabela.acoesTabela ?? []).filter((a) => a.posicao === "inferior"),
|
||||||
|
)
|
||||||
|
const temAcoesSuperiores = computed(() => acoesSuperiores.value.length > 0)
|
||||||
|
const temAcoesInferiores = computed(() => acoesInferiores.value.length > 0)
|
||||||
|
const parametrosConsultaAtuais = computed(() => {
|
||||||
|
const limite = Math.max(1, registrosPorConsulta.value)
|
||||||
|
const offset = (paginaAtual.value - 1) * limite
|
||||||
|
// biome-ignore lint/suspicious/noExplicitAny: dynamic params
|
||||||
|
const params: parametrosConsulta<any> = { offSet: offset, limit: limite }
|
||||||
|
const busca = (valorBusca.value ?? "").trim()
|
||||||
|
if (busca) {
|
||||||
|
params.texto_busca = busca
|
||||||
|
} else {
|
||||||
|
const filtros = filtrosAvancadosAtivos.value
|
||||||
|
if (filtros.length) params.filtros = filtros
|
||||||
|
}
|
||||||
|
if (colunaOrdenacao.value) {
|
||||||
|
params.coluna_ordem = colunaOrdenacao.value
|
||||||
|
params.direcao_ordem = direcaoOrdenacao.value
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...params,
|
||||||
|
atualizarConsulta: async () => {
|
||||||
|
await carregar()
|
||||||
|
},
|
||||||
|
// biome-ignore lint/suspicious/noExplicitAny: dynamic list update
|
||||||
|
editarLista: async (novaLista: any[]) => {
|
||||||
|
linhas.value = novaLista
|
||||||
|
return novaLista
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
let carregamentoSequencial = 0
|
||||||
|
|
||||||
|
async function carregar() {
|
||||||
|
const idCarregamento = ++carregamentoSequencial
|
||||||
|
carregando.value = true
|
||||||
|
erro.value = null
|
||||||
|
try {
|
||||||
|
acoesVisiveis.value = []
|
||||||
|
linhasExpandidas.value = {}
|
||||||
|
const limite = Math.max(1, registrosPorConsulta.value)
|
||||||
|
const offset = (paginaAtual.value - 1) * limite
|
||||||
|
const parametros: {
|
||||||
|
// biome-ignore lint/suspicious/noExplicitAny: generic
|
||||||
|
filtros?: tipoFiltroDeltaColunaTipada<any>[]
|
||||||
|
coluna_ordem?: never
|
||||||
|
direcao_ordem?: "asc" | "desc"
|
||||||
|
offSet: number
|
||||||
|
limit: number
|
||||||
|
texto_busca?: string
|
||||||
|
} = { offSet: offset, limit: limite }
|
||||||
|
const busca = (valorBusca.value ?? "").trim()
|
||||||
|
if (busca) {
|
||||||
|
parametros.texto_busca = busca
|
||||||
|
} else {
|
||||||
|
const filtros = filtrosAvancadosAtivos.value
|
||||||
|
if (filtros.length) parametros.filtros = filtros
|
||||||
|
}
|
||||||
|
if (colunaOrdenacao.value) {
|
||||||
|
parametros.coluna_ordem = colunaOrdenacao.value as never
|
||||||
|
parametros.direcao_ordem = direcaoOrdenacao.value
|
||||||
|
}
|
||||||
|
if (isDev) console.log("[EliTabela] consulta(parametros)", parametros) // eslint-disable-line no-console
|
||||||
|
const res = await props.tabela.consulta(parametros)
|
||||||
|
if (idCarregamento !== carregamentoSequencial) return
|
||||||
|
if (res.cod !== codigosResposta.sucesso) {
|
||||||
|
linhas.value = []
|
||||||
|
quantidade.value = 0
|
||||||
|
erro.value = res.mensagem
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const valores = res.valor?.valores ?? []
|
||||||
|
const total = res.valor?.quantidade ?? valores.length
|
||||||
|
linhas.value = valores
|
||||||
|
quantidade.value = Number(total) || 0
|
||||||
|
const totalPaginasRecalculado = Math.max(1, Math.ceil((quantidade.value || 0) / limite))
|
||||||
|
if (paginaAtual.value > totalPaginasRecalculado) paginaAtual.value = totalPaginasRecalculado
|
||||||
|
const acoesConf = props.tabela.acoesLinha ?? []
|
||||||
|
if (!acoesConf.length) {
|
||||||
|
acoesVisiveis.value = []
|
||||||
|
return
|
||||||
|
}
|
||||||
|
acoesVisiveis.value = valores.map(() =>
|
||||||
|
acoesConf.map((a) =>
|
||||||
|
a.exibir === undefined ? true : typeof a.exibir === "boolean" ? a.exibir : false,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
const visibilidade = await Promise.all(
|
||||||
|
valores.map((linha) =>
|
||||||
|
Promise.all(
|
||||||
|
acoesConf.map(async (acao) => {
|
||||||
|
if (acao.exibir === undefined) return true
|
||||||
|
if (typeof acao.exibir === "boolean") return acao.exibir
|
||||||
|
try {
|
||||||
|
return Boolean(await Promise.resolve(acao.exibir(linha as never)))
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
if (idCarregamento === carregamentoSequencial) acoesVisiveis.value = visibilidade
|
||||||
|
} catch (e) {
|
||||||
|
console.error("[EliTabela] Erro capturado em carregar():", e)
|
||||||
|
if (idCarregamento !== carregamentoSequencial) return
|
||||||
|
linhas.value = []
|
||||||
|
quantidade.value = 0
|
||||||
|
erro.value = e instanceof Error ? e.message : "Erro ao carregar dados."
|
||||||
|
} finally {
|
||||||
|
if (idCarregamento === carregamentoSequencial) carregando.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function alternarOrdenacao(chave?: string) {
|
||||||
|
if (!chave) return
|
||||||
|
if (colunaOrdenacao.value === chave) {
|
||||||
|
direcaoOrdenacao.value = direcaoOrdenacao.value === "asc" ? "desc" : "asc"
|
||||||
|
void carregar()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
colunaOrdenacao.value = chave
|
||||||
|
direcaoOrdenacao.value = "asc"
|
||||||
|
if (paginaAtual.value !== 1) paginaAtual.value = 1
|
||||||
|
else void carregar()
|
||||||
|
}
|
||||||
|
|
||||||
|
function atualizarBusca(texto: string) {
|
||||||
|
if (valorBusca.value === texto) return
|
||||||
|
valorBusca.value = texto
|
||||||
|
if (paginaAtual.value !== 1) paginaAtual.value = 1
|
||||||
|
else void carregar()
|
||||||
|
}
|
||||||
|
|
||||||
|
function irParaPagina(pagina: number) {
|
||||||
|
const alvo = Math.min(Math.max(1, pagina), totalPaginas.value)
|
||||||
|
if (alvo !== paginaAtual.value) paginaAtual.value = alvo
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(paginaAtual, (nova, antiga) => {
|
||||||
|
if (nova !== antiga) void carregar()
|
||||||
|
})
|
||||||
|
watch(linhas, () => {
|
||||||
|
linhasExpandidas.value = {}
|
||||||
|
})
|
||||||
|
watch(
|
||||||
|
() => props.tabela.mostrarCaixaDeBusca,
|
||||||
|
(mostrar) => {
|
||||||
|
if (!mostrar && valorBusca.value) {
|
||||||
|
valorBusca.value = ""
|
||||||
|
if (paginaAtual.value !== 1) paginaAtual.value = 1
|
||||||
|
else void carregar()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
watch(
|
||||||
|
() => props.tabela.registros_por_consulta,
|
||||||
|
() => {
|
||||||
|
if (paginaAtual.value !== 1) paginaAtual.value = 1
|
||||||
|
else void carregar()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
isDev,
|
||||||
|
tabela,
|
||||||
|
carregando,
|
||||||
|
erro,
|
||||||
|
linhas,
|
||||||
|
linhasPaginadas,
|
||||||
|
quantidade,
|
||||||
|
quantidadeFiltrada,
|
||||||
|
acoesVisiveis,
|
||||||
|
colunaOrdenacao,
|
||||||
|
direcaoOrdenacao,
|
||||||
|
registrosPorConsulta,
|
||||||
|
totalPaginas,
|
||||||
|
temAcoes,
|
||||||
|
exibirBusca,
|
||||||
|
acoesSuperiores,
|
||||||
|
acoesInferiores,
|
||||||
|
temAcoesSuperiores,
|
||||||
|
temAcoesInferiores,
|
||||||
|
parametrosConsultaAtuais,
|
||||||
|
carregar,
|
||||||
|
alternarOrdenacao,
|
||||||
|
atualizarBusca,
|
||||||
|
irParaPagina,
|
||||||
|
}
|
||||||
|
}
|
||||||
128
src/componentes/EliTabela/useEliTabelaFiltrosAvancados.ts
Normal file
128
src/componentes/EliTabela/useEliTabelaFiltrosAvancados.ts
Normal file
|
|
@ -0,0 +1,128 @@
|
||||||
|
/**
|
||||||
|
* useEliTabelaFiltrosAvancados
|
||||||
|
* Gerencia o estado de filtros avançados da EliTabela, incluindo:
|
||||||
|
* - Linhas de filtro ativas (`filtrosUi`) e fórmula booleana (`filtrosFormula`)
|
||||||
|
* - Computed `filtrosAvancadosAtivos` que converte as linhas em FiltroDelta para a API
|
||||||
|
* - Abertura/fechamento e limpeza do modal de filtro avançado
|
||||||
|
*
|
||||||
|
* A fórmula é armazenada no formato `{chave}` (ex: `{email} E {nome}`) e convertida
|
||||||
|
* para IDs globais `{c1} {c2}` durante a montagem do FiltroDelta final.
|
||||||
|
*
|
||||||
|
* @param props - Props do componente (tabela.filtroAvancado, tabela.nome)
|
||||||
|
* @param paginaAtual - Ref da página atual (para resetar ao aplicar filtros)
|
||||||
|
* @param valorBusca - Ref da busca textual (limpa ao aplicar filtros avançados)
|
||||||
|
* @param carregar - Função de recarregamento dos dados
|
||||||
|
*/
|
||||||
|
import type { tipoCondicaoFiltroDelta, tipoFiltroDeltaColunaTipada } from "p-comuns"
|
||||||
|
import { computed, type Ref, ref } from "vue"
|
||||||
|
import {
|
||||||
|
carregarFiltroAvancado,
|
||||||
|
limparFiltroAvancado,
|
||||||
|
salvarFiltroAvancado,
|
||||||
|
} from "./filtroAvancadoStorage"
|
||||||
|
|
||||||
|
// biome-ignore lint/suspicious/noExplicitAny: dinâmico — genérico passado por prop
|
||||||
|
type PropsFiltros = { tabela: { filtroAvancado?: any[]; nome: string } }
|
||||||
|
|
||||||
|
type LinhaFiltroUI = { chave: string; valor: unknown }
|
||||||
|
|
||||||
|
export function useEliTabelaFiltrosAvancados(
|
||||||
|
props: PropsFiltros,
|
||||||
|
paginaAtual: Ref<number>,
|
||||||
|
valorBusca: Ref<string>,
|
||||||
|
carregar: () => Promise<void>,
|
||||||
|
) {
|
||||||
|
const _filtrosCarregados = carregarFiltroAvancado<Record<string, unknown>>(props.tabela.nome)
|
||||||
|
const filtrosUi = ref<LinhaFiltroUI[]>(_filtrosCarregados.linhas)
|
||||||
|
const filtrosFormula = ref<string>(_filtrosCarregados.formula ?? "")
|
||||||
|
const modalFiltroAberto = ref(false)
|
||||||
|
|
||||||
|
const exibirFiltroAvancado = computed(() => (props.tabela.filtroAvancado ?? []).length > 0)
|
||||||
|
|
||||||
|
// biome-ignore lint/suspicious/noExplicitAny: generic
|
||||||
|
const filtrosAvancadosAtivos = computed<tipoFiltroDeltaColunaTipada<any>[]>(() => {
|
||||||
|
const base = props.tabela.filtroAvancado ?? []
|
||||||
|
const ativos = (filtrosUi.value ?? []).filter((f) => f && f.chave !== undefined)
|
||||||
|
if (ativos.length === 0) return []
|
||||||
|
|
||||||
|
const filtrosIndividuais = ativos
|
||||||
|
.map((f) => {
|
||||||
|
const b = base.find((x) => String(x.chave) === String(f.chave))
|
||||||
|
if (!b) return null
|
||||||
|
return { chave: f.chave, filtro: b.filtro(f.valor) }
|
||||||
|
})
|
||||||
|
.filter(
|
||||||
|
(f): f is { chave: string; filtro: tipoFiltroDeltaColunaTipada<string> } =>
|
||||||
|
f !== null && f.filtro !== undefined && f.filtro !== null,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (filtrosFormula.value && filtrosIndividuais.length > 1) {
|
||||||
|
const condicoesFinais: tipoCondicaoFiltroDelta[] = []
|
||||||
|
let formulaFinal = filtrosFormula.value
|
||||||
|
filtrosIndividuais.forEach(({ chave }, i) => {
|
||||||
|
formulaFinal = formulaFinal.replace(new RegExp(`\\{${chave}\\}`, "g"), `{c${i + 1}}`)
|
||||||
|
})
|
||||||
|
let globalIdx = 1
|
||||||
|
for (let i = 0; i < filtrosIndividuais.length; i++) {
|
||||||
|
const { filtro } = filtrosIndividuais[i]
|
||||||
|
const idMap: Record<string, string> = {}
|
||||||
|
for (const cond of filtro.condicoes) {
|
||||||
|
const novoId = `{c${globalIdx++}}`
|
||||||
|
idMap[cond.id] = novoId
|
||||||
|
condicoesFinais.push({ ...cond, id: novoId })
|
||||||
|
}
|
||||||
|
let formulaLocal = filtro.formula
|
||||||
|
for (const [oldId, newId] of Object.entries(idMap)) {
|
||||||
|
formulaLocal = formulaLocal.replace(new RegExp(`\\{${oldId.slice(1, -1)}\\}`, "g"), newId)
|
||||||
|
}
|
||||||
|
const subst = filtro.condicoes.length > 1 ? `(${formulaLocal})` : formulaLocal
|
||||||
|
formulaFinal = formulaFinal.replace(new RegExp(`\\{c${i + 1}\\}`, "g"), subst)
|
||||||
|
}
|
||||||
|
return [{ condicoes: condicoesFinais, formula: formulaFinal }]
|
||||||
|
}
|
||||||
|
return filtrosIndividuais.map((f) => f.filtro)
|
||||||
|
})
|
||||||
|
|
||||||
|
function abrirModalFiltro() {
|
||||||
|
modalFiltroAberto.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function fecharModalFiltro() {
|
||||||
|
modalFiltroAberto.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function limparFiltrosAvancados() {
|
||||||
|
filtrosUi.value = []
|
||||||
|
filtrosFormula.value = ""
|
||||||
|
limparFiltroAvancado(props.tabela.nome)
|
||||||
|
modalFiltroAberto.value = false
|
||||||
|
valorBusca.value = ""
|
||||||
|
if (paginaAtual.value !== 1) paginaAtual.value = 1
|
||||||
|
else void carregar()
|
||||||
|
}
|
||||||
|
|
||||||
|
function salvarFiltrosAvancados(novoLinhas: LinhaFiltroUI[], novaFormula?: string) {
|
||||||
|
filtrosUi.value = novoLinhas ?? []
|
||||||
|
filtrosFormula.value = novaFormula ?? ""
|
||||||
|
salvarFiltroAvancado<Record<string, unknown>>(props.tabela.nome, {
|
||||||
|
linhas: filtrosUi.value,
|
||||||
|
formula: filtrosFormula.value,
|
||||||
|
})
|
||||||
|
modalFiltroAberto.value = false
|
||||||
|
valorBusca.value = ""
|
||||||
|
if (paginaAtual.value !== 1) paginaAtual.value = 1
|
||||||
|
else void carregar()
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
filtrosUi,
|
||||||
|
filtrosFormula,
|
||||||
|
modalFiltroAberto,
|
||||||
|
exibirFiltroAvancado,
|
||||||
|
filtrosAvancadosAtivos,
|
||||||
|
abrirModalFiltro,
|
||||||
|
fecharModalFiltro,
|
||||||
|
limparFiltrosAvancados,
|
||||||
|
salvarFiltrosAvancados,
|
||||||
|
}
|
||||||
|
}
|
||||||
91
src/componentes/EliTabela/useEliTabelaMenu.ts
Normal file
91
src/componentes/EliTabela/useEliTabelaMenu.ts
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
/**
|
||||||
|
* useEliTabelaMenu
|
||||||
|
* Gerencia o menu de ações por linha da EliTabela.
|
||||||
|
* Controla abertura/fechamento, posicionamento do popup e detecção de clique externo.
|
||||||
|
*
|
||||||
|
* @param props - Props do componente (tabela.acoesLinha)
|
||||||
|
* @param acoesVisiveis - Ref com matriz de visibilidade das ações por linha
|
||||||
|
*/
|
||||||
|
import { nextTick, onBeforeUnmount, onMounted, type Ref, ref } from "vue"
|
||||||
|
import type { tipoEliTabelaAcao } from "./types-eli-tabela"
|
||||||
|
|
||||||
|
// biome-ignore lint/suspicious/noExplicitAny: genérico de linha passado por prop
|
||||||
|
type PropsMenu = { tabela: { acoesLinha?: tipoEliTabelaAcao<any>[] } }
|
||||||
|
|
||||||
|
export function useEliTabelaMenu(props: PropsMenu, acoesVisiveis: Ref<boolean[][]>) {
|
||||||
|
const menuAberto = ref<number | null>(null)
|
||||||
|
const menuPopup = ref<{ menuEl: HTMLElement | null } | null>(null)
|
||||||
|
const menuPopupPos = ref({ top: 0, left: 0 })
|
||||||
|
|
||||||
|
function atualizarPosicaoMenu(anchor: HTMLElement) {
|
||||||
|
const rect = anchor.getBoundingClientRect()
|
||||||
|
const gap = 8
|
||||||
|
const alturaMenu = menuPopup.value?.menuEl?.offsetHeight ?? 0
|
||||||
|
const larguraMenu = menuPopup.value?.menuEl?.offsetWidth ?? 180
|
||||||
|
let top = rect.bottom + gap
|
||||||
|
const left = rect.right - larguraMenu
|
||||||
|
if (alturaMenu && top + alturaMenu > window.innerHeight - gap) {
|
||||||
|
top = rect.top - gap - alturaMenu
|
||||||
|
}
|
||||||
|
menuPopupPos.value = {
|
||||||
|
top: Math.max(gap, Math.round(top)),
|
||||||
|
left: Math.max(gap, Math.round(left)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleClickFora(evento: MouseEvent) {
|
||||||
|
if (menuAberto.value === null) return
|
||||||
|
const alvo = evento.target as Node
|
||||||
|
if (menuPopup.value?.menuEl?.contains(alvo)) return
|
||||||
|
if (import.meta.env.DEV) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log("[EliTabela] click fora => fechar menu", { menuAberto: menuAberto.value })
|
||||||
|
}
|
||||||
|
menuAberto.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
function acoesDisponiveisPorLinha(i: number) {
|
||||||
|
const acoesLinha = props.tabela.acoesLinha ?? []
|
||||||
|
const visibilidade = acoesVisiveis.value[i] ?? []
|
||||||
|
return acoesLinha
|
||||||
|
.map((acao, indice) => {
|
||||||
|
const fallbackVisivel =
|
||||||
|
acao.exibir === undefined ? true : typeof acao.exibir === "boolean" ? acao.exibir : false
|
||||||
|
return { acao, indice, visivel: visibilidade[indice] ?? fallbackVisivel }
|
||||||
|
})
|
||||||
|
.filter((item) => item.visivel)
|
||||||
|
}
|
||||||
|
|
||||||
|
function possuiAcoes(i: number) {
|
||||||
|
return acoesDisponiveisPorLinha(i).length > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleMenu(i: number, evento?: MouseEvent) {
|
||||||
|
if (!possuiAcoes(i)) return
|
||||||
|
if (import.meta.env.DEV) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log("[EliTabela] toggleMenu (antes)", { i, atual: menuAberto.value })
|
||||||
|
}
|
||||||
|
if (menuAberto.value === i) {
|
||||||
|
menuAberto.value = null
|
||||||
|
return
|
||||||
|
}
|
||||||
|
menuAberto.value = i
|
||||||
|
if (import.meta.env.DEV) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log("[EliTabela] toggleMenu => abriu", { i })
|
||||||
|
}
|
||||||
|
const anchor = (evento?.currentTarget as HTMLElement | null) ?? null
|
||||||
|
if (anchor) {
|
||||||
|
atualizarPosicaoMenu(anchor)
|
||||||
|
nextTick(() => {
|
||||||
|
if (menuAberto.value !== null) atualizarPosicaoMenu(anchor)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => document.addEventListener("click", handleClickFora))
|
||||||
|
onBeforeUnmount(() => document.removeEventListener("click", handleClickFora))
|
||||||
|
|
||||||
|
return { menuAberto, menuPopup, menuPopupPos, possuiAcoes, toggleMenu, acoesDisponiveisPorLinha }
|
||||||
|
}
|
||||||
96
src/componentes/EliTabela/useEliTabelaSelecao.ts
Normal file
96
src/componentes/EliTabela/useEliTabelaSelecao.ts
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
/**
|
||||||
|
* useEliTabelaSelecao
|
||||||
|
* Gerencia a seleção múltipla de linhas na EliTabela.
|
||||||
|
* Suporta seleção por objeto completo ou por chave configurável (`chave_selecao`).
|
||||||
|
*
|
||||||
|
* @param props - Props do componente (selecionados, tabela.chave_selecao, tabela.mostrar_selecao)
|
||||||
|
* @param emit - Função de emit para `update:selecionados`
|
||||||
|
* @param linhasPaginadas - Ref com as linhas da página atual (para todosSelecionados/algunsSelecionados)
|
||||||
|
*/
|
||||||
|
import { computed, type Ref } from "vue"
|
||||||
|
|
||||||
|
type PropsSelecao = {
|
||||||
|
selecionados: unknown[]
|
||||||
|
tabela: { mostrar_selecao?: boolean; chave_selecao?: string | number | symbol }
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useEliTabelaSelecao(
|
||||||
|
props: PropsSelecao,
|
||||||
|
emit: (event: "update:selecionados", value: unknown[]) => void,
|
||||||
|
linhasPaginadas: Ref<unknown[]>,
|
||||||
|
) {
|
||||||
|
const mostrarSelecao = computed(() => Boolean(props.tabela.mostrar_selecao))
|
||||||
|
const chaveSelecao = computed(() => props.tabela.chave_selecao as string | undefined)
|
||||||
|
|
||||||
|
function obterChaveLinha(linha: unknown): unknown {
|
||||||
|
if (chaveSelecao.value && linha && typeof linha === "object") {
|
||||||
|
return (linha as Record<string, unknown>)[chaveSelecao.value]
|
||||||
|
}
|
||||||
|
return linha
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSelecionado(linha: unknown): boolean {
|
||||||
|
const chave = obterChaveLinha(linha)
|
||||||
|
if (chaveSelecao.value) {
|
||||||
|
return props.selecionados.some((s) => obterChaveLinha(s) === chave)
|
||||||
|
}
|
||||||
|
return props.selecionados.includes(linha)
|
||||||
|
}
|
||||||
|
|
||||||
|
function alternarSelecao(linha: unknown) {
|
||||||
|
const atual = [...props.selecionados]
|
||||||
|
const chave = obterChaveLinha(linha)
|
||||||
|
|
||||||
|
let index = -1
|
||||||
|
if (chaveSelecao.value) {
|
||||||
|
index = atual.findIndex((s) => obterChaveLinha(s) === chave)
|
||||||
|
} else {
|
||||||
|
index = atual.indexOf(linha)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index === -1) {
|
||||||
|
atual.push(linha)
|
||||||
|
} else {
|
||||||
|
atual.splice(index, 1)
|
||||||
|
}
|
||||||
|
emit("update:selecionados", atual)
|
||||||
|
}
|
||||||
|
|
||||||
|
const todosSelecionados = computed(() => {
|
||||||
|
if (linhasPaginadas.value.length === 0) return false
|
||||||
|
return linhasPaginadas.value.every((linha) => isSelecionado(linha))
|
||||||
|
})
|
||||||
|
|
||||||
|
const algunsSelecionados = computed(() => {
|
||||||
|
if (linhasPaginadas.value.length === 0) return false
|
||||||
|
return linhasPaginadas.value.some((linha) => isSelecionado(linha)) && !todosSelecionados.value
|
||||||
|
})
|
||||||
|
|
||||||
|
function alternarSelecaoTodos(forcarMarcar?: boolean) {
|
||||||
|
const marcarTodos = forcarMarcar ?? !todosSelecionados.value
|
||||||
|
let atual = [...props.selecionados]
|
||||||
|
|
||||||
|
if (marcarTodos) {
|
||||||
|
for (const linha of linhasPaginadas.value) {
|
||||||
|
if (!isSelecionado(linha)) atual.push(linha)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const chavesPagina = linhasPaginadas.value.map(obterChaveLinha)
|
||||||
|
if (chaveSelecao.value) {
|
||||||
|
atual = atual.filter((s) => !chavesPagina.includes(obterChaveLinha(s)))
|
||||||
|
} else {
|
||||||
|
atual = atual.filter((s) => !chavesPagina.includes(s))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
emit("update:selecionados", atual)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
mostrarSelecao,
|
||||||
|
isSelecionado,
|
||||||
|
alternarSelecao,
|
||||||
|
todosSelecionados,
|
||||||
|
algunsSelecionados,
|
||||||
|
alternarSelecaoTodos,
|
||||||
|
}
|
||||||
|
}
|
||||||
211
src/componentes/EliTabela/useFiltroAvancado.ts
Normal file
211
src/componentes/EliTabela/useFiltroAvancado.ts
Normal file
|
|
@ -0,0 +1,211 @@
|
||||||
|
/**
|
||||||
|
* useFiltroAvancado
|
||||||
|
* Composable que encapsula a lógica do modal de filtro avançado da EliTabela.
|
||||||
|
*
|
||||||
|
* Responsável por:
|
||||||
|
* - Gerenciar a lista de linhas de filtro ativas
|
||||||
|
* - Converter fórmulas booleanas entre notação de exibição (`{c1}`) e persistência (`{chave}`)
|
||||||
|
* - Normalizar filtros carregados do localStorage (compatibilidade com formatos legados)
|
||||||
|
* - Resolver o componente de entrada correto por tipo (`texto`, `numero`, `dataHora`)
|
||||||
|
*
|
||||||
|
* Extraído de `EliTabelaModalFiltroAvancado.vue` para manter o componente abaixo de 250 linhas.
|
||||||
|
*/
|
||||||
|
import { computed, type Ref, ref, watch } from "vue"
|
||||||
|
import { EliEntradaDataHora, EliEntradaNumero, EliEntradaTexto } from "../EliEntrada"
|
||||||
|
import type { ComponenteEntrada } from "../EliEntrada/tiposEntradas"
|
||||||
|
import type { tipoEliTabelaConsulta } from "./types-eli-tabela"
|
||||||
|
|
||||||
|
export type FiltroBase<T> = NonNullable<tipoEliTabelaConsulta<T>["filtroAvancado"]>[number]
|
||||||
|
|
||||||
|
export type LinhaFiltro = {
|
||||||
|
chave: string
|
||||||
|
entrada: ComponenteEntrada
|
||||||
|
/** Valor do campo de filtro — unknown intencionalmente (vem de localStorage ou entrada do usuário). */
|
||||||
|
valor: unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retorna o rótulo de exibição de uma definição de filtro.
|
||||||
|
* Utilizado no template do modal para exibir o nome do campo ao usuário.
|
||||||
|
*
|
||||||
|
* @param f - Definição de filtro da configuração da tabela
|
||||||
|
* @returns Rótulo textual do filtro
|
||||||
|
*/
|
||||||
|
export function rotuloDoFiltro(f: FiltroBase<string>) {
|
||||||
|
return f.rotulo
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UseFiltroAvancadoOptions {
|
||||||
|
aberto: Ref<boolean>
|
||||||
|
filtrosBase: Ref<FiltroBase<string>[]>
|
||||||
|
modelo: Ref<Array<{ chave: string; valor: unknown }>>
|
||||||
|
modeloFormula: Ref<string>
|
||||||
|
emit: {
|
||||||
|
(event: "fechar"): void
|
||||||
|
(event: "limpar"): void
|
||||||
|
(event: "salvar", linhas: Array<{ chave: string; valor: unknown }>, formula?: string): void
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Composable principal do modal de filtro avançado da EliTabela.
|
||||||
|
* Sincroniza automaticamente quando o modal abre ou o modelo/filtrosBase mudam.
|
||||||
|
*
|
||||||
|
* @param options.aberto - Ref que controla se o modal está aberto
|
||||||
|
* @param options.filtrosBase - Ref com as definições de filtros disponíveis na tabela
|
||||||
|
* @param options.modelo - Ref com as linhas de filtro persistidas (vindo do pai)
|
||||||
|
* @param options.modeloFormula - Ref com a fórmula booleana persistida no pai
|
||||||
|
* @param options.emit - Função de emit do componente Vue pai
|
||||||
|
* @returns Estado reativo e ações do modal de filtro
|
||||||
|
*/
|
||||||
|
export function useFiltroAvancado({
|
||||||
|
aberto,
|
||||||
|
filtrosBase,
|
||||||
|
modelo,
|
||||||
|
modeloFormula,
|
||||||
|
emit,
|
||||||
|
}: UseFiltroAvancadoOptions) {
|
||||||
|
const linhas = ref<LinhaFiltro[]>([])
|
||||||
|
const chaveParaAdicionar = ref<string>("")
|
||||||
|
const formulaCustomizada = ref("")
|
||||||
|
|
||||||
|
const formulaGerada = computed(() => linhas.value.map((_, i) => `{c${i + 1}}`).join(" E "))
|
||||||
|
|
||||||
|
function formulaParaExibicao(formula: string): string {
|
||||||
|
let resultado = formula
|
||||||
|
linhas.value.forEach((l, i) => {
|
||||||
|
resultado = resultado.replace(new RegExp(`\\{${l.chave}\\}`, "g"), `{c${i + 1}}`)
|
||||||
|
})
|
||||||
|
return resultado
|
||||||
|
}
|
||||||
|
|
||||||
|
function formulaParaPersistencia(formula: string): string {
|
||||||
|
let resultado = formula
|
||||||
|
for (let i = linhas.value.length; i >= 1; i--) {
|
||||||
|
const chave = linhas.value[i - 1]?.chave
|
||||||
|
if (chave) {
|
||||||
|
resultado = resultado.replace(new RegExp(`\\{c${i}\\}`, "g"), `{${chave}}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resultado
|
||||||
|
}
|
||||||
|
|
||||||
|
const chavesDisponiveis = computed(() => (filtrosBase.value ?? []).map((b) => String(b.chave)))
|
||||||
|
|
||||||
|
const opcoesParaAdicionar = computed(() => {
|
||||||
|
const usadas = new Set(linhas.value.map((l) => String(l.chave)))
|
||||||
|
return (filtrosBase.value ?? []).filter((b) => !usadas.has(String(b.chave)))
|
||||||
|
})
|
||||||
|
|
||||||
|
function componenteEntrada(entrada: ComponenteEntrada) {
|
||||||
|
const tipo = entrada?.[0]
|
||||||
|
if (tipo === "numero") return EliEntradaNumero
|
||||||
|
if (tipo === "dataHora") return EliEntradaDataHora
|
||||||
|
return EliEntradaTexto
|
||||||
|
}
|
||||||
|
|
||||||
|
function opcoesEntrada(entrada: ComponenteEntrada) {
|
||||||
|
const opcoes: Record<string, unknown> =
|
||||||
|
typeof entrada?.[1] === "object" && entrada[1] !== null
|
||||||
|
? { ...(entrada[1] as Record<string, unknown>) }
|
||||||
|
: {}
|
||||||
|
return { ...opcoes, rotulo: "" }
|
||||||
|
}
|
||||||
|
|
||||||
|
function valorInicialPorEntrada(entrada: ComponenteEntrada) {
|
||||||
|
if (entrada?.[0] === "numero") return null
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizarModelo() {
|
||||||
|
const base = filtrosBase.value ?? []
|
||||||
|
const modeloArr = Array.isArray(modelo.value) ? modelo.value : []
|
||||||
|
linhas.value = modeloArr
|
||||||
|
.map((m: unknown) => {
|
||||||
|
if (!m || typeof m !== "object") return null
|
||||||
|
const item = m as Record<string, unknown>
|
||||||
|
const baseItem = base.find((b) => String(b.chave) === String(item["chave"])) ?? base[0]
|
||||||
|
if (!baseItem) return null
|
||||||
|
const entrada = (baseItem.entrada ?? item["entrada"]) as ComponenteEntrada
|
||||||
|
const chave = String(baseItem.chave ?? item["chave"])
|
||||||
|
const val = item["valor"] ?? valorInicialPorEntrada(entrada)
|
||||||
|
return { chave, entrada, valor: val } as LinhaFiltro
|
||||||
|
})
|
||||||
|
.filter(Boolean) as LinhaFiltro[]
|
||||||
|
|
||||||
|
linhas.value = linhas.value.filter((l) => chavesDisponiveis.value.includes(String(l.chave)))
|
||||||
|
|
||||||
|
if (modeloFormula.value) {
|
||||||
|
formulaCustomizada.value = formulaParaExibicao(modeloFormula.value)
|
||||||
|
} else {
|
||||||
|
formulaCustomizada.value = formulaGerada.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => [aberto.value, filtrosBase.value, modelo.value] as const,
|
||||||
|
() => {
|
||||||
|
if (aberto.value) normalizarModelo()
|
||||||
|
},
|
||||||
|
{ deep: true, immediate: true },
|
||||||
|
)
|
||||||
|
|
||||||
|
function adicionar() {
|
||||||
|
if (!chaveParaAdicionar.value) return
|
||||||
|
const b0 = (filtrosBase.value ?? []).find(
|
||||||
|
(b) => String(b.chave) === String(chaveParaAdicionar.value),
|
||||||
|
)
|
||||||
|
if (!b0) return
|
||||||
|
if (linhas.value.some((l) => String(l.chave) === String(b0.chave))) return
|
||||||
|
linhas.value.push({
|
||||||
|
chave: String(b0.chave),
|
||||||
|
entrada: b0.entrada,
|
||||||
|
valor: valorInicialPorEntrada(b0.entrada),
|
||||||
|
})
|
||||||
|
formulaCustomizada.value = formulaGerada.value
|
||||||
|
chaveParaAdicionar.value = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
function remover(idx: number) {
|
||||||
|
linhas.value.splice(idx, 1)
|
||||||
|
formulaCustomizada.value = formulaGerada.value
|
||||||
|
}
|
||||||
|
|
||||||
|
function emitFechar() {
|
||||||
|
emit("fechar")
|
||||||
|
}
|
||||||
|
function emitLimpar() {
|
||||||
|
emit("limpar")
|
||||||
|
}
|
||||||
|
|
||||||
|
function emitSalvar() {
|
||||||
|
const formulaPersistida = formulaParaPersistencia(formulaCustomizada.value)
|
||||||
|
emit(
|
||||||
|
"salvar",
|
||||||
|
linhas.value.map((l) => ({ chave: l.chave, valor: l.valor })),
|
||||||
|
formulaPersistida,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function rotuloDoFiltroAtivo(chave: string): string {
|
||||||
|
const base = (filtrosBase.value ?? []).find((b) => String(b.chave) === chave)
|
||||||
|
return base ? rotuloDoFiltro(base) : chave
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
linhas,
|
||||||
|
opcoesParaAdicionar,
|
||||||
|
chaveParaAdicionar,
|
||||||
|
formulaCustomizada,
|
||||||
|
formulaGerada,
|
||||||
|
componenteEntrada,
|
||||||
|
opcoesEntrada,
|
||||||
|
adicionar,
|
||||||
|
remover,
|
||||||
|
emitFechar,
|
||||||
|
emitSalvar,
|
||||||
|
emitLimpar,
|
||||||
|
rotuloDoFiltro,
|
||||||
|
rotuloDoFiltroAtivo,
|
||||||
|
}
|
||||||
|
}
|
||||||
46
src/design-tokens.css
Normal file
46
src/design-tokens.css
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
/**
|
||||||
|
* Design Tokens — Paleta Institucional Azteca
|
||||||
|
*
|
||||||
|
* Arquivo centralizado de variáveis CSS usado por todos os componentes Eli.
|
||||||
|
* Importado no ponto de entrada do projeto (main.ts ou App.vue).
|
||||||
|
*
|
||||||
|
* Escala primária: Verde floresta (#688C00)
|
||||||
|
* Escala de apoio: Âmbar terroso
|
||||||
|
* Escala neutra: Cinza musgo
|
||||||
|
*/
|
||||||
|
|
||||||
|
:root {
|
||||||
|
/* ── Escala primária — Verde floresta ── */
|
||||||
|
--cor-eli-primaria-bg: #EEF3D0;
|
||||||
|
--cor-eli-primaria-zebra: #F6F9E8;
|
||||||
|
--cor-eli-primaria-suave: #BFC99C;
|
||||||
|
--cor-eli-primaria-header: #ADCC44;
|
||||||
|
--cor-eli-primaria-clara: #D4E290;
|
||||||
|
--cor-eli-primaria-media: #ADCC44;
|
||||||
|
--cor-eli-primaria-acao: #8AAF00;
|
||||||
|
--cor-eli-primaria: #688C00;
|
||||||
|
--cor-eli-primaria-escura: #4A6500;
|
||||||
|
--cor-eli-primaria-texto: #2E3F00;
|
||||||
|
|
||||||
|
/* ── Escala de apoio — Âmbar terroso ── */
|
||||||
|
--cor-eli-alerta-bg: #FAF3DF;
|
||||||
|
--cor-eli-alerta-clara: #F5DC8A;
|
||||||
|
--cor-eli-alerta: #DDB847;
|
||||||
|
--cor-eli-alerta-forte: #8C6A00;
|
||||||
|
|
||||||
|
/* ── Escala neutra — Cinza musgo ── */
|
||||||
|
--cor-eli-fundo: #F2F3EE;
|
||||||
|
--cor-eli-borda: #D5D8CC;
|
||||||
|
--cor-eli-borda-forte: #B5BAA8;
|
||||||
|
--cor-eli-neutro: #5E6553;
|
||||||
|
--cor-eli-neutro-escuro: #3E4436;
|
||||||
|
--cor-eli-superficie: #FFFFFF;
|
||||||
|
|
||||||
|
/* ── Semânticas ── */
|
||||||
|
--cor-eli-sucesso: #2E7D32;
|
||||||
|
--cor-eli-sucesso-bg: #E8F5E9;
|
||||||
|
--cor-eli-erro: #C62828;
|
||||||
|
--cor-eli-erro-bg: #FFEBEE;
|
||||||
|
--cor-eli-info: #1565C0;
|
||||||
|
--cor-eli-info-bg: #E3F2FD;
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import type { App, Plugin } from "vue"
|
import type { App, Plugin } from "vue"
|
||||||
import "./styles/eli-vue-fonts.css"
|
import "./styles/eli-vue-fonts.css"
|
||||||
|
import "./design-tokens.css"
|
||||||
import { EliBotao } from "./componentes/botao"
|
import { EliBotao } from "./componentes/botao"
|
||||||
import { EliCartao } from "./componentes/cartao"
|
import { EliCartao } from "./componentes/cartao"
|
||||||
import {
|
import {
|
||||||
|
|
@ -13,7 +14,7 @@ import { EliTabela } from "./componentes/EliTabela"
|
||||||
import { EliBadge } from "./componentes/indicador"
|
import { EliBadge } from "./componentes/indicador"
|
||||||
import { EliOlaMundo } from "./componentes/ola_mundo"
|
import { EliOlaMundo } from "./componentes/ola_mundo"
|
||||||
|
|
||||||
export { criarFiltro26 } from "p-comuns"
|
export { iniciarFiltroDelta } 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"
|
||||||
|
|
|
||||||
|
|
@ -1,33 +1,78 @@
|
||||||
<template>
|
<template>
|
||||||
<v-container class="py-6">
|
<div style="display: flex; flex-direction: row; min-height: 100vh;">
|
||||||
<h1 class="text-h5 mb-4">Playground — eli-vue</h1>
|
<!-- Seção de Componentes -->
|
||||||
|
<div style="flex: 1; min-width: 0; overflow-y: auto;">
|
||||||
|
<v-container class="py-6">
|
||||||
|
<h1 class="text-h5 mb-4">Playground — eli-vue</h1>
|
||||||
|
|
||||||
|
<v-tabs v-model="aba" color="primary" density="comfortable">
|
||||||
|
<v-tab value="botao">Botão</v-tab>
|
||||||
|
<v-tab value="indicador">Indicador</v-tab>
|
||||||
|
<v-tab value="cartao">Cartão</v-tab>
|
||||||
|
<v-tab value="entradas">Entradas</v-tab>
|
||||||
|
<v-tab value="data_hora">Data e hora</v-tab>
|
||||||
|
<v-tab value="tabela">Tabela</v-tab>
|
||||||
|
<v-tab value="ola_mundo">Demo</v-tab>
|
||||||
|
</v-tabs>
|
||||||
|
|
||||||
|
<v-divider class="my-4" />
|
||||||
|
|
||||||
<v-tabs v-model="aba" color="primary" density="comfortable">
|
<BotaoPlayground v-if="aba === 'botao'" />
|
||||||
<v-tab value="botao">Botão</v-tab>
|
<IndicadorPlayground v-else-if="aba === 'indicador'" />
|
||||||
<v-tab value="indicador">Indicador</v-tab>
|
<CartaoPlayground v-else-if="aba === 'cartao'" />
|
||||||
<v-tab value="cartao">Cartão</v-tab>
|
<EntradasPlayground v-else-if="aba === 'entradas'" />
|
||||||
<v-tab value="entradas">Entradas</v-tab>
|
<DataHoraPlayground v-else-if="aba === 'data_hora'" />
|
||||||
<v-tab value="data_hora">Data e hora</v-tab>
|
<TabelaPlayground v-else-if="aba === 'tabela'" />
|
||||||
<v-tab value="tabela">Tabela</v-tab>
|
<OlaMundoPlayground v-else />
|
||||||
<v-tab value="ola_mundo">Demo</v-tab>
|
</v-container>
|
||||||
</v-tabs>
|
</div>
|
||||||
|
|
||||||
<v-divider class="my-4" />
|
<!-- Console Global do Playground -->
|
||||||
|
<div
|
||||||
|
class="playground-console"
|
||||||
|
style="width: 400px; flex-shrink: 0; background: #121212; color: #a3e635; padding: 24px 20px; border-left: 1px solid #333; display: flex; flex-direction: column; font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace; font-size: 12px;"
|
||||||
|
>
|
||||||
|
<div style="display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #333; padding-bottom: 12px; margin-bottom: 16px;">
|
||||||
|
<span style="font-weight: 700; color: #fff; text-transform: uppercase; letter-spacing: 1.5px; font-size: 11px; display: flex; align-items: center; gap: 8px;">
|
||||||
|
Console
|
||||||
|
<div style="width: 6px; height: 6px; border-radius: 50%; background-color: #22c55e; box-shadow: 0 0 8px #22c55e;"></div>
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
@click="logsConsole = []"
|
||||||
|
style="font-size: 10px; cursor: pointer; color: #ef4444; background: rgba(239, 68, 68, 0.1); border: 1px solid rgba(239, 68, 68, 0.2); border-radius: 4px; padding: 2px 8px; font-family: inherit;"
|
||||||
|
>LIMPAR</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<BotaoPlayground v-if="aba === 'botao'" />
|
<div style="flex: 1; overflow-y: auto; display: flex; flex-direction: column; gap: 8px;" class="playground-console-scroll">
|
||||||
<IndicadorPlayground v-else-if="aba === 'indicador'" />
|
<div v-if="logsConsole.length === 0" style="color: #52525b; font-size: 11px;">Nenhum log registrado ainda...</div>
|
||||||
<CartaoPlayground v-else-if="aba === 'cartao'" />
|
<pre
|
||||||
<EntradasPlayground v-else-if="aba === 'entradas'" />
|
v-for="(log, i) in logsConsole"
|
||||||
<DataHoraPlayground v-else-if="aba === 'data_hora'" />
|
:key="i"
|
||||||
<TabelaPlayground v-else-if="aba === 'tabela'" />
|
style="font-size: 11px; white-space: pre-wrap; word-break: break-all; color: #a1a1aa; border-left: 2px solid #52525b; padding-left: 8px; margin: 0;"
|
||||||
<OlaMundoPlayground v-else />
|
>{{ log }}</pre>
|
||||||
</v-container>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.playground-console-scroll::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
.playground-console-scroll::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
.playground-console-scroll::-webkit-scrollbar-thumb {
|
||||||
|
background: #3f3f46;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
.playground-console-scroll::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #52525b;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from "vue"
|
import { defineComponent, provide, ref } from "vue"
|
||||||
import BotaoPlayground from "./botao.playground.vue"
|
import BotaoPlayground from "./botao.playground.vue"
|
||||||
import CartaoPlayground from "./cartao.playground.vue"
|
import CartaoPlayground from "./cartao.playground.vue"
|
||||||
import DataHoraPlayground from "./data_hora.playground.vue"
|
import DataHoraPlayground from "./data_hora.playground.vue"
|
||||||
|
|
@ -65,6 +110,15 @@ const mapaAbaParaHash: Record<AbaPlayground, string> = {
|
||||||
ola_mundo: "demo",
|
ola_mundo: "demo",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Chave de injeção para o console do playground */
|
||||||
|
export const PLAYGROUND_CONSOLE_KEY = "playgroundConsole"
|
||||||
|
|
||||||
|
/** Tipagem do console injetado */
|
||||||
|
export interface PlaygroundConsole {
|
||||||
|
adicionarLog: (acao: string, req: unknown, res: unknown) => void
|
||||||
|
logMensagem: (mensagem: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "App",
|
name: "App",
|
||||||
components: {
|
components: {
|
||||||
|
|
@ -76,43 +130,34 @@ export default defineComponent({
|
||||||
TabelaPlayground,
|
TabelaPlayground,
|
||||||
OlaMundoPlayground,
|
OlaMundoPlayground,
|
||||||
},
|
},
|
||||||
data() {
|
setup() {
|
||||||
|
const logsConsole = ref<string[]>([])
|
||||||
|
|
||||||
|
/** Adiciona um log estruturado ao console global do playground */
|
||||||
|
function adicionarLog(acao: string, req: unknown, res: unknown) {
|
||||||
|
const time = new Date().toLocaleTimeString()
|
||||||
|
logsConsole.value.unshift(
|
||||||
|
`[${time}] 🌐 ${acao}\nReq: ${JSON.stringify(req, null, 2)}\nRes: ${JSON.stringify(res, null, 2)}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Adiciona uma mensagem simples ao console */
|
||||||
|
function logMensagem(mensagem: string) {
|
||||||
|
const time = new Date().toLocaleTimeString()
|
||||||
|
logsConsole.value.unshift(`[${time}] ${mensagem}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const consoleApi: PlaygroundConsole = { adicionarLog, logMensagem }
|
||||||
|
provide(PLAYGROUND_CONSOLE_KEY, consoleApi)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
aba: "botao" as AbaPlayground,
|
logsConsole,
|
||||||
fontVars: {
|
aba: ref<AbaPlayground>("botao"),
|
||||||
eli: "",
|
|
||||||
v: "",
|
|
||||||
body: "",
|
|
||||||
},
|
|
||||||
googleSansCheck: "(checando...)" as string,
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.sincronizarAbaComHash()
|
this.sincronizarAbaComHash()
|
||||||
window.addEventListener("hashchange", this.sincronizarAbaComHash)
|
window.addEventListener("hashchange", this.sincronizarAbaComHash)
|
||||||
|
|
||||||
// Coleta variáveis de fonte e confirma carregamento via FontFaceSet.
|
|
||||||
this.fontVars.eli = getComputedStyle(document.documentElement)
|
|
||||||
.getPropertyValue("--eli-font-family")
|
|
||||||
.trim()
|
|
||||||
this.fontVars.v = getComputedStyle(document.documentElement)
|
|
||||||
.getPropertyValue("--v-font-family")
|
|
||||||
.trim()
|
|
||||||
this.fontVars.body = getComputedStyle(document.body).fontFamily
|
|
||||||
|
|
||||||
// Espera fonts carregarem e então checa se o navegador considera Google Sans disponível
|
|
||||||
// (isso não garante 100% que TODOS pesos/estilos foram baixados, mas confirma que a face existe/foi carregada)
|
|
||||||
document.fonts.ready
|
|
||||||
.then(() => {
|
|
||||||
this.googleSansCheck = String(
|
|
||||||
document.fonts.check('12px "Google Sans"'),
|
|
||||||
)
|
|
||||||
// atualiza body depois do load, pois pode mudar após o font-face carregar
|
|
||||||
this.fontVars.body = getComputedStyle(document.body).fontFamily
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
this.googleSansCheck = "erro"
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
beforeUnmount() {
|
beforeUnmount() {
|
||||||
window.removeEventListener("hashchange", this.sincronizarAbaComHash)
|
window.removeEventListener("hashchange", this.sincronizarAbaComHash)
|
||||||
|
|
@ -129,19 +174,14 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
sincronizarAbaComHash() {
|
sincronizarAbaComHash() {
|
||||||
const hash = this.normalizarHash()
|
const hash = this.normalizarHash()
|
||||||
|
|
||||||
const aba = mapaHashParaAba[hash] ?? "botao"
|
const aba = mapaHashParaAba[hash] ?? "botao"
|
||||||
|
|
||||||
if (this.aba !== aba) {
|
if (this.aba !== aba) {
|
||||||
this.aba = aba
|
this.aba = aba
|
||||||
}
|
}
|
||||||
|
|
||||||
// Se a URL estiver vazia/fora do padrão, normalizamos.
|
|
||||||
this.sincronizarHashComAba()
|
this.sincronizarHashComAba()
|
||||||
},
|
},
|
||||||
sincronizarHashComAba() {
|
sincronizarHashComAba() {
|
||||||
const destino = `#/${mapaAbaParaHash[this.aba]}`
|
const destino = `#/${mapaAbaParaHash[this.aba]}`
|
||||||
|
|
||||||
if (window.location.hash !== destino) {
|
if (window.location.hash !== destino) {
|
||||||
window.location.hash = destino
|
window.location.hash = destino
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,445 +0,0 @@
|
||||||
import { respostaComuns, type tipoResposta } from "p-respostas"
|
|
||||||
import type { tipoFiltro } from "@/componentes/EliTabela/types-eli-tabela"
|
|
||||||
|
|
||||||
export type Linha = {
|
|
||||||
codigo: string
|
|
||||||
empreendedor: string
|
|
||||||
empreendimento: string
|
|
||||||
documento: string
|
|
||||||
email: string
|
|
||||||
telefone: string
|
|
||||||
faturamento: number
|
|
||||||
peso_kg: number
|
|
||||||
criado_em: string
|
|
||||||
atualizado_em: string
|
|
||||||
}
|
|
||||||
|
|
||||||
// == SIMULAÇÃO DE API (CRUD) ==
|
|
||||||
// Dados isolados (simulando um banco de dados no servidor)
|
|
||||||
const _bancoDeDados: Linha[] = [
|
|
||||||
{
|
|
||||||
codigo: String(Math.random()),
|
|
||||||
empreendedor: "Maria Silva",
|
|
||||||
empreendimento: "Doces da Maria",
|
|
||||||
documento: "12.345.678/0001-90",
|
|
||||||
email:
|
|
||||||
"contato.comercial.super.longo@doces-da-maria-exemplo-muito-grande.com.br",
|
|
||||||
telefone: "(11) 91234-5678",
|
|
||||||
faturamento: 12500.5,
|
|
||||||
peso_kg: 12.4,
|
|
||||||
criado_em: "2026-01-09T16:15:00Z",
|
|
||||||
atualizado_em: "2026-01-29T15:30:00-03:00",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
codigo: String(Math.random()),
|
|
||||||
empreendedor: "João Pereira",
|
|
||||||
empreendimento: "Café Peregrino",
|
|
||||||
documento: "98.765.432/0001-12",
|
|
||||||
email: "contato@cafeperegrino.com",
|
|
||||||
telefone: "(11) 93456-7810",
|
|
||||||
faturamento: 8000,
|
|
||||||
peso_kg: 6.2,
|
|
||||||
criado_em: "2026-01-02T10:00:00-03:00",
|
|
||||||
atualizado_em: "2026-01-20T08:10:00-03:00",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
codigo: String(Math.random()),
|
|
||||||
empreendedor: "Ana Costa",
|
|
||||||
empreendimento: "Flor de Sal Gastronomia",
|
|
||||||
documento: "45.678.912/0001-55",
|
|
||||||
email: "ana@flordesal.com",
|
|
||||||
telefone: "(21) 99876-5432",
|
|
||||||
faturamento: 3200.75,
|
|
||||||
peso_kg: 4.8,
|
|
||||||
criado_em: "2025-12-10T09:30:00-03:00",
|
|
||||||
atualizado_em: "2026-01-15T19:20:00-03:00",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
codigo: String(Math.random()),
|
|
||||||
empreendedor: "Rafael Lima",
|
|
||||||
empreendimento: "Tech Agro Solutions",
|
|
||||||
documento: "07.654.321/0001-00",
|
|
||||||
email: "rafael@techagro.com",
|
|
||||||
telefone: "(31) 94567-8899",
|
|
||||||
faturamento: 15999.9,
|
|
||||||
peso_kg: 9.1,
|
|
||||||
criado_em: "2026-01-12T12:00:00-03:00",
|
|
||||||
atualizado_em: "2026-01-28T11:00:00-03:00",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
codigo: String(Math.random()),
|
|
||||||
empreendedor: "Juliana Torres",
|
|
||||||
empreendimento: "Torres Moda Sustentável",
|
|
||||||
documento: "33.210.987/0001-44",
|
|
||||||
email: "juliana@torresmoda.com",
|
|
||||||
telefone: "(71) 97766-5544",
|
|
||||||
faturamento: 4500,
|
|
||||||
peso_kg: 3.3,
|
|
||||||
criado_em: "2026-01-01T00:00:00Z",
|
|
||||||
atualizado_em: "2026-01-10T13:45:00-03:00",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
codigo: String(Math.random()),
|
|
||||||
empreendedor: "Marcos Vieira",
|
|
||||||
empreendimento: "Padaria Pão Quentinho",
|
|
||||||
documento: "21.109.876/0001-32",
|
|
||||||
email: "marcos@paoquentinho.com",
|
|
||||||
telefone: "(48) 99654-3210",
|
|
||||||
faturamento: 2200.4,
|
|
||||||
peso_kg: 15.7,
|
|
||||||
criado_em: "2026-01-05T07:25:00-03:00",
|
|
||||||
atualizado_em: "2026-01-23T20:15:00-03:00",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
codigo: String(Math.random()),
|
|
||||||
empreendedor: "Bianca Rocha",
|
|
||||||
empreendimento: "Studio Beleza em Casa",
|
|
||||||
documento: "54.321.098/0001-21",
|
|
||||||
email: "contato@belezaemcasa.com",
|
|
||||||
telefone: "(85) 98877-1122",
|
|
||||||
faturamento: 990,
|
|
||||||
peso_kg: 1.9,
|
|
||||||
criado_em: "2026-01-03T14:00:00-03:00",
|
|
||||||
atualizado_em: "2026-01-29T09:00:00-03:00",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
codigo: String(Math.random()),
|
|
||||||
empreendedor: "Caio Albuquerque",
|
|
||||||
empreendimento: "Albuquerque Engenharia Verde",
|
|
||||||
documento: "65.432.109/0001-09",
|
|
||||||
email: "caio@engenhariaverde.com",
|
|
||||||
telefone: "(61) 98123-4567",
|
|
||||||
faturamento: 100000,
|
|
||||||
peso_kg: 21.3,
|
|
||||||
criado_em: "2025-11-18T16:10:00-03:00",
|
|
||||||
atualizado_em: "2026-01-25T10:40:00-03:00",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
codigo: String(Math.random()),
|
|
||||||
empreendedor: "Fernanda Almeida",
|
|
||||||
empreendimento: "Faz Bem Produtos Naturais",
|
|
||||||
documento: "87.654.210/0001-98",
|
|
||||||
email: "fernanda@fazbem.com",
|
|
||||||
telefone: "(41) 99777-6655",
|
|
||||||
faturamento: 5600.2,
|
|
||||||
peso_kg: 8.6,
|
|
||||||
criado_em: "2026-01-08T12:12:00-03:00",
|
|
||||||
atualizado_em: "2026-01-21T17:55:00-03:00",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
codigo: String(Math.random()),
|
|
||||||
empreendedor: "Gabriel Martins",
|
|
||||||
empreendimento: "Martins Tech Repair",
|
|
||||||
documento: "19.876.543/0001-76",
|
|
||||||
email: "suporte@martinstech.com",
|
|
||||||
telefone: "(19) 98888-9090",
|
|
||||||
faturamento: 7550,
|
|
||||||
peso_kg: 7.7,
|
|
||||||
criado_em: "2026-01-11T11:00:00-03:00",
|
|
||||||
atualizado_em: "2026-01-18T11:30:00-03:00",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
codigo: String(Math.random()),
|
|
||||||
empreendedor: "Helena Duarte",
|
|
||||||
empreendimento: "Ateliê Costura Criativa",
|
|
||||||
documento: "23.456.789/0001-65",
|
|
||||||
email: "helena@costuracriativa.com",
|
|
||||||
telefone: "(51) 98765-4433",
|
|
||||||
faturamento: 2700,
|
|
||||||
peso_kg: 2.5,
|
|
||||||
criado_em: "2026-01-07T08:00:00-03:00",
|
|
||||||
atualizado_em: "2026-01-16T14:10:00-03:00",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
codigo: String(Math.random()),
|
|
||||||
empreendedor: "Igor Santos",
|
|
||||||
empreendimento: "Santos Consultoria Financeira",
|
|
||||||
documento: "43.219.876/0001-54",
|
|
||||||
email: "igor@santosconsultoria.com",
|
|
||||||
telefone: "(31) 99332-1100",
|
|
||||||
faturamento: 12000,
|
|
||||||
peso_kg: 5.4,
|
|
||||||
criado_em: "2026-01-06T10:30:00-03:00",
|
|
||||||
atualizado_em: "2026-01-22T18:30:00-03:00",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
codigo: String(Math.random()),
|
|
||||||
empreendedor: "Jéssica Nunes",
|
|
||||||
empreendimento: "Nunes Eventos & Decoração",
|
|
||||||
documento: "09.876.543/0001-33",
|
|
||||||
email: "jessica@nunesdecora.com",
|
|
||||||
telefone: "(62) 99922-3344",
|
|
||||||
faturamento: 3300.1,
|
|
||||||
peso_kg: 4.1,
|
|
||||||
criado_em: "2026-01-04T13:30:00-03:00",
|
|
||||||
atualizado_em: "2026-01-26T09:20:00-03:00",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
codigo: String(Math.random()),
|
|
||||||
empreendedor: "Leonardo Prado",
|
|
||||||
empreendimento: "Prado Bike Courier",
|
|
||||||
documento: "72.345.098/0001-27",
|
|
||||||
email: "contato@pradobike.com",
|
|
||||||
telefone: "(47) 98444-6677",
|
|
||||||
faturamento: 4100,
|
|
||||||
peso_kg: 6.8,
|
|
||||||
criado_em: "2026-01-02T18:40:00-03:00",
|
|
||||||
atualizado_em: "2026-01-27T12:00:00-03:00",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
codigo: String(Math.random()),
|
|
||||||
empreendedor: "Marina Lopes",
|
|
||||||
empreendimento: "Lopes Design Digital",
|
|
||||||
documento: "56.789.012/0001-11",
|
|
||||||
email: "marina@lopesdesign.com",
|
|
||||||
telefone: "(27) 99911-2233",
|
|
||||||
faturamento: 2100,
|
|
||||||
peso_kg: 3.7,
|
|
||||||
criado_em: "2026-01-09T09:00:00-03:00",
|
|
||||||
atualizado_em: "2026-01-19T15:35:00-03:00",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
codigo: String(Math.random()),
|
|
||||||
empreendedor: "Nicolas Teixeira",
|
|
||||||
empreendimento: "QualiVida Fitness",
|
|
||||||
documento: "34.567.890/0001-02",
|
|
||||||
email: "nicolas@qualivida.com",
|
|
||||||
telefone: "(92) 99456-7788",
|
|
||||||
faturamento: 8600.9,
|
|
||||||
peso_kg: 11.2,
|
|
||||||
criado_em: "2026-01-12T19:15:00-03:00",
|
|
||||||
atualizado_em: "2026-01-29T10:50:00-03:00",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
codigo: String(Math.random()),
|
|
||||||
empreendedor: "Olívia Azevedo",
|
|
||||||
empreendimento: "Azevedo Pet Care",
|
|
||||||
documento: "88.901.234/0001-45",
|
|
||||||
email: "olivia@petcare.com",
|
|
||||||
telefone: "(16) 99788-6655",
|
|
||||||
faturamento: 1900.3,
|
|
||||||
peso_kg: 2.2,
|
|
||||||
criado_em: "2026-01-10T15:00:00-03:00",
|
|
||||||
atualizado_em: "2026-01-14T16:00:00-03:00",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
codigo: String(Math.random()),
|
|
||||||
empreendedor: "Paulo Henrique",
|
|
||||||
empreendimento: "Henrique Soluções Elétricas",
|
|
||||||
documento: "44.556.778/0001-90",
|
|
||||||
email: "paulo@solucoeseletricas.com",
|
|
||||||
telefone: "(13) 98810-2020",
|
|
||||||
faturamento: 50000,
|
|
||||||
peso_kg: 17.9,
|
|
||||||
criado_em: "2026-01-13T10:10:00-03:00",
|
|
||||||
atualizado_em: "2026-01-24T21:00:00-03:00",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
codigo: String(Math.random()),
|
|
||||||
empreendedor: "Renata Souza",
|
|
||||||
empreendimento: "Souza Traduções Especializadas",
|
|
||||||
documento: "11.223.344/0001-08",
|
|
||||||
email: "renata@souzatraducoes.com",
|
|
||||||
telefone: "(24) 99661-7788",
|
|
||||||
faturamento: 6400,
|
|
||||||
peso_kg: 9.9,
|
|
||||||
criado_em: "2026-01-08T07:00:00-03:00",
|
|
||||||
atualizado_em: "2026-01-28T09:05:00-03:00",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
codigo: String(Math.random()),
|
|
||||||
empreendedor: "Tiago Moura",
|
|
||||||
empreendimento: "Moura Agro Sustentável",
|
|
||||||
documento: "78.912.345/0001-67",
|
|
||||||
email: "tiago@mouraagro.com",
|
|
||||||
telefone: "(68) 99999-4545",
|
|
||||||
faturamento: 7200,
|
|
||||||
peso_kg: 13.5,
|
|
||||||
criado_em: "2026-01-03T12:00:00-03:00",
|
|
||||||
atualizado_em: "2026-01-23T12:45:00-03:00",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
codigo: String(Math.random()),
|
|
||||||
empreendedor: "Viviane Castro",
|
|
||||||
empreendimento: "Castro Arte & Cerâmica",
|
|
||||||
documento: "32.165.498/0001-87",
|
|
||||||
email: "viviane@castroarte.com",
|
|
||||||
telefone: "(81) 98787-1212",
|
|
||||||
faturamento: 2800,
|
|
||||||
peso_kg: 4.6,
|
|
||||||
criado_em: "2026-01-01T09:00:00-03:00",
|
|
||||||
atualizado_em: "2026-01-17T09:30:00-03:00",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
// Helpers internos da API (não expostos)
|
|
||||||
const _filtrarPorBusca = (lista: Linha[], texto?: string) => {
|
|
||||||
const termo = texto?.trim().toLowerCase()
|
|
||||||
if (!termo) return [...lista]
|
|
||||||
return lista.filter((linha) =>
|
|
||||||
[linha.empreendedor, linha.empreendimento].some((v) =>
|
|
||||||
v.toLowerCase().includes(termo),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const _comparar = (
|
|
||||||
op: string,
|
|
||||||
valLine: unknown,
|
|
||||||
valFiltro: unknown,
|
|
||||||
): boolean => {
|
|
||||||
switch (op) {
|
|
||||||
case "=":
|
|
||||||
return valLine == valFiltro
|
|
||||||
case "!=":
|
|
||||||
return valLine != valFiltro
|
|
||||||
case ">":
|
|
||||||
return Number(valLine) > Number(valFiltro)
|
|
||||||
case ">=":
|
|
||||||
return Number(valLine) >= Number(valFiltro)
|
|
||||||
case "<":
|
|
||||||
return Number(valLine) < Number(valFiltro)
|
|
||||||
case "<=":
|
|
||||||
return Number(valLine) <= Number(valFiltro)
|
|
||||||
case "like":
|
|
||||||
return String(valLine ?? "")
|
|
||||||
.toLowerCase()
|
|
||||||
.includes(String(valFiltro ?? "").toLowerCase())
|
|
||||||
case "in": {
|
|
||||||
const arr = Array.isArray(valFiltro)
|
|
||||||
? valFiltro
|
|
||||||
: String(valFiltro ?? "")
|
|
||||||
.split(",")
|
|
||||||
.map((s) => s.trim())
|
|
||||||
.filter(Boolean)
|
|
||||||
return arr.includes(String(valLine))
|
|
||||||
}
|
|
||||||
case "isNull":
|
|
||||||
return valLine === null || valLine === undefined || valLine === ""
|
|
||||||
default:
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const _filtrarAvancado = (lista: Linha[], filtros?: tipoFiltro<Linha>[]) => {
|
|
||||||
if (!filtros?.length) return [...lista]
|
|
||||||
|
|
||||||
return lista.filter((linha) =>
|
|
||||||
filtros.every((f) => {
|
|
||||||
// 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)
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const _ordenar = (
|
|
||||||
lista: Linha[],
|
|
||||||
p?: { coluna_ordem?: keyof Linha; direcao_ordem?: "asc" | "desc" },
|
|
||||||
) => {
|
|
||||||
if (!p?.coluna_ordem) return [...lista]
|
|
||||||
const dir = p.direcao_ordem === "desc" ? -1 : 1
|
|
||||||
const key = p.coluna_ordem
|
|
||||||
return [...lista].sort((a, b) => {
|
|
||||||
const va = String(a[key] ?? "")
|
|
||||||
const vb = String(b[key] ?? "")
|
|
||||||
return dir * va.localeCompare(vb, "pt-BR", { sensitivity: "base" })
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const delay = (ms: number) =>
|
|
||||||
new Promise((r) => setTimeout(r, ms * Math.random() + 2000))
|
|
||||||
|
|
||||||
// -- API PÚBLICA SIMULADA --
|
|
||||||
|
|
||||||
export async function api_ler(params: {
|
|
||||||
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
|
|
||||||
|
|
||||||
// 1. Filtro
|
|
||||||
let resultado = params.texto_busca
|
|
||||||
? _filtrarPorBusca(_bancoDeDados, params.texto_busca)
|
|
||||||
: _filtrarAvancado(_bancoDeDados, params.filtros)
|
|
||||||
|
|
||||||
// 2. Ordenação
|
|
||||||
resultado = _ordenar(resultado, params)
|
|
||||||
|
|
||||||
// 3. Paginação
|
|
||||||
const limit = Math.max(1, Number(params.limit ?? 10))
|
|
||||||
const offset = Math.max(0, Number(params.offSet ?? 0))
|
|
||||||
const paginado = resultado.slice(offset, offset + limit)
|
|
||||||
|
|
||||||
return {
|
|
||||||
total: resultado.length,
|
|
||||||
dados: paginado,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function api_criar(
|
|
||||||
dados: Partial<Linha>,
|
|
||||||
): Promise<tipoResposta<Linha>> {
|
|
||||||
await delay(1500) // simula tempo de salvar
|
|
||||||
|
|
||||||
if (!dados.empreendedor) {
|
|
||||||
return respostaComuns.erro("Empreendedor é obrigatório")
|
|
||||||
}
|
|
||||||
|
|
||||||
const novo: Linha = {
|
|
||||||
codigo: Math.random().toString(),
|
|
||||||
empreendedor: dados.empreendedor || "Novo",
|
|
||||||
empreendimento: dados.empreendimento || "Novo Emp",
|
|
||||||
documento: dados.documento || "00.000.000/0000-00",
|
|
||||||
email: dados.email || "novo@email.com",
|
|
||||||
telefone: dados.telefone || "(00) 0000-0000",
|
|
||||||
faturamento: dados.faturamento || 0,
|
|
||||||
peso_kg: dados.peso_kg || 0,
|
|
||||||
criado_em: new Date().toISOString(),
|
|
||||||
atualizado_em: new Date().toISOString(),
|
|
||||||
}
|
|
||||||
_bancoDeDados.unshift(novo)
|
|
||||||
return respostaComuns.valor(novo)
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function api_editar(
|
|
||||||
codigo: string,
|
|
||||||
novosDados: Partial<Linha>,
|
|
||||||
): Promise<tipoResposta<Linha>> {
|
|
||||||
await delay(1000)
|
|
||||||
const idx = _bancoDeDados.find((l) => l.codigo === codigo)
|
|
||||||
if (!idx) return respostaComuns.erro("Registro não encontrado")
|
|
||||||
Object.assign(idx, novosDados)
|
|
||||||
return respostaComuns.valor(idx)
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function api_deletar(
|
|
||||||
codigo: string,
|
|
||||||
): Promise<tipoResposta<string>> {
|
|
||||||
await delay(800)
|
|
||||||
const idx = _bancoDeDados.findIndex((l) => l.codigo === codigo)
|
|
||||||
if (idx !== -1) {
|
|
||||||
_bancoDeDados.splice(idx, 1)
|
|
||||||
return respostaComuns.valor("Registro deletado com sucesso")
|
|
||||||
}
|
|
||||||
return respostaComuns.erro("Registro não encontrado")
|
|
||||||
}
|
|
||||||
195
src/playground/api_simulador/banco.ts
Normal file
195
src/playground/api_simulador/banco.ts
Normal file
|
|
@ -0,0 +1,195 @@
|
||||||
|
import alasql from "alasql"
|
||||||
|
import type { tipoCondicaoFiltroDelta, zFiltroDelta } from "p-comuns"
|
||||||
|
import type z from "zod"
|
||||||
|
import { _dados_empreendedores, type tipoEmpreendedores } from "./dados"
|
||||||
|
|
||||||
|
const zCondicaoFiltroDeltaParaQuery = ({
|
||||||
|
condicao,
|
||||||
|
}: {
|
||||||
|
condicao: tipoCondicaoFiltroDelta
|
||||||
|
}): { sql: string; parametro: Record<string, unknown> } => {
|
||||||
|
const coluna = condicao.coluna
|
||||||
|
|
||||||
|
const retorno: { sql: string; parametro: Record<string, unknown> } = {
|
||||||
|
sql: "",
|
||||||
|
parametro: {},
|
||||||
|
}
|
||||||
|
|
||||||
|
if (condicao.operador === "!=") {
|
||||||
|
retorno.sql = `${coluna} != ${condicao.id}`
|
||||||
|
retorno.parametro[condicao.id] = condicao.valor
|
||||||
|
} else if (condicao.operador === "=") {
|
||||||
|
retorno.sql = `${coluna} = ${condicao.id}`
|
||||||
|
retorno.parametro[condicao.id] = condicao.valor
|
||||||
|
} else if (condicao.operador === ">") {
|
||||||
|
retorno.sql = `${coluna} > ${condicao.id}`
|
||||||
|
retorno.parametro[condicao.id] = condicao.valor
|
||||||
|
} else if (condicao.operador === "<") {
|
||||||
|
retorno.sql = `${coluna} < ${condicao.id}`
|
||||||
|
retorno.parametro[condicao.id] = condicao.valor
|
||||||
|
} else if (condicao.operador === ">=") {
|
||||||
|
retorno.sql = `${coluna} >= ${condicao.id}`
|
||||||
|
retorno.parametro[condicao.id] = condicao.valor
|
||||||
|
} else if (condicao.operador === "<=") {
|
||||||
|
retorno.sql = `${coluna} <= ${condicao.id}`
|
||||||
|
retorno.parametro[condicao.id] = condicao.valor
|
||||||
|
} else if (condicao.operador === "like") {
|
||||||
|
retorno.sql = `LOWER(${coluna}) LIKE LOWER(${condicao.id})`
|
||||||
|
retorno.parametro[condicao.id] = condicao.valor.includes("%")
|
||||||
|
? condicao.valor
|
||||||
|
: `%${condicao.valor}%`
|
||||||
|
} else if (condicao.operador === "in") {
|
||||||
|
// Alasql usa @(?) para arrays. Porém, o passo 2 substitui {id} por ?.
|
||||||
|
// O que resultará em @(?).
|
||||||
|
retorno.sql = `${coluna} IN @(${condicao.id})`
|
||||||
|
retorno.parametro[condicao.id] = Array.isArray(condicao.valor)
|
||||||
|
? condicao.valor
|
||||||
|
: String(condicao.valor)
|
||||||
|
.split(",")
|
||||||
|
.map((s) => s.trim())
|
||||||
|
} else {
|
||||||
|
throw new Error(`Operador inválido: ${condicao.operador as string}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return retorno
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converte um `FiltroDelta` em um fragmento SQL com parâmetros posicionais para o Alasql.
|
||||||
|
* Realiza dois passes na fórmula:
|
||||||
|
* 1. Substitui cada `{id}` pelo sub-SQL da condição correspondente
|
||||||
|
* 2. Extrai os parâmetros em ordem e troca `{id}` por `?`
|
||||||
|
* Por fim, traduz os operadores lógicos pt-BR (E, OU, NAO) para SQL (AND, OR, NOT).
|
||||||
|
*
|
||||||
|
* @param filtros - Um FiltroDelta (fórmula + condições) ou `undefined` para sem filtro
|
||||||
|
* @returns Fragmento SQL e array de parâmetros posicionais
|
||||||
|
*/
|
||||||
|
export const zFiltroDeltaParaQuery = ({
|
||||||
|
filtros,
|
||||||
|
}: {
|
||||||
|
filtros: z.infer<typeof zFiltroDelta> | undefined
|
||||||
|
}): { sql: string; parametros: unknown[] } => {
|
||||||
|
if (!filtros) return { sql: "", parametros: [] }
|
||||||
|
|
||||||
|
const parametros: unknown[] = []
|
||||||
|
const sqlParametros: Record<string, unknown> = {}
|
||||||
|
|
||||||
|
// 1. Substituir `{id}` pelo sub-sql de cada condição e extrair parâmetros
|
||||||
|
let sql = filtros.formula.replace(/\{[\w:]+\}/g, (match) => {
|
||||||
|
const id = match.slice(1, -1)
|
||||||
|
const condicao = filtros.condicoes.find((c) => c.id === match)
|
||||||
|
|
||||||
|
if (!condicao) return match
|
||||||
|
|
||||||
|
const { sql: sqlCondicao, parametro } = zCondicaoFiltroDeltaParaQuery({ condicao })
|
||||||
|
sqlParametros[id] = parametro[match]
|
||||||
|
|
||||||
|
return ` (${sqlCondicao}) `
|
||||||
|
})
|
||||||
|
|
||||||
|
// 2. Extrair parâmetros na ordem correta, trocando os {id} restantes por ?
|
||||||
|
sql = sql.replace(/\{[\w:]+\}/g, (match) => {
|
||||||
|
const id = match.slice(1, -1)
|
||||||
|
parametros.push(sqlParametros[id])
|
||||||
|
return "?"
|
||||||
|
})
|
||||||
|
|
||||||
|
// 3. Traduzir operadores lógicos do filtroDelta
|
||||||
|
sql = sql
|
||||||
|
.replace(/\bNAO\b/g, " NOT ")
|
||||||
|
.replace(/\bE\b/g, " AND ")
|
||||||
|
.replace(/\bOU\b/g, " OR ")
|
||||||
|
|
||||||
|
return { sql, parametros }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Combina um array de FiltroDelta em uma cláusula WHERE única para o Alasql.
|
||||||
|
* Cada filtro é convertido individualmente e combinado com AND.
|
||||||
|
* Retorna `"1=1"` (sem filtro) quando o array é vazio.
|
||||||
|
*
|
||||||
|
* @param filtrosArray - Lista de FiltroDelta a combinar; `undefined` ou vazio = sem filtro
|
||||||
|
* @returns Fragmento SQL e array de parâmetros posicionais combinados
|
||||||
|
*/
|
||||||
|
export const zFiltrosArrayParaAlasql = (
|
||||||
|
filtrosArray?: z.infer<typeof zFiltroDelta>[],
|
||||||
|
): { sql: string; parametros: unknown[] } => {
|
||||||
|
if (!filtrosArray?.length) return { sql: "1=1", parametros: [] }
|
||||||
|
|
||||||
|
const queryStrings: string[] = []
|
||||||
|
const todosParametros: unknown[] = []
|
||||||
|
|
||||||
|
for (const filtro of filtrosArray) {
|
||||||
|
const { sql, parametros } = zFiltroDeltaParaQuery({ filtros: filtro })
|
||||||
|
if (sql) {
|
||||||
|
queryStrings.push(`(${sql})`)
|
||||||
|
todosParametros.push(...parametros)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (queryStrings.length === 0) return { sql: "1=1", parametros: [] }
|
||||||
|
return { sql: queryStrings.join(" AND "), parametros: todosParametros }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executa uma consulta no banco em memória Alasql com suporte a filtros, ordenação e paginação.
|
||||||
|
* Simula o comportamento de uma API REST real para fins de demonstração no playground.
|
||||||
|
* Os filtros são compilados pela `zFiltroDeltaParaQuery`; a contagem total é obtida antes da paginação.
|
||||||
|
*
|
||||||
|
* @param params.filtros - FiltroDelta opcional (filtros avançados do usuário)
|
||||||
|
* @param params.coluna_ordem - Nome da coluna para ordenar
|
||||||
|
* @param params.direcao_ordem - Direção da ordenação (`"asc"` | `"desc"`)
|
||||||
|
* @param params.limit - Número de registros por página
|
||||||
|
* @param params.offSet - Deslocamento (página × limit)
|
||||||
|
* @returns Objeto `{ total, dados }` com o total geral e a página de registros
|
||||||
|
*/
|
||||||
|
export const executarConsultaAlasql = (params: {
|
||||||
|
filtros?: z.infer<typeof zFiltroDelta>
|
||||||
|
coluna_ordem?: string
|
||||||
|
direcao_ordem?: "asc" | "desc"
|
||||||
|
limit?: number
|
||||||
|
offSet?: number
|
||||||
|
}): { total: number; dados: tipoEmpreendedores[] } => {
|
||||||
|
let whereSql = "1=1"
|
||||||
|
const parametros: unknown[] = []
|
||||||
|
|
||||||
|
// 2. Filtros Avançados
|
||||||
|
if (params.filtros) {
|
||||||
|
const parsed = zFiltroDeltaParaQuery({ filtros: params.filtros })
|
||||||
|
if (parsed.sql && parsed.sql !== "1=1") {
|
||||||
|
whereSql += ` AND (${parsed.sql})`
|
||||||
|
parametros.push(...parsed.parametros)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Montagem da Query base
|
||||||
|
let sql = `SELECT * FROM ? WHERE ${whereSql}`
|
||||||
|
|
||||||
|
// 3. Ordenação
|
||||||
|
if (params.coluna_ordem) {
|
||||||
|
const dir = params.direcao_ordem === "desc" ? "DESC" : "ASC"
|
||||||
|
sql += ` ORDER BY ${params.coluna_ordem} ${dir}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obter Total antes da paginação
|
||||||
|
const sqlCount = `SELECT COUNT(*) as t FROM ? WHERE ${whereSql}`
|
||||||
|
const countResult = alasql(sqlCount, [_dados_empreendedores, ...parametros]) as {
|
||||||
|
t: number
|
||||||
|
}[]
|
||||||
|
const total = countResult[0].t
|
||||||
|
|
||||||
|
// 4. Paginação
|
||||||
|
const limit = Math.max(1, Number(params.limit ?? 10))
|
||||||
|
const offset = Math.max(0, Number(params.offSet ?? 0))
|
||||||
|
sql += ` LIMIT ${limit} OFFSET ${offset}`
|
||||||
|
|
||||||
|
console.groupCollapsed("🔍 [Alasql] Consulta executada")
|
||||||
|
console.log("SQL:", sql)
|
||||||
|
console.log("Parâmetros:", parametros)
|
||||||
|
console.log("Total Encontrado:", total)
|
||||||
|
console.groupEnd()
|
||||||
|
|
||||||
|
const dados = alasql(sql, [_dados_empreendedores, ...parametros]) as tipoEmpreendedores[]
|
||||||
|
|
||||||
|
return { total, dados }
|
||||||
|
}
|
||||||
250
src/playground/api_simulador/dados.ts
Normal file
250
src/playground/api_simulador/dados.ts
Normal file
|
|
@ -0,0 +1,250 @@
|
||||||
|
import z from "zod"
|
||||||
|
|
||||||
|
let _id = 0
|
||||||
|
/** Gera um ID incremental único para uso no seed de dados do playground. Não usar em produção. */
|
||||||
|
export const gerarId = () => `item-${_id++}`
|
||||||
|
|
||||||
|
/** Schema Zod do registro de empreendedor — usado para validação e inferência de tipo no playground. */
|
||||||
|
export const zEmpreendedores = z.object({
|
||||||
|
codigo: z.string(),
|
||||||
|
empreendedor: z.string(),
|
||||||
|
empreendimento: z.string(),
|
||||||
|
documento: z.string(),
|
||||||
|
email: z.string(),
|
||||||
|
telefone: z.string(),
|
||||||
|
faturamento: z.number(),
|
||||||
|
peso_kg: z.number(),
|
||||||
|
areas_atuacao: z.array(
|
||||||
|
z.enum(["mineracao", "agro", "turismo", "tecnologia", "educacao", "saude"]),
|
||||||
|
),
|
||||||
|
criado_em: z.string(),
|
||||||
|
atualizado_em: z.string(),
|
||||||
|
})
|
||||||
|
|
||||||
|
export type tipoEmpreendedores = z.infer<typeof zEmpreendedores>
|
||||||
|
|
||||||
|
// == SIMULAÇÃO DE API (CRUD) ==
|
||||||
|
// Dados isolados (simulando um banco de dados no servidor)
|
||||||
|
export const _dados_empreendedores: tipoEmpreendedores[] = [
|
||||||
|
{
|
||||||
|
codigo: gerarId(),
|
||||||
|
empreendedor: "Maria Silva",
|
||||||
|
empreendimento: "Doces da Maria",
|
||||||
|
documento: "12.345.678/0001-90",
|
||||||
|
email: "contato.comercial.super.longo@doces-da-maria-exemplo-muito-grande.com.br",
|
||||||
|
telefone: "(11) 91234-5678",
|
||||||
|
faturamento: 12500.5,
|
||||||
|
peso_kg: 12.4,
|
||||||
|
areas_atuacao: ["agro", "turismo"],
|
||||||
|
criado_em: "2026-01-09T16:15:00Z",
|
||||||
|
atualizado_em: "2026-01-29T15:30:00-03:00",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
codigo: gerarId(),
|
||||||
|
empreendedor: "João Pereira",
|
||||||
|
empreendimento: "Café Peregrino",
|
||||||
|
documento: "98.765.432/0001-12",
|
||||||
|
email: "contato@cafeperegrino.com",
|
||||||
|
telefone: "(11) 93456-7810",
|
||||||
|
faturamento: 8000,
|
||||||
|
peso_kg: 6.2,
|
||||||
|
areas_atuacao: ["tecnologia", "agro"],
|
||||||
|
criado_em: "2026-01-02T10:00:00-03:00",
|
||||||
|
atualizado_em: "2026-01-20T08:10:00-03:00",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
codigo: gerarId(),
|
||||||
|
empreendedor: "Ana Costa",
|
||||||
|
empreendimento: "Flor de Sal Gastronomia",
|
||||||
|
documento: "45.678.912/0001-55",
|
||||||
|
email: "ana@flordesal.com",
|
||||||
|
telefone: "(21) 99876-5432",
|
||||||
|
faturamento: 3200.75,
|
||||||
|
peso_kg: 4.8,
|
||||||
|
areas_atuacao: ["educacao"],
|
||||||
|
criado_em: "2025-12-10T09:30:00-03:00",
|
||||||
|
atualizado_em: "2026-01-15T19:20:00-03:00",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
codigo: gerarId(),
|
||||||
|
empreendedor: "Rafael Lima",
|
||||||
|
empreendimento: "Tech Agro Solutions",
|
||||||
|
documento: "07.654.321/0001-00",
|
||||||
|
email: "rafael@techagro.com",
|
||||||
|
telefone: "(31) 94567-8899",
|
||||||
|
faturamento: 15999.9,
|
||||||
|
peso_kg: 9.1,
|
||||||
|
areas_atuacao: ["tecnologia"],
|
||||||
|
criado_em: "2026-01-12T12:00:00-03:00",
|
||||||
|
atualizado_em: "2026-01-28T11:00:00-03:00",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
codigo: gerarId(),
|
||||||
|
empreendedor: "Juliana Torres",
|
||||||
|
empreendimento: "Torres Moda Sustentável",
|
||||||
|
documento: "33.210.987/0001-44",
|
||||||
|
email: "juliana@torresmoda.com",
|
||||||
|
telefone: "(71) 97766-5544",
|
||||||
|
faturamento: 4500,
|
||||||
|
peso_kg: 3.3,
|
||||||
|
areas_atuacao: ["turismo", "agro"],
|
||||||
|
criado_em: "2026-01-01T00:00:00Z",
|
||||||
|
atualizado_em: "2026-01-10T13:45:00-03:00",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
codigo: gerarId(),
|
||||||
|
empreendedor: "Marcos Vieira",
|
||||||
|
empreendimento: "Padaria Pão Quentinho",
|
||||||
|
documento: "21.109.876/0001-32",
|
||||||
|
email: "marcos@paoquentinho.com",
|
||||||
|
telefone: "(48) 99654-3210",
|
||||||
|
faturamento: 2200.4,
|
||||||
|
peso_kg: 15.7,
|
||||||
|
areas_atuacao: ["saude"],
|
||||||
|
criado_em: "2026-01-05T07:25:00-03:00",
|
||||||
|
atualizado_em: "2026-01-23T20:15:00-03:00",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
codigo: gerarId(),
|
||||||
|
empreendedor: "Bianca Rocha",
|
||||||
|
empreendimento: "Studio Beleza em Casa",
|
||||||
|
documento: "54.321.098/0001-21",
|
||||||
|
email: "contato@belezaemcasa.com",
|
||||||
|
telefone: "(85) 98877-1122",
|
||||||
|
faturamento: 990,
|
||||||
|
peso_kg: 1.9,
|
||||||
|
areas_atuacao: ["agro", "tecnologia"],
|
||||||
|
criado_em: "2026-01-03T14:00:00-03:00",
|
||||||
|
atualizado_em: "2026-01-29T09:00:00-03:00",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
codigo: gerarId(),
|
||||||
|
empreendedor: "Caio Albuquerque",
|
||||||
|
empreendimento: "Albuquerque Engenharia Verde",
|
||||||
|
documento: "65.432.109/0001-09",
|
||||||
|
email: "caio@engenhariaverde.com",
|
||||||
|
telefone: "(61) 98123-4567",
|
||||||
|
faturamento: 100000,
|
||||||
|
peso_kg: 21.3,
|
||||||
|
areas_atuacao: ["mineracao"],
|
||||||
|
criado_em: "2025-11-18T16:10:00-03:00",
|
||||||
|
atualizado_em: "2026-01-25T10:40:00-03:00",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
codigo: gerarId(),
|
||||||
|
empreendedor: "Fernanda Almeida",
|
||||||
|
empreendimento: "Faz Bem Produtos Naturais",
|
||||||
|
documento: "87.654.210/0001-98",
|
||||||
|
email: "fernanda@fazbem.com",
|
||||||
|
telefone: "(41) 99777-6655",
|
||||||
|
faturamento: 5600.2,
|
||||||
|
peso_kg: 8.6,
|
||||||
|
areas_atuacao: ["mineracao", "turismo"],
|
||||||
|
criado_em: "2026-01-08T12:12:00-03:00",
|
||||||
|
atualizado_em: "2026-01-21T17:55:00-03:00",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
codigo: gerarId(),
|
||||||
|
empreendedor: "Gabriel Martins",
|
||||||
|
empreendimento: "Martins Tech Repair",
|
||||||
|
documento: "19.876.543/0001-76",
|
||||||
|
email: "suporte@martinstech.com",
|
||||||
|
telefone: "(19) 98888-9090",
|
||||||
|
faturamento: 7550,
|
||||||
|
peso_kg: 7.7,
|
||||||
|
areas_atuacao: ["mineracao", "turismo"],
|
||||||
|
criado_em: "2026-01-11T11:00:00-03:00",
|
||||||
|
atualizado_em: "2026-01-18T11:30:00-03:00",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
codigo: gerarId(),
|
||||||
|
empreendedor: "Helena Duarte",
|
||||||
|
empreendimento: "Ateliê Costura Criativa",
|
||||||
|
documento: "23.456.789/0001-65",
|
||||||
|
email: "helena@costuracriativa.com",
|
||||||
|
telefone: "(51) 98765-4433",
|
||||||
|
faturamento: 2700,
|
||||||
|
peso_kg: 2.5,
|
||||||
|
areas_atuacao: ["turismo", "mineracao"],
|
||||||
|
criado_em: "2026-01-07T08:00:00-03:00",
|
||||||
|
atualizado_em: "2026-01-16T14:10:00-03:00",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
codigo: gerarId(),
|
||||||
|
empreendedor: "Igor Santos",
|
||||||
|
empreendimento: "Santos Consultoria Financeira",
|
||||||
|
documento: "43.219.876/0001-54",
|
||||||
|
email: "igor@santosconsultoria.com",
|
||||||
|
telefone: "(31) 99332-1100",
|
||||||
|
faturamento: 12000,
|
||||||
|
peso_kg: 5.4,
|
||||||
|
areas_atuacao: ["saude"],
|
||||||
|
criado_em: "2026-01-06T10:30:00-03:00",
|
||||||
|
atualizado_em: "2026-01-22T18:30:00-03:00",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
codigo: gerarId(),
|
||||||
|
empreendedor: "Jéssica Nunes",
|
||||||
|
empreendimento: "Nunes Eventos & Decoração",
|
||||||
|
documento: "09.876.543/0001-33",
|
||||||
|
email: "jessica@nunesdecora.com",
|
||||||
|
telefone: "(62) 99922-3344",
|
||||||
|
faturamento: 3300.1,
|
||||||
|
peso_kg: 4.1,
|
||||||
|
areas_atuacao: ["turismo"],
|
||||||
|
criado_em: "2026-01-04T13:30:00-03:00",
|
||||||
|
atualizado_em: "2026-01-26T09:20:00-03:00",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
codigo: gerarId(),
|
||||||
|
empreendedor: "Leonardo Prado",
|
||||||
|
empreendimento: "Prado Bike Courier",
|
||||||
|
documento: "72.345.098/0001-27",
|
||||||
|
email: "contato@pradobike.com",
|
||||||
|
telefone: "(47) 98444-6677",
|
||||||
|
faturamento: 4100,
|
||||||
|
peso_kg: 6.8,
|
||||||
|
areas_atuacao: ["mineracao"],
|
||||||
|
criado_em: "2026-01-02T18:40:00-03:00",
|
||||||
|
atualizado_em: "2026-01-27T12:00:00-03:00",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
codigo: gerarId(),
|
||||||
|
empreendedor: "Marina Lopes",
|
||||||
|
empreendimento: "Lopes Design Digital",
|
||||||
|
documento: "56.789.012/0001-11",
|
||||||
|
email: "marina@lopesdesign.com",
|
||||||
|
telefone: "(27) 99911-2233",
|
||||||
|
faturamento: 2100,
|
||||||
|
peso_kg: 3.7,
|
||||||
|
areas_atuacao: ["agro"],
|
||||||
|
criado_em: "2026-01-09T09:00:00-03:00",
|
||||||
|
atualizado_em: "2026-01-19T15:35:00-03:00",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
codigo: gerarId(),
|
||||||
|
empreendedor: "Nicolas Teixeira",
|
||||||
|
empreendimento: "QualiVida Fitness",
|
||||||
|
documento: "34.567.890/0001-02",
|
||||||
|
email: "nicolas@qualivida.com",
|
||||||
|
telefone: "(92) 99456-7788",
|
||||||
|
faturamento: 8600.9,
|
||||||
|
peso_kg: 11.2,
|
||||||
|
areas_atuacao: ["agro", "saude"],
|
||||||
|
criado_em: "2026-01-12T19:15:00-03:00",
|
||||||
|
atualizado_em: "2026-01-29T10:50:00-03:00",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
codigo: gerarId(),
|
||||||
|
empreendedor: "Olívia Azevedo",
|
||||||
|
empreendimento: "Azevedo Pet Care",
|
||||||
|
documento: "88.901.234/0001-45",
|
||||||
|
email: "olivia@petcare.com",
|
||||||
|
telefone: "(16) 99788-6655",
|
||||||
|
faturamento: 1900.3,
|
||||||
|
peso_kg: 2.2,
|
||||||
|
areas_atuacao: ["agro"],
|
||||||
|
criado_em: "2026-01-10T15:00:00-03:00",
|
||||||
|
atualizado_em: "2026-01-14T16:00:00-03:00",
|
||||||
|
},
|
||||||
|
]
|
||||||
114
src/playground/api_simulador/index.ts
Normal file
114
src/playground/api_simulador/index.ts
Normal file
|
|
@ -0,0 +1,114 @@
|
||||||
|
import { respostaComuns, type tipoResposta } from "p-respostas"
|
||||||
|
import type z from "zod"
|
||||||
|
import { executarConsultaAlasql } from "./banco"
|
||||||
|
import { _dados_empreendedores, type tipoEmpreendedores } from "./dados"
|
||||||
|
import { type tipoFuncaoApiSimulacao, zApiSimulacao } from "./tipagem"
|
||||||
|
|
||||||
|
const delay = (ms: number) => new Promise((r) => setTimeout(r, ms * Math.random() + 2000))
|
||||||
|
|
||||||
|
// -- API PÚBLICA SIMULADA --
|
||||||
|
|
||||||
|
const api_ler: tipoFuncaoApiSimulacao<"listar"> = async (params) => {
|
||||||
|
// simula latência de rede
|
||||||
|
|
||||||
|
const { total, dados } = executarConsultaAlasql({
|
||||||
|
filtros: params.filtros,
|
||||||
|
coluna_ordem: params.coluna_ordem as string | undefined,
|
||||||
|
direcao_ordem: params.direcao_ordem,
|
||||||
|
limit: params.limit,
|
||||||
|
offSet: params.offSet,
|
||||||
|
})
|
||||||
|
|
||||||
|
return respostaComuns.valor({
|
||||||
|
total,
|
||||||
|
dados,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const api_criar: tipoFuncaoApiSimulacao<"criar"> = async (dados) => {
|
||||||
|
if (!dados.empreendedor) {
|
||||||
|
return respostaComuns.erro("Empreendedor é obrigatório")
|
||||||
|
}
|
||||||
|
|
||||||
|
const novo: tipoEmpreendedores = {
|
||||||
|
codigo: Math.random().toString(),
|
||||||
|
empreendedor: dados.empreendedor || "Novo",
|
||||||
|
empreendimento: dados.empreendimento || "Novo Emp",
|
||||||
|
documento: dados.documento || "00.000.000/0000-00",
|
||||||
|
email: dados.email || "novo@email.com",
|
||||||
|
telefone: dados.telefone || "(00) 0000-0000",
|
||||||
|
faturamento: dados.faturamento || 0,
|
||||||
|
peso_kg: dados.peso_kg || 0,
|
||||||
|
areas_atuacao: dados.areas_atuacao || [],
|
||||||
|
criado_em: new Date().toISOString(),
|
||||||
|
atualizado_em: new Date().toISOString(),
|
||||||
|
}
|
||||||
|
_dados_empreendedores.unshift(novo)
|
||||||
|
return respostaComuns.valor(novo)
|
||||||
|
}
|
||||||
|
|
||||||
|
const api_editar: tipoFuncaoApiSimulacao<"editar"> = async (dados) => {
|
||||||
|
const idx = _dados_empreendedores.find((l) => l.codigo === dados.codigo)
|
||||||
|
if (!idx) return respostaComuns.erro("Registro não encontrado")
|
||||||
|
Object.assign(idx, dados.novosDados)
|
||||||
|
return respostaComuns.valor(idx)
|
||||||
|
}
|
||||||
|
|
||||||
|
const api_deletar: tipoFuncaoApiSimulacao<"deletar"> = async (dados) => {
|
||||||
|
const idx = _dados_empreendedores.findIndex((l) => l.codigo === dados.codigo)
|
||||||
|
if (idx !== -1) {
|
||||||
|
_dados_empreendedores.splice(idx, 1)
|
||||||
|
return respostaComuns.valor("Registro deletado com sucesso")
|
||||||
|
}
|
||||||
|
return respostaComuns.erro("Registro não encontrado")
|
||||||
|
}
|
||||||
|
|
||||||
|
const ApiSimulacao = async <tTipo extends keyof z.infer<typeof zApiSimulacao>>(
|
||||||
|
tIPO: tTipo,
|
||||||
|
req: z.infer<typeof zApiSimulacao>[tTipo]["req"],
|
||||||
|
Callback?: (
|
||||||
|
req: z.infer<typeof zApiSimulacao>[tTipo]["req"],
|
||||||
|
res: tipoResposta<z.infer<typeof zApiSimulacao>[tTipo]["res"]>,
|
||||||
|
) => void,
|
||||||
|
): Promise<tipoResposta<z.infer<typeof zApiSimulacao>[tTipo]["res"]>> => {
|
||||||
|
await delay((2000 * Math.random() + 200) | 0)
|
||||||
|
|
||||||
|
let retorno: tipoResposta<z.infer<typeof zApiSimulacao>[tTipo]["res"]> =
|
||||||
|
respostaComuns.erro("Não entrou!")
|
||||||
|
const zSchema = zApiSimulacao.shape[tIPO]
|
||||||
|
|
||||||
|
try {
|
||||||
|
await zSchema.shape.req.parseAsync(req)
|
||||||
|
|
||||||
|
switch (tIPO) {
|
||||||
|
case "listar":
|
||||||
|
retorno = await api_ler(req as z.infer<typeof zApiSimulacao>["listar"]["req"])
|
||||||
|
break
|
||||||
|
case "criar":
|
||||||
|
retorno = await api_criar(req as z.infer<typeof zApiSimulacao>["criar"]["req"])
|
||||||
|
break
|
||||||
|
case "editar":
|
||||||
|
retorno = await api_editar(req as z.infer<typeof zApiSimulacao>["editar"]["req"])
|
||||||
|
break
|
||||||
|
case "deletar":
|
||||||
|
retorno = await api_deletar(req as z.infer<typeof zApiSimulacao>["deletar"]["req"])
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
retorno = respostaComuns.erro("Tipo de API não encontrado!")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (retorno.eCerto) {
|
||||||
|
await zSchema.shape.res.parseAsync(retorno.valor)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
retorno = respostaComuns.erro((error as Error).message)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Callback) {
|
||||||
|
Callback(req, retorno)
|
||||||
|
}
|
||||||
|
|
||||||
|
return retorno
|
||||||
|
}
|
||||||
|
|
||||||
|
export { ApiSimulacao }
|
||||||
52
src/playground/api_simulador/tipagem.ts
Normal file
52
src/playground/api_simulador/tipagem.ts
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
import { zFiltroDelta } from "p-comuns"
|
||||||
|
import type { tipoResposta } from "p-respostas"
|
||||||
|
import z from "zod"
|
||||||
|
import { type tipoEmpreendedores, zEmpreendedores } from "./dados"
|
||||||
|
|
||||||
|
const baseApi = <tTipo extends string, tReq extends z.ZodTypeAny, tRes extends z.ZodTypeAny>(
|
||||||
|
tipo: tTipo,
|
||||||
|
req: tReq,
|
||||||
|
res: tRes,
|
||||||
|
) =>
|
||||||
|
z.object({
|
||||||
|
tipo: z.literal(tipo),
|
||||||
|
req: req,
|
||||||
|
res: res,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const zApiSimulacao = z.object({
|
||||||
|
listar: baseApi(
|
||||||
|
"listar",
|
||||||
|
z.object({
|
||||||
|
filtros: zFiltroDelta.optional(),
|
||||||
|
coluna_ordem: z.custom<keyof tipoEmpreendedores>().optional(),
|
||||||
|
direcao_ordem: z.enum(["asc", "desc"]).optional(),
|
||||||
|
limit: z.number().optional(),
|
||||||
|
offSet: z.number().optional(),
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
total: z.number(),
|
||||||
|
dados: z.array(zEmpreendedores),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
criar: baseApi("criar", zEmpreendedores, zEmpreendedores),
|
||||||
|
editar: baseApi(
|
||||||
|
"editar",
|
||||||
|
z.object({
|
||||||
|
codigo: z.string(),
|
||||||
|
novosDados: zEmpreendedores.partial(),
|
||||||
|
}),
|
||||||
|
zEmpreendedores,
|
||||||
|
),
|
||||||
|
deletar: baseApi(
|
||||||
|
"deletar",
|
||||||
|
z.object({
|
||||||
|
codigo: z.string(),
|
||||||
|
}),
|
||||||
|
z.string(),
|
||||||
|
),
|
||||||
|
})
|
||||||
|
|
||||||
|
export type tipoFuncaoApiSimulacao<tTipo extends keyof z.infer<typeof zApiSimulacao>> = (
|
||||||
|
dados: z.infer<typeof zApiSimulacao>[tTipo]["req"],
|
||||||
|
) => Promise<tipoResposta<z.infer<typeof zApiSimulacao>[tTipo]["res"]>>
|
||||||
|
|
@ -121,9 +121,7 @@ export default defineComponent({
|
||||||
function itensFiltrados(itens: Card[]) {
|
function itensFiltrados(itens: Card[]) {
|
||||||
const q = filtro.value.trim().toLowerCase()
|
const q = filtro.value.trim().toLowerCase()
|
||||||
if (!q) return itens
|
if (!q) return itens
|
||||||
return itens.filter((i) =>
|
return itens.filter((i) => `${i.titulo} ${i.cliente} ${i.valor}`.toLowerCase().includes(q))
|
||||||
`${i.titulo} ${i.cliente} ${i.valor}`.toLowerCase().includes(q),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function criar() {
|
function criar() {
|
||||||
|
|
|
||||||
|
|
@ -125,12 +125,7 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from "vue"
|
import { defineComponent } from "vue"
|
||||||
import {
|
import { EliEntradaNumero, EliEntradaParagrafo, EliEntradaSelecao, EliEntradaTexto } from "../index"
|
||||||
EliEntradaNumero,
|
|
||||||
EliEntradaParagrafo,
|
|
||||||
EliEntradaSelecao,
|
|
||||||
EliEntradaTexto,
|
|
||||||
} from "../index"
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "EntradasPlayground",
|
name: "EntradasPlayground",
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import * as directives from "vuetify/directives"
|
||||||
// Estilos globais da lib (font-face + variáveis de fonte)
|
// Estilos globais da lib (font-face + variáveis de fonte)
|
||||||
// Importamos DEPOIS do Vuetify para garantir que `--v-font-family` seja sobrescrito.
|
// Importamos DEPOIS do Vuetify para garantir que `--v-font-family` seja sobrescrito.
|
||||||
import "../styles/eli-vue-fonts.css"
|
import "../styles/eli-vue-fonts.css"
|
||||||
|
import "../design-tokens.css"
|
||||||
|
|
||||||
const themeDefault = {
|
const themeDefault = {
|
||||||
dark: false,
|
dark: false,
|
||||||
|
|
@ -173,22 +174,15 @@ if (import.meta.env.DEV) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log(
|
console.log(
|
||||||
"[eli-vue] --v-font-family:",
|
"[eli-vue] --v-font-family:",
|
||||||
getComputedStyle(document.documentElement).getPropertyValue(
|
getComputedStyle(document.documentElement).getPropertyValue("--v-font-family"),
|
||||||
"--v-font-family",
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log(
|
console.log(
|
||||||
"[eli-vue] --eli-font-family:",
|
"[eli-vue] --eli-font-family:",
|
||||||
getComputedStyle(document.documentElement).getPropertyValue(
|
getComputedStyle(document.documentElement).getPropertyValue("--eli-font-family"),
|
||||||
"--eli-font-family",
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log(
|
console.log("[eli-vue] body font-family:", getComputedStyle(document.body).fontFamily)
|
||||||
"[eli-vue] body font-family:",
|
|
||||||
getComputedStyle(document.body).fontFamily,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
app.use(vuetify).mount("#app")
|
app.use(vuetify).mount("#app")
|
||||||
|
|
|
||||||
|
|
@ -2,15 +2,14 @@
|
||||||
<section class="stack">
|
<section class="stack">
|
||||||
<h2>EliTabela</h2>
|
<h2>EliTabela</h2>
|
||||||
|
|
||||||
<!-- Exemplo: botão de ação "Novo" no cabeçalho que adiciona uma linha -->
|
|
||||||
<div style="margin-bottom: 12px">
|
<div style="margin-bottom: 12px">
|
||||||
<strong>Selecionados:</strong> {{ selecionados.map(s => s.empreendedor).join(', ') || 'Nenhum' }}
|
<strong>Selecionados:</strong> {{ selecionados.map(s => s.empreendedor).join(', ') || 'Nenhum' }}
|
||||||
</div>
|
</div>
|
||||||
<EliTabela :tabela="tabelaOk" :key="versaoTabelaOk" v-model:selecionados="selecionados" />
|
<EliTabela :tabela="tabelaOk" :key="versaoTabelaOk" v-model:selecionados="selecionados" esconderDebug />
|
||||||
|
|
||||||
<EliTabela :tabela="tabelaVazia" />
|
<EliTabela :tabela="tabelaVazia" esconderDebug />
|
||||||
|
|
||||||
<EliTabela :tabela="tabelaErro" />
|
<EliTabela :tabela="tabelaErro" esconderDebug />
|
||||||
|
|
||||||
<!-- Modal de simulação de cadastro -->
|
<!-- Modal de simulação de cadastro -->
|
||||||
<v-dialog v-model="mostrarModalCadastro" max-width="500px">
|
<v-dialog v-model="mostrarModalCadastro" max-width="500px">
|
||||||
|
|
@ -65,359 +64,15 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { BadgeCheck, Eye, Pencil, Plus, Trash2 } from "@lucide/vue"
|
import { defineComponent } from "vue"
|
||||||
import { criarFiltro26, type tipoFiltro26 } from "p-comuns"
|
import { EliTabela } from "@/componentes/EliTabela"
|
||||||
import { codigosResposta } from "p-respostas"
|
import { useTabelaPlayground } from "./useTabelaPlayground"
|
||||||
import { defineComponent, ref } from "vue"
|
|
||||||
import type { ComponenteEntrada } from "@/componentes/EliEntrada/tiposEntradas"
|
|
||||||
import type { tipoEliTabelaConsulta } from "@/componentes/EliTabela"
|
|
||||||
import { celulaTabela, EliTabela } from "@/componentes/EliTabela"
|
|
||||||
|
|
||||||
import { api_criar, api_deletar, api_ler, type Linha } from "./api_simulador"
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "TabelaPlayground",
|
name: "TabelaPlayground",
|
||||||
components: { EliTabela },
|
components: { EliTabela },
|
||||||
setup() {
|
setup() {
|
||||||
const acoesLinha: tipoEliTabelaConsulta<Linha>["acoesLinha"] = [
|
return useTabelaPlayground()
|
||||||
{
|
|
||||||
icone: Eye,
|
|
||||||
cor: "#2563eb",
|
|
||||||
rotulo: "Detalhes",
|
|
||||||
acao: (linha) => {
|
|
||||||
console.log("Visualizar detalhes de", linha.empreendedor)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icone: Trash2,
|
|
||||||
cor: "#dc2626",
|
|
||||||
rotulo: "Remover",
|
|
||||||
acao: (linha) => {
|
|
||||||
removerCadastro(linha)
|
|
||||||
},
|
|
||||||
exibir: (linha) => !linha.empreendimento.toLowerCase().includes("café"),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
const versaoTabelaOk = ref(0)
|
|
||||||
const selecionados = ref<Linha[]>([])
|
|
||||||
const mostrarModalCadastro = ref(false)
|
|
||||||
const salvando = ref(false)
|
|
||||||
const novoCadastro = ref({
|
|
||||||
empreendedor: "",
|
|
||||||
empreendimento: "",
|
|
||||||
documento: "",
|
|
||||||
email: "",
|
|
||||||
telefone: "",
|
|
||||||
faturamento: 0,
|
|
||||||
peso_kg: 0,
|
|
||||||
})
|
|
||||||
const erroCadastro = ref<string | undefined>(undefined)
|
|
||||||
|
|
||||||
// Armazena a função de atualização da tabela atual (vinda da ação do cabeçalho)
|
|
||||||
let atualizarTabelaCallback: (() => Promise<void>) | undefined
|
|
||||||
|
|
||||||
// A ação do cabeçalho recebe { atualizarConsulta, editarLista, ... }
|
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: playground
|
|
||||||
function abrirModalCadastro(params?: any) {
|
|
||||||
if (params?.atualizarConsulta) {
|
|
||||||
atualizarTabelaCallback = params.atualizarConsulta
|
|
||||||
}
|
|
||||||
|
|
||||||
novoCadastro.value = {
|
|
||||||
empreendedor: "",
|
|
||||||
empreendimento: "",
|
|
||||||
documento: "",
|
|
||||||
email: "",
|
|
||||||
telefone: "",
|
|
||||||
faturamento: 0,
|
|
||||||
peso_kg: 0,
|
|
||||||
}
|
|
||||||
erroCadastro.value = undefined
|
|
||||||
mostrarModalCadastro.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
async function salvarNovoCadastro() {
|
|
||||||
salvando.value = true
|
|
||||||
erroCadastro.value = undefined
|
|
||||||
try {
|
|
||||||
const resposta = await api_criar(novoCadastro.value)
|
|
||||||
if (resposta.eErro) {
|
|
||||||
erroCadastro.value = resposta.mensagem
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// PADRÃO RECOMENDADO:
|
|
||||||
// Se temos o callback de atualização, usamos ele para recarregar os dados na tabela
|
|
||||||
// sem precisar remontar o componente (sem alterar key).
|
|
||||||
if (atualizarTabelaCallback) {
|
|
||||||
await atualizarTabelaCallback()
|
|
||||||
} else {
|
|
||||||
// Fallback (caso não tenha vindo pela ação da tabela): força re-render
|
|
||||||
versaoTabelaOk.value++
|
|
||||||
}
|
|
||||||
|
|
||||||
mostrarModalCadastro.value = false
|
|
||||||
} catch (e) {
|
|
||||||
erroCadastro.value = String(e)
|
|
||||||
} finally {
|
|
||||||
salvando.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function removerCadastro(linha: Linha) {
|
|
||||||
// Exemplo de uso do api_deletar
|
|
||||||
// Não temos um ID único confiável além das propriedades, vamos usar empreendedor como chave neste exemplo simples
|
|
||||||
if (confirm(`Tem certeza que deseja remover ${linha.empreendedor}?`)) {
|
|
||||||
api_deletar("empreendedor", linha.empreendedor).then(() => {
|
|
||||||
versaoTabelaOk.value++
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const tabelaOk: tipoEliTabelaConsulta<Linha> = {
|
|
||||||
nome: "ExemploPlaygroundData",
|
|
||||||
registros_por_consulta: 10,
|
|
||||||
mostrarCaixaDeBusca: true,
|
|
||||||
mostrar_selecao: true,
|
|
||||||
chave_selecao: "empreendedor",
|
|
||||||
acoesTabela: [
|
|
||||||
{
|
|
||||||
posicao: "superior",
|
|
||||||
icone: Plus,
|
|
||||||
cor: "#16a34a",
|
|
||||||
rotulo: "Novo (Superior)",
|
|
||||||
acao: abrirModalCadastro,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
posicao: "inferior",
|
|
||||||
icone: Plus,
|
|
||||||
cor: "#2563eb",
|
|
||||||
rotulo: "Novo (Inferior)",
|
|
||||||
acao: abrirModalCadastro,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
colunas: [
|
|
||||||
{
|
|
||||||
rotulo: "Empreendedor",
|
|
||||||
celula: (l) =>
|
|
||||||
celulaTabela("textoSimples", { texto: l.empreendedor }),
|
|
||||||
coluna_ordem: "empreendedor",
|
|
||||||
visivel: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rotulo: "Empreendimento",
|
|
||||||
celula: (l) =>
|
|
||||||
celulaTabela("textoSimples", { texto: l.empreendimento }),
|
|
||||||
coluna_ordem: "empreendimento",
|
|
||||||
visivel: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rotulo: "Documento",
|
|
||||||
celula: (l) => celulaTabela("textoSimples", { texto: l.documento }),
|
|
||||||
coluna_ordem: "documento",
|
|
||||||
visivel: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rotulo: "E-mail",
|
|
||||||
celula: (l) =>
|
|
||||||
celulaTabela("textoTruncado", {
|
|
||||||
texto: l.email,
|
|
||||||
acao: () => {
|
|
||||||
// Exemplo de ação: poderia abrir detalhes
|
|
||||||
alert(`Clicou em ${l.email}`)
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
coluna_ordem: "email",
|
|
||||||
visivel: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rotulo: "Telefone",
|
|
||||||
celula: (l) => celulaTabela("textoSimples", { texto: l.telefone }),
|
|
||||||
coluna_ordem: "telefone",
|
|
||||||
visivel: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rotulo: "Faturamento",
|
|
||||||
celula: (l) =>
|
|
||||||
celulaTabela("numero", {
|
|
||||||
numero: l.faturamento,
|
|
||||||
prefixo: "R$",
|
|
||||||
// Exemplo de ação (clicável)
|
|
||||||
acao: () =>
|
|
||||||
alert(`Faturamento de ${l.empreendedor}: R$ ${l.faturamento}`),
|
|
||||||
}),
|
|
||||||
visivel: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rotulo: "Peso",
|
|
||||||
celula: (l) =>
|
|
||||||
celulaTabela("numero", {
|
|
||||||
numero: l.peso_kg,
|
|
||||||
sufixo: "kg",
|
|
||||||
}),
|
|
||||||
visivel: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rotulo: "Status",
|
|
||||||
celula: (l) =>
|
|
||||||
celulaTabela("tags", {
|
|
||||||
opcoes: [
|
|
||||||
{
|
|
||||||
rotulo: "Ativo",
|
|
||||||
cor: "success",
|
|
||||||
icone: BadgeCheck,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rotulo: "Editar",
|
|
||||||
cor: "primary",
|
|
||||||
icone: Pencil,
|
|
||||||
acao: () => {
|
|
||||||
alert(`Editar ${l.empreendedor}`)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
visivel: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rotulo: "Criado em",
|
|
||||||
celula: (l) =>
|
|
||||||
celulaTabela("data", {
|
|
||||||
valor: l.criado_em,
|
|
||||||
formato: "data",
|
|
||||||
}),
|
|
||||||
visivel: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rotulo: "Atualizado",
|
|
||||||
celula: (l) =>
|
|
||||||
celulaTabela("data", {
|
|
||||||
valor: l.atualizado_em,
|
|
||||||
formato: "relativo",
|
|
||||||
acao: () => alert(`Atualizado em: ${l.atualizado_em}`),
|
|
||||||
}),
|
|
||||||
visivel: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
acoesLinha: acoesLinha,
|
|
||||||
filtroAvancado: [
|
|
||||||
{
|
|
||||||
chave: "empreendedor",
|
|
||||||
rotulo: "Empreendedor",
|
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
chave: "documento",
|
|
||||||
rotulo: "Documento",
|
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
|
||||||
filtro: (valor: string) =>
|
|
||||||
criarFiltro26({
|
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: playground
|
|
||||||
documento: { like: valor as string } as any,
|
|
||||||
}) as tipoFiltro26<Linha>,
|
|
||||||
entrada: [
|
|
||||||
"texto",
|
|
||||||
{ rotulo: "Documento", formato: "cpfCnpj" },
|
|
||||||
] as ComponenteEntrada,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
chave: "email",
|
|
||||||
rotulo: "E-mail",
|
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
|
||||||
filtro: (valor: string) =>
|
|
||||||
criarFiltro26({
|
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: playground
|
|
||||||
email: { like: valor as string } as any,
|
|
||||||
}) as tipoFiltro26<Linha>,
|
|
||||||
entrada: [
|
|
||||||
"texto",
|
|
||||||
{ rotulo: "E-mail", formato: "email" },
|
|
||||||
] as ComponenteEntrada,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
consulta: async (parametrosConsulta) => {
|
|
||||||
try {
|
|
||||||
const resposta = await api_ler(parametrosConsulta)
|
|
||||||
return {
|
|
||||||
cod: codigosResposta.sucesso,
|
|
||||||
eCerto: true,
|
|
||||||
eErro: false,
|
|
||||||
mensagem: undefined,
|
|
||||||
valor: {
|
|
||||||
quantidade: resposta.total,
|
|
||||||
valores: resposta.dados,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
return {
|
|
||||||
cod: codigosResposta.erroConhecido,
|
|
||||||
eCerto: false,
|
|
||||||
eErro: true,
|
|
||||||
mensagem: String(error),
|
|
||||||
valor: undefined,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const tabelaVazia: tipoEliTabelaConsulta<Linha> = {
|
|
||||||
nome: "Exemplo Vazio",
|
|
||||||
registros_por_consulta: 10,
|
|
||||||
mostrarCaixaDeBusca: true,
|
|
||||||
colunas: tabelaOk.colunas,
|
|
||||||
consulta: async (_parametrosConsulta) => {
|
|
||||||
return {
|
|
||||||
cod: codigosResposta.sucesso,
|
|
||||||
eCerto: true,
|
|
||||||
eErro: false,
|
|
||||||
mensagem: undefined,
|
|
||||||
valor: {
|
|
||||||
quantidade: 0,
|
|
||||||
valores: [],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mensagemVazio: "Nada para mostrar aqui.",
|
|
||||||
acoesLinha: acoesLinha,
|
|
||||||
}
|
|
||||||
|
|
||||||
const tabelaErro: tipoEliTabelaConsulta<Linha> = {
|
|
||||||
nome: "Exemplo",
|
|
||||||
registros_por_consulta: tabelaOk.registros_por_consulta,
|
|
||||||
mostrarCaixaDeBusca: tabelaOk.mostrarCaixaDeBusca,
|
|
||||||
colunas: tabelaOk.colunas,
|
|
||||||
acoesLinha: acoesLinha,
|
|
||||||
consulta: async (_parametrosConsulta) => {
|
|
||||||
return {
|
|
||||||
cod: codigosResposta.erroConhecido,
|
|
||||||
eCerto: false,
|
|
||||||
eErro: true,
|
|
||||||
mensagem: "Falha ao buscar dados",
|
|
||||||
valor: undefined,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
tabelaOk,
|
|
||||||
tabelaVazia,
|
|
||||||
tabelaErro,
|
|
||||||
versaoTabelaOk,
|
|
||||||
mostrarModalCadastro,
|
|
||||||
salvando,
|
|
||||||
novoCadastro,
|
|
||||||
salvarNovoCadastro,
|
|
||||||
erroCadastro,
|
|
||||||
selecionados,
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -429,6 +84,15 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
|
|
||||||
.stack :deep(.eli-tabela) {
|
.stack :deep(.eli-tabela) {
|
||||||
max-width: 900px;
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug {
|
||||||
|
padding: 12px;
|
||||||
|
background: rgba(0, 0, 0, 0.04);
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: auto;
|
||||||
|
font-size: 12px;
|
||||||
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
171
src/playground/useTabelaPlayground.ts
Normal file
171
src/playground/useTabelaPlayground.ts
Normal file
|
|
@ -0,0 +1,171 @@
|
||||||
|
/**
|
||||||
|
* useTabelaPlayground
|
||||||
|
* Composable principal do playground da EliTabela.
|
||||||
|
* Gerencia estado de CRUD (modal, formulário, erros) e compõe as 3 instâncias de tabela de demonstração.
|
||||||
|
* A configuração de colunas, filtros e consulta está em `useTabelaPlaygroundConfig.ts`.
|
||||||
|
*
|
||||||
|
* @returns Estado reativo, configurações de tabela e ações de CRUD para o template
|
||||||
|
* @see useTabelaPlaygroundConfig.ts — definições de colunas, filtros e consulta
|
||||||
|
* @see tabela.playground.vue — componente que consome este composable
|
||||||
|
*/
|
||||||
|
import { Plus } from "@lucide/vue"
|
||||||
|
import { codigosResposta } from "p-respostas"
|
||||||
|
import { inject, ref } from "vue"
|
||||||
|
import type { tipoEliTabelaConsulta } from "@/componentes/EliTabela"
|
||||||
|
import { PLAYGROUND_CONSOLE_KEY, type PlaygroundConsole } from "./App.vue"
|
||||||
|
import { ApiSimulacao } from "./api_simulador"
|
||||||
|
import type { tipoEmpreendedores } from "./api_simulador/dados"
|
||||||
|
import { colunasTabela, criarAcoesLinha, filtrosAvancados } from "./useTabelaPlaygroundConfig"
|
||||||
|
import { consultar } from "./useTabelaPlaygroundConsultar"
|
||||||
|
|
||||||
|
export function useTabelaPlayground() {
|
||||||
|
const playgroundConsole = inject<PlaygroundConsole>(PLAYGROUND_CONSOLE_KEY)
|
||||||
|
|
||||||
|
function adicionarLog(acao: string, req: unknown, res: unknown) {
|
||||||
|
if (playgroundConsole) playgroundConsole.adicionarLog(`ApiSimulacao: ${acao}`, req, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
const versaoTabelaOk = ref(0)
|
||||||
|
const selecionados = ref<tipoEmpreendedores[]>([])
|
||||||
|
const mostrarModalCadastro = ref(false)
|
||||||
|
const salvando = ref(false)
|
||||||
|
const novoCadastro = ref<Omit<tipoEmpreendedores, "codigo" | "criado_em" | "atualizado_em">>({
|
||||||
|
empreendedor: "",
|
||||||
|
empreendimento: "",
|
||||||
|
documento: "",
|
||||||
|
email: "",
|
||||||
|
telefone: "",
|
||||||
|
faturamento: 0,
|
||||||
|
peso_kg: 0,
|
||||||
|
areas_atuacao: [],
|
||||||
|
})
|
||||||
|
const erroCadastro = ref<string | undefined>(undefined)
|
||||||
|
|
||||||
|
let atualizarTabelaCallback: (() => Promise<void>) | undefined
|
||||||
|
|
||||||
|
// biome-ignore lint/suspicious/noExplicitAny: playground — parâmetros dinâmicos da ação de tabela
|
||||||
|
function abrirModalCadastro(params?: any) {
|
||||||
|
if (params?.atualizarConsulta) atualizarTabelaCallback = params.atualizarConsulta
|
||||||
|
novoCadastro.value = {
|
||||||
|
empreendedor: "",
|
||||||
|
empreendimento: "",
|
||||||
|
documento: "",
|
||||||
|
email: "",
|
||||||
|
telefone: "",
|
||||||
|
faturamento: 0,
|
||||||
|
peso_kg: 0,
|
||||||
|
areas_atuacao: [],
|
||||||
|
}
|
||||||
|
erroCadastro.value = undefined
|
||||||
|
mostrarModalCadastro.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
async function salvarNovoCadastro() {
|
||||||
|
salvando.value = true
|
||||||
|
erroCadastro.value = undefined
|
||||||
|
try {
|
||||||
|
const payload: tipoEmpreendedores = {
|
||||||
|
...novoCadastro.value,
|
||||||
|
codigo: "novo",
|
||||||
|
criado_em: new Date().toISOString(),
|
||||||
|
atualizado_em: new Date().toISOString(),
|
||||||
|
}
|
||||||
|
const resposta = await ApiSimulacao("criar", payload, (req, res) => {
|
||||||
|
console.groupCollapsed("🌐 [ApiSimulacao] criar")
|
||||||
|
console.info("Req:", req)
|
||||||
|
console.info("Res:", res)
|
||||||
|
console.groupEnd()
|
||||||
|
adicionarLog("criar", req, res)
|
||||||
|
})
|
||||||
|
if (resposta.eErro) {
|
||||||
|
erroCadastro.value = resposta.mensagem
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (atualizarTabelaCallback) await atualizarTabelaCallback()
|
||||||
|
else versaoTabelaOk.value++
|
||||||
|
mostrarModalCadastro.value = false
|
||||||
|
} catch (e) {
|
||||||
|
erroCadastro.value = String(e)
|
||||||
|
} finally {
|
||||||
|
salvando.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function removerCadastro(linha: tipoEmpreendedores) {
|
||||||
|
if (confirm(`Tem certeza que deseja remover ${linha.empreendedor}?`)) {
|
||||||
|
ApiSimulacao("deletar", { codigo: linha.codigo }, (req, res) => {
|
||||||
|
console.groupCollapsed("🌐 [ApiSimulacao] deletar")
|
||||||
|
console.info("Req:", req)
|
||||||
|
console.info("Res:", res)
|
||||||
|
console.groupEnd()
|
||||||
|
adicionarLog("deletar", req, res)
|
||||||
|
}).then(() => {
|
||||||
|
versaoTabelaOk.value++
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const acoesLinha = criarAcoesLinha(removerCadastro)
|
||||||
|
const consultarFn: tipoEliTabelaConsulta<tipoEmpreendedores>["consulta"] = (params) =>
|
||||||
|
consultar(params, adicionarLog)
|
||||||
|
|
||||||
|
const tabelaOk: tipoEliTabelaConsulta<tipoEmpreendedores> = {
|
||||||
|
nome: "ExemploPlaygroundData",
|
||||||
|
registros_por_consulta: 10,
|
||||||
|
mostrarCaixaDeBusca: true,
|
||||||
|
mostrar_selecao: true,
|
||||||
|
chave_selecao: "empreendedor",
|
||||||
|
acoesTabela: [
|
||||||
|
{ posicao: "superior", icone: Plus, rotulo: "Novo (Superior)", acao: abrirModalCadastro },
|
||||||
|
{ posicao: "inferior", icone: Plus, rotulo: "Novo (Inferior)", acao: abrirModalCadastro },
|
||||||
|
],
|
||||||
|
colunas: colunasTabela,
|
||||||
|
acoesLinha,
|
||||||
|
filtroAvancado: filtrosAvancados,
|
||||||
|
consulta: consultarFn,
|
||||||
|
}
|
||||||
|
|
||||||
|
const tabelaVazia: tipoEliTabelaConsulta<tipoEmpreendedores> = {
|
||||||
|
nome: "Exemplo Vazio",
|
||||||
|
registros_por_consulta: 10,
|
||||||
|
mostrarCaixaDeBusca: true,
|
||||||
|
colunas: colunasTabela,
|
||||||
|
mensagemVazio: "Nada para mostrar aqui.",
|
||||||
|
acoesLinha,
|
||||||
|
consulta: async () => ({
|
||||||
|
cod: codigosResposta.sucesso,
|
||||||
|
eCerto: true,
|
||||||
|
eErro: false,
|
||||||
|
mensagem: undefined,
|
||||||
|
valor: { quantidade: 0, valores: [] },
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
const tabelaErro: tipoEliTabelaConsulta<tipoEmpreendedores> = {
|
||||||
|
nome: "Exemplo",
|
||||||
|
registros_por_consulta: tabelaOk.registros_por_consulta,
|
||||||
|
mostrarCaixaDeBusca: tabelaOk.mostrarCaixaDeBusca,
|
||||||
|
colunas: colunasTabela,
|
||||||
|
acoesLinha,
|
||||||
|
consulta: async () => ({
|
||||||
|
cod: codigosResposta.erroConhecido,
|
||||||
|
eCerto: false,
|
||||||
|
eErro: true,
|
||||||
|
mensagem: "Falha ao buscar dados",
|
||||||
|
valor: undefined,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
tabelaOk,
|
||||||
|
tabelaVazia,
|
||||||
|
tabelaErro,
|
||||||
|
versaoTabelaOk,
|
||||||
|
mostrarModalCadastro,
|
||||||
|
salvando,
|
||||||
|
novoCadastro,
|
||||||
|
salvarNovoCadastro,
|
||||||
|
erroCadastro,
|
||||||
|
selecionados,
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue