ajustes na tabela de consulta

This commit is contained in:
Luiz Silva 2026-06-01 13:13:32 -03:00
parent aea3da6585
commit 1446d6225b
78 changed files with 17921 additions and 17580 deletions

View file

@ -5,13 +5,25 @@ BASE="https://paiol.idz.one/desenvolvimento/agent-ia"
ERROS=0
baixar() {
TMP=$(mktemp)
echo "baixando $1"
ERRO_CURL="$(curl -sSfL --create-dirs "$1" -o "$2" 2>&1)" || {
ERRO_CURL="$(curl -sSfL --create-dirs "$1" -o "$TMP" 2>&1)" || {
echo " ⚠ $1"
echo " $ERRO_CURL"
rm -f "$TMP"
ERROS=$((ERROS + 1))
return 1
}
# Rejeitar arquivos menores que 50 bytes (prováveis erros de proxy/WAF)
TAMANHO=$(wc -c < "$TMP" | tr -d ' ')
if [ "$TAMANHO" -lt 50 ]; then
echo " ⚠ $1 — arquivo muito pequeno ($TAMANHO bytes), ignorado"
rm -f "$TMP"
ERROS=$((ERROS + 1))
return 1
fi
mkdir -p "$(dirname "$2")"
mv "$TMP" "$2"
}
# ignorar se não auto atualizar
@ -27,7 +39,14 @@ fi
baixar "$BASE/rules/carta-principios-agente-azteca.global.md" "$DIR/rules/carta-principios-agente-azteca.global.md"
baixar "$BASE/rules/escrita-natural.global.md" "$DIR/rules/escrita-natural.global.md"
baixar "$BASE/rules/typescript-sem-any.global.md" "$DIR/rules/typescript-sem-any.global.md"
baixar "$BASE/rules/regras_negocio_md.global.md" "$DIR/rules/regras_negocio_md.global.md"
baixar "$BASE/rules/regras-negocio-md.global.md" "$DIR/rules/regras-negocio-md.global.md"
baixar "$BASE/rules/zod-validacao-padrao.global.md" "$DIR/rules/zod-validacao-padrao.global.md"
baixar "$BASE/rules/limite-linhas.global.md" "$DIR/rules/limite-linhas.global.md"
baixar "$BASE/rules/estrutura-vue.global.md" "$DIR/rules/estrutura-vue.global.md"
baixar "$BASE/rules/jsdoc-padrao.global.md" "$DIR/rules/jsdoc-padrao.global.md"
baixar "$BASE/rules/autonomia-agente.global.md" "$DIR/rules/autonomia-agente.global.md"
baixar "$BASE/rules/fronteiras-monorepo.global.md" "$DIR/rules/fronteiras-monorepo.global.md"
baixar "$BASE/rules/mensagens-usuario.global.md" "$DIR/rules/mensagens-usuario.global.md"
# workflows
baixar "$BASE/workflows/aprimorar-IA.global.md" "$DIR/workflows/aprimorar-IA.global.md"
@ -48,8 +67,11 @@ baixar "$BASE/skills/mapear-codigo-fonte.global/SKILL.md" "$DIR/sk
baixar "$BASE/skills/diagnosticar-erros-build.global/SKILL.md" "$DIR/skills/diagnosticar-erros-build.global/SKILL.md"
baixar "$BASE/skills/documentar-decisao-tecnica.global/SKILL.md" "$DIR/skills/documentar-decisao-tecnica.global/SKILL.md"
baixar "$BASE/skills/documentar-regras-negocio.global/SKILL.md" "$DIR/skills/documentar-regras-negocio.global/SKILL.md"
baixar "$BASE/skills/criar-schema-zod.global/SKILL.md" "$DIR/skills/criar-schema-zod.global/SKILL.md"
baixar "$BASE/skills/documentar-dominio-modulo.global/SKILL.md" "$DIR/skills/documentar-dominio-modulo.global/SKILL.md"
# raiz (.agent)
baixar "$BASE/tools.md" "$DIR/tools.md"
baixar "$BASE/verificar-linhas.sh" "$DIR/verificar-linhas.sh"
exit "$ERROS"

View file

@ -0,0 +1,60 @@
---
type: rule
name: autonomia-agente
description: Define limites de autonomia do agente — o que pode fazer sozinho, o que requer confirmação e o que é proibido
scope: global
trigger: always_on
priority: alta
updatedAt: "2026-06-01"
---
# Regra: Limites de Autonomia do Agente
## Contexto
O agente existe para **ampliar a capacidade humana**, não para substituir decisões. Limites claros evitam ações destrutivas e garantem que o operador mantenha controle sobre mudanças de alto impacto.
## Ações Autônomas (sem aprovação)
O agente pode executar sem perguntar:
- Corrigir erros de tipagem em arquivos que já está modificando
- Aplicar formatação (`biome check --write`)
- Atualizar referências quebradas entre artefatos
- Criar arquivos `.regras-negocio.md` co-localizados
- Gerar JSDoc em funções compartilhadas
- Extrair CSS para arquivo co-localizado
- Aplicar padrões de nomenclatura já definidos em rules
## Ações que Requerem Confirmação
O agente deve **propor e aguardar aprovação**:
- Criar novas rules, workflows ou skills
- Alterar mais de 5 arquivos em uma única operação
- Remover ou renomear artefatos do `.agent/`
- Mudar contratos de API (parâmetros, retornos, rotas)
- Alterar schemas Zod compartilhados no pacote `*-comuns`
- Propor novas dependências ao `package.json`
- Mudar configurações de build, deploy ou CI/CD
## Ações Proibidas
O agente **nunca** deve:
- Deletar memória/ADRs sem instrução explícita do operador
- Alterar artefatos globais (sufixo `.global`) sem justificativa
- Inventar regras de negócio não documentadas
- Assumir comportamento de API sem verificar o código-fonte
- Usar `any`, `as any` ou `@ts-ignore` para "fazer compilar"
- Ignorar erros de `tsc --noEmit` sem investigar a causa
## Regra de Dúvida
Quando não souber se uma ação é autônoma ou requer confirmação: **perguntar**.
O custo de uma pergunta é zero. O custo de uma ação destrutiva é alto.
## Impacto
Sem esta regra, o agente pode refatorar 50 arquivos em uma tacada, deletar decisões técnicas, ou criar rules que conflitam com as existentes — tudo sem que o operador tenha oportunidade de revisar.

View file

@ -3,9 +3,10 @@ trigger: always_on
type: rule
name: carta-principios-agente-azteca
description: Princípios morais e operacionais que orientam o comportamento de agentes de IA no ecossistema Azteca
version: 1.0
version: "1.0"
scope: global
priority: alta
updatedAt: "2026-06-01"
---
# Carta de Princípios do Agente de IA da Azteca

View file

@ -1,6 +1,14 @@
---
trigger: always_on
description: Garantir escrita natural, fluida e humana em textos técnicos e comerciais, evitando linguagem robótica ou excessivamente genérica.
type: rule
name: escrita-natural
trigger: context_match
description: Garantir escrita natural, fluida e humana em textos comerciais e de comunicação, evitando linguagem robótica ou excessivamente genérica.
scope: global
updatedAt: "2026-06-01"
ativacao: >
Aplicar quando o agente estiver produzindo textos comerciais, de marketing,
orçamentos, propostas, reels ou qualquer comunicação voltada ao público externo.
NÃO aplicar em documentação técnica (ADRs, rules, JSDoc, regras de negócio).
---
# Regra: Escrita Estratégica e Natural

View file

@ -0,0 +1,86 @@
---
type: rule
name: estrutura-vue
description: Padrões obrigatórios de estrutura para componentes Vue 3 (defineComponent, setup, CSS, dispatch)
scope: global
trigger: file_match
globs:
- "**/*.vue"
updatedAt: "2026-06-01"
---
# Regra: Estrutura de Componentes Vue
## 1. `defineComponent` obrigatório
Todo componente Vue deve usar `defineComponent`:
```vue
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
name: 'MeuComponente',
setup() {
// lógica aqui
}
})
</script>
```
## 2. `setup()` não pode ser externo
O `setup()` deve estar sempre dentro do `.vue`. Nunca delegar para arquivo externo.
```ts
// ❌ Proibido — setup esvaziado
export default defineComponent({
setup(props, { emit }) {
return useEliTabela(props, emit) // proxy vazio
}
})
// ✅ Correto — setup com lógica própria
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, mas nunca substituir o `setup()`. Se ultrapassar 250 linhas → extrair subcomponentes, CSS ou Dispatch Table.
## 3. CSS co-localizado
Separação de estilos em arquivo externo quando o CSS contribui para o total de linhas:
```
MeuComponente.vue
MeuComponente.css
```
Importar via `import './MeuComponente.css'` ou `<style src="./MeuComponente.css" />`.
## 4. Dispatch Table
Uso de mapa de funções para eliminar `if/else` ou `switch` longos:
```ts
// handlers.ts
export const acaoHandlers: Record<string, () => void> = {
salvar: () => { ... },
cancelar: () => { ... },
excluir: () => { ... },
}
```
## Impacto
Sem estes padrões, componentes perdem inferência de tipos, lógica fica inauditável em composables externos, e CSS cresce inline sem controle.
## Referências
- Rule: `.agent/rules/limite-linhas.global.md`
- Rule: `.agent/rules/typescript-sem-any.global.md`

View file

@ -0,0 +1,120 @@
---
type: rule
name: fronteiras-monorepo
description: Define a convenção de pacotes e regras de importação entre módulos do monorepo
scope: global
trigger: always_on
updatedAt: "2026-06-01"
---
# Regra: Fronteiras de Pacote no Monorepo
## Contexto
Cada módulo SaaS do ecossistema é organizado em até 4 pacotes dentro de um monorepo. O agente **deve** respeitar as fronteiras de importação entre pacotes para evitar acoplamento indevido.
## Convenção de Pacotes
Cada módulo segue a estrutura:
```
<modulo>-back ← Servidor: rotas, serviços, filas, CRON
<modulo>-front ← Interface: páginas Vue, componentes, composables
<modulo>-comuns ← Compartilhado: tipos, schemas Zod, enums, utils puros
<modulo>-migracao ← Banco de dados: migrations, seeds, scripts de carga
```
Nem todo módulo terá os 4 pacotes. O mínimo funcional é `back` + `front` + `comuns`.
## Regras de Importação
### Permitido ✅
```
<modulo>-front → pode importar de → <modulo>-comuns
<modulo>-back → pode importar de → <modulo>-comuns
<modulo>-migracao → pode importar de → <modulo>-comuns
```
### Proibido ❌
```
<modulo>-front ✗ nunca importar de → <modulo>-back
<modulo>-back ✗ nunca importar de → <modulo>-front
<modulo>-migracao ✗ nunca importar de → <modulo>-front
<modulo>-front ✗ nunca importar de → <outro-modulo>-back
<outro-modulo> ✗ nunca importar de → <modulo>-back (direto)
```
### Comunicação entre módulos
Módulos diferentes **nunca** importam código um do outro diretamente. Comunicação entre módulos deve ocorrer via:
- **API HTTP** (chamada de serviço entre backs)
- **Fila** (BullMQ, Redis) para processamento assíncrono
- **Banco compartilhado** com schemas/tabelas separados por módulo
- **Pacote `*-comuns` compartilhado** apenas para tipos e schemas de contrato
## O que vive em cada pacote
### `<modulo>-comuns`
O pacote comuns é a **única fonte de verdade** para tipos e schemas compartilhados:
```
<modulo>-comuns/src/
├── tabelas/ ← schemas Zod por entidade (zEntidadeCriar, etc.)
├── enums/ ← enums de domínio do módulo
├── tipos/ ← tipos derivados dos schemas
└── utils/ ← funções puras sem side-effect
```
**Proibido** no comuns:
- Componentes Vue ou React
- Código com `import` de Node.js (`fs`, `path`, `http`)
- Código com `import` de bibliotecas de frontend (`vue`, `vuetify`)
- Lógica de negócio que depende de banco ou serviço externo
### `<modulo>-back`
```
<modulo>-back/src/
├── rotas/ ← endpoints Express/Fastify
├── servicos/ ← lógica de negócio
├── banco/ ← queries (Knex, Prisma, etc.)
├── filas/ ← processadores de fila
├── cron/ ← tarefas agendadas
└── utils/ ← helpers exclusivos do back
```
### `<modulo>-front`
```
<modulo>-front/src/
├── paginas/ ← páginas/views por rota
├── componentes/ ← componentes reutilizáveis do módulo
├── composables/ ← lógica reutilizável (use*.ts)
└── utils/ ← helpers exclusivos do front
```
### `<modulo>-migracao`
```
<modulo>-migracao/src/
├── migrations/ ← criação/alteração de tabelas
├── seeds/ ← dados iniciais
└── scripts/ ← scripts de carga/transformação
```
## Impacto
Sem esta regra:
- Front importa serviço de back (quebra em build de produção)
- Módulo A acessa tabelas do Módulo B diretamente (acoplamento oculto)
- Schemas ficam duplicados entre back e front
- Mudança em um módulo quebra outro sem aviso
## Referências
- Rule: `.agent/rules/zod-validacao-padrao.global.md` (schemas no `*-comuns`)
- Skill: `.agent/skills/mapear-codigo-fonte.global/SKILL.md`

View file

@ -0,0 +1,59 @@
---
type: rule
name: jsdoc-padrao
description: Padrões de documentação em código (JSDoc, casts temporários e TODOs estruturados)
scope: global
trigger: file_match
globs:
- "**/*.ts"
- "**/*.tsx"
- "**/*.vue"
updatedAt: "2026-06-01"
---
# Regra: Documentação em Código
## 1. JSDoc — Princípios
- **Por que, não o Quê**: documente o raciocínio, não o mecanismo que o TypeScript já expressa.
- **Nomenclatura limpa**: confie em nomes expressivos antes de comentários.
## 2. JSDoc obrigatório em funções compartilhadas
Toda função central ou de uso compartilhado deve ter cabeçalho JSDoc:
```typescript
/**
* Busca detalhes da entidade e calcula privilégios aplicáveis.
* Não utilizar em processamento em lote para evitar sobrecarga.
*
* @param tenantId - Identificador do tenant no sistema
* @param entityId - ID do registro na base de dados
* @returns Objeto consolidado ou `null` se não encontrado
*/
export const obterDetalhes = async (tenantId: string, entityId: string) => { ... }
```
## 3. Casts temporários — justificativa obrigatória
Coerções agressivas requerem comentário explicando o porquê:
```typescript
// Necessário: biblioteca externa não provê tipagem genérica na versão atual
const dados = payload.raw as unknown as RespostaFormatada
```
## 4. TODOs estruturados
Dívida técnica ou dúvida de negócio deve seguir formato padronizado:
- **Tipagem**: `// TODO de tipagem: Criar interface para o retorno da API`
- **Negócio**: `// TODO de regra: Validar com Produto se taxa se aplica a clientes antigos`
## Impacto
Sem este padrão, funções compartilhadas ficam sem documentação, casts aparecem sem justificativa, e TODOs são inconsistentes e difíceis de buscar.
## Referências
- Rule: `.agent/rules/typescript-sem-any.global.md`

View file

@ -0,0 +1,66 @@
---
type: rule
name: limite-linhas
description: Nenhum arquivo de código pode ultrapassar 250 linhas
scope: global
trigger: file_match
globs:
- "**/*.ts"
- "**/*.tsx"
- "**/*.vue"
- "**/*.css"
updatedAt: "2026-06-01"
---
# Regra: Limite de 250 Linhas por Arquivo
## Regra
Nenhum arquivo `*.vue`, `*.ts`, `*.tsx` ou `*.css` pode ultrapassar **250 linhas**.
> Validação automática: `.agent/verificar-linhas.sh`
## Estratégias de Decomposição
| 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` |
## Divisão em Pasta
Quando um componente precisa de múltiplos sub-arquivos, converter em **pasta homônima** com `index.vue`:
```
# Antes
ComponenteGrande.vue ← monolítico (+250 linhas)
# Depois
ComponenteGrande/
├── index.vue ← orquestração (≤250 linhas)
├── SecaoPrincipal.vue ← bloco de template extraído
├── SecaoSecundaria.vue ← outro bloco extraído
├── componenteGrande.mapeadores.ts ← lógica de eventos
└── ComponenteGrande.css ← estilos co-localizados
```
| Arquivo | Responsabilidade |
|---|---|
| `index.vue` | Orquestração: importa e compõe. Sem lógica densa. |
| `Secao*.vue` | Template isolado. Props + eventos. |
| `*.mapeadores.ts` | Funções de ação desacopladas. |
| `*.css` | Estilos co-localizados. |
> Importações externas não mudam: `ComponenteGrande/` resolve para `index.vue`.
## Impacto
Sem esta regra, arquivos crescem silenciosamente até se tornarem intocáveis — ninguém quer revisar ou refatorar um componente de 800 linhas.
## Referências
- Script: `.agent/verificar-linhas.sh`
- Workflow: `.agent/workflows/otimizar-codigos.global.md`

View file

@ -0,0 +1,123 @@
---
type: rule
name: mensagens-usuario
description: Toda mensagem visível ao usuário final deve ser escrita em linguagem do domínio, sem termos técnicos de TI
scope: global
trigger: file_match
globs:
- "**/*.vue"
- "**/*.ts"
- "**/*.tsx"
updatedAt: "2026-06-01"
---
# Regra: Mensagens Orientadas ao Domínio
## Contexto
O usuário final dos módulos SaaS é profissional de **engenharia, ciência ou gestão ambiental**. Ele entende de legislação, processos técnicos e ciência aplicada — mas **não tem obrigação de entender tecnologia**.
Toda mensagem visível na interface — erro, validação, confirmação, tooltip, empty state, ajuda — deve ser escrita na linguagem do domínio do módulo.
## Regra
### 1. Erros de validação devem indicar O QUE preencher, não O QUE falhou
```typescript
// ❌ ERRADO — linguagem técnica
"Campo obrigatório"
"Erro de validação"
"Formato inválido"
"Payload inválido"
// ✅ CORRETO — linguagem do domínio
"Informe a Área do Empreendimento (em hectares)"
"Selecione o Tipo de Licença: LP, LI ou LO"
"A Data da Vistoria deve ser anterior à data atual"
"Informe o CNPJ no formato 00.000.000/0000-00"
```
### 2. Erros de sistema devem ser traduzidos para o usuário
```typescript
// ❌ ERRADO — erro técnico exposto
"Erro 500: Internal Server Error"
"Timeout na requisição"
"Network Error"
"ECONNREFUSED"
// ✅ CORRETO — mensagem humanizada
"Não foi possível salvar. Tente novamente em instantes."
"A operação demorou mais que o esperado. Tente novamente."
"Sem conexão com o servidor. Verifique sua internet."
```
### 3. Estados vazios devem explicar o contexto
Quando uma lista ou tabela está vazia, a mensagem deve orientar o usuário:
```typescript
// ❌ ERRADO — genérico
"Nenhum registro encontrado"
"Lista vazia"
// ✅ CORRETO — explica o domínio e orienta
"Nenhuma condicionante cadastrada para esta licença."
"Nenhum relatório de monitoramento registrado neste período."
"Nenhum empreendimento encontrado com os filtros aplicados. Tente ampliar a busca."
```
### 4. Confirmações devem descrever a consequência
```typescript
// ❌ ERRADO — genérico
"Tem certeza que deseja excluir?"
"Confirmar operação?"
// ✅ CORRETO — descreve o impacto
"Excluir esta condicionante? O prazo de cumprimento e os documentos vinculados serão removidos."
"Arquivar esta licença? Ela não aparecerá mais nas listagens ativas."
```
### 5. Labels e placeholders devem usar vocabulário do domínio
```typescript
// ❌ ERRADO — genérico
label: "Nome"
placeholder: "Digite aqui"
// ✅ CORRETO — específico do domínio
label: "Razão Social do Empreendedor"
placeholder: "Ex: Mineração Vale Verde Ltda."
label: "Área Total do Empreendimento"
placeholder: "Em hectares (ex: 150,5)"
```
### 6. Schemas Zod devem ter mensagens de domínio
```typescript
// ❌ ERRADO — mensagens padrão do Zod
const zEmpreendimento = z.object({
nome: z.string().min(1), // "String must contain at least 1 character(s)"
area: z.number().positive(), // "Number must be greater than 0"
})
// ✅ CORRETO — mensagens customizadas
const zEmpreendimento = z.object({
nome: z.string().min(1, "Informe o nome do empreendimento"),
area: z.number({ invalid_type_error: "A área deve ser um número" })
.positive("A área deve ser maior que zero"),
cnpj: z.string().regex(/^\d{2}\.\d{3}\.\d{3}\/\d{4}-\d{2}$/,
"CNPJ inválido. Use o formato 00.000.000/0000-00"),
})
```
## Impacto
Sem esta regra, o agente gera mensagens técnicas que confundem gestores ambientais, consultores e técnicos de campo — forçando a equipe de suporte a traduzir erros de sistema para o vocabulário do cliente.
## Referências
- Rule: `.agent/rules/zod-validacao-padrao.global.md` (schemas com mensagens customizadas)
- Workflow: `.agent/workflows/ux.global.md` (design de interface)

View file

@ -0,0 +1,120 @@
---
type: rule
name: regras-negocio-md
description: Arquivo {modulo}.regras-negocio.md obrigatório para módulos com lógica complexa
scope: global
trigger: always_on
updatedAt: "2026-06-01"
---
# Regra: Arquivo `{modulo}.regras-negocio.md` Obrigatório para Módulos com Lógica Complexa
> **Escopo global** — aplica-se a todos os projetos do ecossistema Azteca.
## Contexto
Qualquer arquivo TypeScript que contenha lógica de negócio não-trivial — múltiplas regras de decisão, critérios de bloqueio, estratégias de processamento — deve ter suas regras documentadas em linguagem natural em um arquivo Markdown co-localizado.
Isso garante que produto, suporte e desenvolvimento possam entender as regras sem precisar ler TypeScript, e que o agente de IA encontre rapidamente os critérios corretos antes de alterar qualquer código.
## Regra
**Todo módulo com lógica de negócio não-trivial DEVE ter um arquivo `{modulo}.regras-negocio.md` co-localizado.**
Considera-se lógica não-trivial qualquer módulo que:
- possui 3 ou mais critérios de decisão distintos
- realiza eleição/priorização entre registros
- aplica diferentes estratégias por tipo de dado
- pode resultar em revisão manual (caminho alternativo ao fluxo automático)
- é consumido por CRON, worker ou importador automático
O módulo TypeScript deve conter, na primeira linha do arquivo, a referência:
```typescript
// Regras de negócio: ver {modulo}.regras-negocio.md
```
O arquivo `.md` é a **fonte de verdade**. Os JSDoc do código devem referenciar o `.md`, não duplicá-lo.
## Estrutura Obrigatória do Arquivo
```markdown
# Regras de Negócio — [Nome do Módulo]
Módulo: `_arquivo.ts`
Localização: `caminho/relativo/ao/arquivo`
---
## Contexto
Por que este módulo existe, qual problema resolve.
## Critérios de Entrada / Identificação
O que aciona o módulo, quais dados recebe, filtros aplicados.
## Regras de Processamento
As regras numeradas em ordem de avaliação.
## Casos de Revisão Manual (quando aplicável)
Quando o processamento automático não pode prosseguir.
## Estratégia de Fusão / Resultado (quando aplicável)
O que o módulo produz, como campos são combinados.
## Dependências Externas
Tabelas, filas, serviços externos tocados pelo módulo.
```
## Exemplos
✅ **Correto** — referência no topo do módulo, regras no `.md`:
```typescript
// Regras de negócio: ver juntar_execucoes.regras-negocio.md
import { ... } from "..."
export const compilarJuntarExecucoes = async (...) => {
// implementação conforme regras-negocio.md
}
```
❌ **Errado** — regras embutidas no código como comentário de bloco:
```typescript
/**
* Regras de negócio:
* [x] Pode ter apenas uma execução por data de vencimento.
* [x] Se tiver apenas uma execução consolidada → ela é a principal.
* ...
*/
```
❌ **Errado** — JSDoc duplicando as regras em vez de referenciar o `.md`:
```typescript
/**
* Retorna false quando:
* - vencimentos divergentes
* - executada com datas distintas
* - vencimento_prorrogado distintos
* ...
*/
```
## Impacto
Sem este padrão:
- Regras de negócio ficam implícitas no código ou em JSDoc que envelhece sem controle
- O agente de IA precisa inferir regras do código ao invés de lê-las diretamente
- Produto e suporte não conseguem validar o comportamento sem ajuda técnica
- Mudanças de regra geram JSDoc desatualizado espalhado pelo código
## Aplicação
- Módulos de CRON com lógica de auditoria/fusão
- Importadores automáticos (ex: importação de licenças via IA)
- Workers de processamento em lote
- Qualquer utilitário com lógica de decisão multi-critério
## Referências
- Skill: `.agent/skills/documentar-regras-negocio.global/SKILL.md`
- Workflow: `.agent/workflows/documentar-regras-negocio.global.md`

View file

@ -1,6 +1,10 @@
---
type: rule
name: typescript-sem-any
trigger: always_on
description: Proíbe uso de any, casts inseguros e padroniza uso de enums em código TypeScript
scope: global
updatedAt: "2026-06-01"
globs:
- "**/*.ts"
- "**/*.tsx"
@ -68,6 +72,6 @@ Se encontrar uma string que represente um valor de domínio do sistema, o agente
## Referências
- Skill: `.agent/skills/diagnosticar-erros-build/SKILL.md` (quando erros de tipagem causam falhas de build em cascata)
- Workflow: `.agent/workflows/documentar-codigo.global.md` (padrão de documentação para casts temporários)
- Skill: `.agent/skills/diagnosticar-erros-build.global/SKILL.md` (quando erros de tipagem causam falhas de build em cascata)
- Workflow: `.agent/workflows/otimizar-codigos.global.md` (padrão de documentação para casts temporários)

View file

@ -0,0 +1,315 @@
---
type: rule
name: zod-validacao-padrao
description: Estabelece Zod como biblioteca obrigatória de validação em todo o ecossistema Azteca
version: "1.0"
scope: global
priority: alta
trigger: always_on
globs:
- "**/*.ts"
- "**/*.tsx"
- "**/*.vue"
alwaysApply: true
updatedAt: "2026-06-01"
references:
- .agent/rules/typescript-sem-any.global.md
- .agent/skills/criar-schema-zod.global/SKILL.md
---
# Regra: Zod como Validador Padrão
## Contexto
Todo projeto do ecossistema Azteca que receba dados externos — API, formulário, webhook, importação, fila, variáveis de ambiente — **deve** validar esses dados com [Zod](https://zod.dev).
Zod é a única biblioteca de validação autorizada porque:
- Inferência nativa de tipos TypeScript (`z.infer<>`) — elimina duplicação entre schema e tipo
- Bundle pequeno (~13KB gzipped) — viável no frontend
- Compartilhável entre back e front via pacotes `*-comuns`
- Ecossistema maduro: `zod-to-json-schema`, `@anatine/zod-nestjs`, integração com `tRPC`
---
## Regra
### 1. Toda entrada externa deve ser validada com Zod
Entradas externas incluem:
- **Corpo de requisição** (API REST, webhook, GraphQL)
- **Query params e path params**
- **Formulários de frontend**
- **Dados de fila** (BullMQ, Redis, etc.)
- **Arquivos importados** (CSV, JSON, Excel)
- **Variáveis de ambiente**
- **Respostas de APIs externas** (quando a resposta não é confiável)
### 2. Schemas devem residir no pacote `*-comuns`
Para que front e back compartilhem os mesmos schemas, eles devem residir no pacote `<modulo>-comuns`:
```
<modulo>-comuns/src/
├── tabelas/
│ ├── usuarios/
│ │ ├── zUsuarioCriar.ts
│ │ ├── zUsuarioEditar.ts
│ │ └── index.ts
│ ├── empresas/
│ │ ├── zEmpresaCriar.ts
│ │ └── index.ts
│ └── index.ts
```
### 3. Tipos devem ser derivados dos schemas (nunca o inverso)
```typescript
// ✅ CORRETO — tipo derivado do schema
export const zUsuarioCriar = z.object({
nome: z.string().min(1),
email: z.string().email(),
ativo: z.boolean().default(true),
})
export type tipoUsuarioCriar = z.infer<typeof zUsuarioCriar>
// ❌ ERRADO — tipo manual separado do schema
interface UsuarioCriar {
nome: string
email: string
ativo?: boolean
}
const zUsuarioCriar = z.object({ ... }) // duplicação!
```
### 4. Nomenclatura obrigatória de schemas
| Padrão | Uso | Exemplo |
|--------|-----|---------|
| `z{Entidade}Criar` | Criação de registro | `zUsuarioCriar` |
| `z{Entidade}Editar` | Edição de registro | `zUsuarioEditar` |
| `z{Entidade}Filtro` | Filtros de listagem | `zUsuarioFiltro` |
| `z{Entidade}Resposta` | Resposta de API | `zUsuarioResposta` |
| `z{Entidade}Params` | Parâmetros de rota | `zUsuarioParams` |
| `z{Modulo}Config` | Configuração do módulo | `zGeeConfig` |
| `z{Modulo}Ambiente` | Variáveis de ambiente | `zBackAmbiente` |
Prefixo `z` minúsculo + nome em `PascalCase`.
### 5. Usar `safeParse` em vez de `parse` para erros controlados
```typescript
// ✅ CORRETO — erro controlado
const resultado = zUsuarioCriar.safeParse(dados)
if (!resultado.success) {
return respostaComuns.erro(resultado.error.issues[0].message)
}
const usuario = resultado.data // tipado automaticamente
// ❌ ERRADO — throw não controlado
const usuario = zUsuarioCriar.parse(dados) // lança ZodError
```
A exceção é em contextos onde o throw é desejável (middleware global, início de pipeline).
### 6. Nunca usar validação manual quando Zod resolve
```typescript
// ❌ ERRADO — validação manual
if (!dados.nome || typeof dados.nome !== "string") {
return erro("Nome é obrigatório")
}
if (!dados.email || !dados.email.includes("@")) {
return erro("Email inválido")
}
// ✅ CORRETO — Zod faz tudo
const resultado = zUsuarioCriar.safeParse(dados)
if (!resultado.success) {
return erro(resultado.error.issues[0].message)
}
```
### 7. Transformações e defaults pertencem ao schema
```typescript
// ✅ CORRETO — transformação no schema
export const zProdutoCriar = z.object({
nome: z.string().trim().min(1),
preco: z.coerce.number().positive(),
ativo: z.boolean().default(true),
codigo: z.string().toUpperCase().optional(),
tags: z.array(z.string()).default([]),
})
// ❌ ERRADO — transformação manual após validação
const produto = zProdutoCriar.parse(dados)
produto.nome = produto.nome.trim()
produto.codigo = produto.codigo?.toUpperCase()
```
### 8. Composição de schemas em vez de duplicação
```typescript
// ✅ CORRETO — composição
const zEndereco = z.object({
rua: z.string(),
cidade: z.string(),
uf: z.string().length(2),
})
const zEmpresaCriar = z.object({
nome: z.string(),
endereco: zEndereco,
})
const zEmpresaEditar = zEmpresaCriar.partial() // todos opcionais
const zEmpresaResposta = zEmpresaCriar.extend({
codigo: z.string().uuid(),
criadoEm: z.string().datetime(),
})
// ❌ ERRADO — copiar campos entre schemas
const zEmpresaEditar = z.object({
nome: z.string().optional(), // duplicou!
endereco: z.object({ // duplicou!
rua: z.string().optional(),
cidade: z.string().optional(),
}),
})
```
### 9. Enums Zod para valores de domínio
```typescript
// ✅ CORRETO — enum Zod quando os valores vêm de dados externos
export const zStatusLicenca = z.enum(["ativa", "vencida", "suspensa", "cancelada"])
export type tipoStatusLicenca = z.infer<typeof zStatusLicenca>
// Usar em outros schemas
const zLicencaCriar = z.object({
status: zStatusLicenca,
// ...
})
// ✅ TAMBÉM CORRETO — TypeScript enum quando já existe no projeto
// e o schema referencia o enum
import { StatusLicenca } from "./enums"
const zLicencaCriar = z.object({
status: z.nativeEnum(StatusLicenca),
})
```
### 10. Bibliotecas proibidas para validação
| ❌ Proibido | Motivo |
|-------------|--------|
| `joi` | Não infere tipos TS, bundle pesado |
| `yup` | Inferência fraca, API menos ergonômica |
| `class-validator` | Decorators, não compartilhável com frontend |
| `io-ts` | Verboso, curva de aprendizado alta |
| `ajv` (direto) | JSON Schema — usar `zod-to-json-schema` se precisar de JSON Schema |
| Validação manual com `if/typeof` | Quando Zod resolve o mesmo caso |
---
## Exemplos
### API backend (Express)
```typescript
// ✅ Correto
import { zUsuarioCriar } from "projeto-comuns/tabelas/usuarios"
app.post("/usuarios", async (req, res) => {
const resultado = zUsuarioCriar.safeParse(req.body)
if (!resultado.success) {
return res.status(400).json({ erro: resultado.error.issues })
}
const usuario = await criarUsuario(resultado.data)
return res.json(usuario)
})
```
### Webhook com fila
```typescript
// ✅ Correto — valida antes de enfileirar
const zPayloadWebhook = z.object({
tabela: z.enum(["empreendedores", "empreendimentos", "usuarios"]),
dados: z.array(z.unknown()).min(1),
})
app.post("/webhook/:tabela", async (req, res) => {
const resultado = zPayloadWebhook.safeParse({
tabela: req.params.tabela,
dados: req.body,
})
if (!resultado.success) {
return res.status(400).json({ erro: "Payload inválido" })
}
await fila.add("sync", resultado.data)
return res.json({ ok: true })
})
```
### Variáveis de ambiente
```typescript
// ✅ Correto — validar no boot da aplicação
const zAmbiente = z.object({
DATABASE_URL: z.string().url(),
REDIS_HOST: z.string().default("localhost"),
REDIS_PORT: z.coerce.number().default(6379),
BASE_URL: z.string().url(),
SESSAO_BACK: z.string().uuid(),
NODE_ENV: z.enum(["development", "production", "test"]).default("development"),
})
export const ambiente = () => zAmbiente.parse(process.env)
```
### Frontend (formulário Vue)
```typescript
// ✅ Correto — schema compartilhado validando antes de enviar
import { zUsuarioCriar } from "projeto-comuns/tabelas/usuarios"
const salvar = async () => {
const resultado = zUsuarioCriar.safeParse(formulario.value)
if (!resultado.success) {
erros.value = resultado.error.flatten().fieldErrors
return
}
await api.post("/usuarios", resultado.data)
}
```
---
## Impacto
Sem esta regra:
- Cada desenvolvedor escolhe uma estratégia diferente de validação (if/typeof, joi, yup, etc.)
- Tipos e schemas ficam dessincronizados — mudança no schema não reflete no tipo e vice-versa
- Validações manuais são incompletas e inconsistentes entre endpoints
- Código de validação é duplicado entre front e back
- Dados inválidos passam despercebidos e corrompem a base
Com esta regra:
- Uma única fonte de verdade: o schema Zod
- Tipos TypeScript sempre sincronizados via `z.infer<>`
- Validação consistente em todos os pontos de entrada
- Reutilização entre front, back e workers via pacote `*-comuns`
- Erros de validação estruturados e internacionalizáveis
---
## Referências
- Rule: `.agent/rules/typescript-sem-any.global.md` (narrowing com Zod mencionado como alternativa)
- Skill: `.agent/skills/criar-schema-zod.global/SKILL.md` (guia prático para criar schemas)

View file

@ -0,0 +1,184 @@
---
name: criar-schema-zod
scope: global
description: >
Guia operacional para criar, organizar e manter schemas de validação Zod em projetos TypeScript.
---
# Skill: Criar Schema Zod
Use esta skill ao adicionar validação de dados em qualquer ponto de entrada do sistema — API, formulário, webhook, importação, fila ou variáveis de ambiente.
---
## PASSO 1 — Identificar a Entrada Externa
Determinar qual dado externo precisa de validação:
| Tipo de entrada | Onde validar |
|---|---|
| Corpo de requisição (POST/PUT) | Handler da rota, antes de qualquer lógica |
| Query/path params | Handler da rota |
| Formulário frontend | Antes de chamar a API |
| Dados de fila (BullMQ) | No processador da fila |
| Arquivo importado (CSV/JSON) | No parser, linha a linha |
| Variáveis de ambiente | No boot da aplicação |
| Resposta de API externa | Após receber a resposta |
---
## PASSO 2 — Localizar ou Criar o Schema no Pacote Comuns
Schemas compartilhados vivem no pacote `*-comuns`:
```
projeto-comuns/src/tabelas/
├── usuarios/
│ ├── zUsuarioCriar.ts
│ ├── zUsuarioEditar.ts
│ └── index.ts
└── index.ts
```
### 2.1. Verificar se já existe
```bash
rg "z{NomeDaEntidade}" --include='*.ts' -l
```
Se já existir → reutilizar ou compor a partir dele (`.extend()`, `.pick()`, `.partial()`).
### 2.2. Criar o schema
```typescript
import { z } from "zod"
export const zUsuarioCriar = z.object({
nome: z.string().trim().min(1, "Nome é obrigatório"),
email: z.string().email("E-mail inválido"),
ativo: z.boolean().default(true),
})
```
### 2.3. Nomenclatura obrigatória
| Padrão | Uso |
|---|---|
| `z{Entidade}Criar` | Criação |
| `z{Entidade}Editar` | Edição (geralmente `.partial()`) |
| `z{Entidade}Filtro` | Filtros de listagem |
| `z{Entidade}Resposta` | Resposta de API |
| `z{Entidade}Params` | Parâmetros de rota |
| `z{Modulo}Config` | Configuração |
| `z{Modulo}Ambiente` | Variáveis de ambiente |
---
## PASSO 3 — Derivar o Tipo com `z.infer<>`
```typescript
export type tipoUsuarioCriar = z.infer<typeof zUsuarioCriar>
```
**Nunca** criar um `interface` ou `type` manual separado do schema. O tipo **sempre** deriva do schema.
---
## PASSO 4 — Implementar `safeParse` no Ponto de Entrada
### Backend (Express)
```typescript
import { zUsuarioCriar } from "projeto-comuns/tabelas/usuarios"
app.post("/usuarios", async (req, res) => {
const resultado = zUsuarioCriar.safeParse(req.body)
if (!resultado.success) {
return res.status(400).json({ erro: resultado.error.issues[0].message })
}
const usuario = await criarUsuario(resultado.data)
return res.json(usuario)
})
```
### Frontend (Vue)
```typescript
import { zUsuarioCriar } from "projeto-comuns/tabelas/usuarios"
const salvar = async () => {
const resultado = zUsuarioCriar.safeParse(formulario.value)
if (!resultado.success) {
erros.value = resultado.error.flatten().fieldErrors
return
}
await api.post("/usuarios", resultado.data)
}
```
### Variáveis de ambiente
```typescript
const zAmbiente = z.object({
DATABASE_URL: z.string().url(),
REDIS_HOST: z.string().default("localhost"),
NODE_ENV: z.enum(["development", "production", "test"]).default("development"),
})
// Aqui o throw é desejável — a app não deve subir com env inválido
export const ambiente = () => zAmbiente.parse(process.env)
```
---
## PASSO 5 — Compor Schemas Derivados
Quando precisar de variações do mesmo schema, **compor** em vez de duplicar:
```typescript
// Schema base
const zProduto = z.object({
nome: z.string().trim().min(1),
preco: z.coerce.number().positive(),
categoria: z.string(),
})
// Edição: todos os campos opcionais
const zProdutoEditar = zProduto.partial()
// Resposta: base + campos gerados
const zProdutoResposta = zProduto.extend({
codigo: z.string().uuid(),
criadoEm: z.string().datetime(),
})
// Filtro: subset de campos opcionais
const zProdutoFiltro = zProduto.pick({ categoria: true }).partial()
```
---
## PASSO 6 — Validar Tipagem
```bash
npx tsc --noEmit
```
Zero erros = concluído.
---
## Checklist Final
- [ ] O schema está no pacote `*-comuns` (quando compartilhado entre front/back)?
- [ ] O tipo é derivado do schema com `z.infer<>`?
- [ ] A validação usa `safeParse` (exceto em boot/middleware)?
- [ ] A nomenclatura segue o padrão `z{Entidade}{Acao}`?
- [ ] Transformações (trim, coerce, default) estão no schema, não no handler?
- [ ] Schemas derivados usam composição (.partial, .extend, .pick), não duplicação?
- [ ] `tsc --noEmit` passa sem erros?
## Referências
- Rule: `.agent/rules/zod-validacao-padrao.global.md`
- Rule: `.agent/rules/typescript-sem-any.global.md`

View file

@ -1,6 +1,7 @@
---
name: diagnosticar-erros-build
scope: global
updatedAt: "2026-06-01"
description: >
Guia operacional para diagnosticar e resolver erros de build em cascata em monorepos TypeScript.
---
@ -181,5 +182,5 @@ pnpm install && pnpm build
## Referências
- Rules: `.agent/rules/typescript-sem-any.global.md`
- Workflow: `.agent/workflows/documentar-codigo.global.md`
- Skill: `.agent/skills/mapear-codigo-fonte/SKILL.md`
- Workflow: `.agent/workflows/otimizar-codigos.global.md`
- Skill: `.agent/skills/mapear-codigo-fonte.global/SKILL.md`

View file

@ -1,6 +1,7 @@
---
name: documentar-decisao-tecnica
scope: global
updatedAt: "2026-06-01"
description: >
Guia operacional para registrar decisões técnicas e arquiteturais como ADRs (Architecture Decision Records).
---
@ -157,4 +158,4 @@ Antes de considerar completo:
## Referências
- Workflow: `.agent/workflows/aprimorar-IA.global.md` (seção de classificação de artefatos)
- Workflow: `.agent/workflows/documentar-codigo.global.md`
- Workflow: `.agent/workflows/otimizar-codigos.global.md`

View file

@ -0,0 +1,131 @@
---
name: documentar-dominio-modulo
scope: global
updatedAt: "2026-06-01"
description: >
Guia para documentar o domínio científico/técnico de cada módulo SaaS,
garantindo que o agente compreenda a terminologia e as regras do domínio
ao gerar código, texto e interface.
---
# Skill: Documentar Domínio de um Módulo
Use esta skill ao iniciar um novo módulo SaaS ou ao perceber que o agente não conhece o vocabulário e as regras do domínio que o módulo aborda.
Cada módulo opera em uma disciplina diferente da ciência/gestão ambiental (licenciamento, gases de efeito estufa, resíduos, projetos, etc.). O agente precisa de uma **base factual do domínio** para gerar labels, mensagens, regras de negócio e textos comerciais corretos.
---
## PASSO 1 — Identificar o Domínio do Módulo
Determinar qual área da ciência/gestão o módulo aborda:
| Exemplo de módulo | Domínio |
|---|---|
| Licenciamento | Licenciamento ambiental (LP, LI, LO, condicionantes) |
| GEE | Gases de Efeito Estufa (inventário, emissões, fatores) |
| Resíduos | Gestão de resíduos sólidos (PGRS, MTR, classificação) |
| Outorga | Outorga de uso de recursos hídricos |
| Flora/Fauna | Monitoramento de biodiversidade |
| Projetos | Gestão de projetos ambientais e consultorias |
---
## PASSO 2 — Criar o Arquivo de Domínio
Criar em `.agent/memoria/dominio-<modulo>.md`:
```markdown
# Domínio: <Nome do Módulo>
## Área de Conhecimento
Breve descrição da disciplina (1-3 linhas).
## Glossário do Módulo
| Termo | Definição | Sigla |
|---|---|---|
| Licença Prévia | Autorização para planejamento do empreendimento | LP |
| Licença de Instalação | Autorização para início das obras | LI |
| ... | ... | ... |
## Entidades Principais
Lista das entidades de negócio do módulo com descrição e relações:
- **Empreendimento**: unidade física onde ocorre a atividade potencialmente poluidora
- **Licença**: autorização ambiental vinculada a um empreendimento
- **Condicionante**: obrigação imposta pelo órgão ambiental na licença
- ...
## Legislação Aplicável
| Norma | Descrição | Relevância |
|---|---|---|
| Lei 6.938/1981 | Política Nacional do Meio Ambiente | Base legal do licenciamento |
| Resolução CONAMA 237/1997 | Regulamenta o licenciamento ambiental | Tipos de licença, prazos |
| ... | ... | ... |
## Regras de citação legal
- Formato: "Resolução CONAMA nº 237/1997, art. 8º, inciso I"
- Toda sigla na primeira ocorrência com nome por extenso
- Legislação federal > estadual > municipal (hierarquia)
## Fluxos do Domínio
Descrever os processos-chave que o módulo implementa:
1. Solicitação de licença → Análise técnica → Emissão → Monitoramento
2. Cadastro de condicionante → Acompanhamento de prazo → Cumprimento
## Vocabulário de Interface
Termos que devem ser usados em labels, botões e mensagens:
| Contexto | ❌ Genérico | ✅ Correto para o módulo |
|---|---|---|
| Campo de nome | "Nome" | "Razão Social do Empreendedor" |
| Status | "Ativo/Inativo" | "Vigente/Vencida/Suspensa" |
| Ação principal | "Criar novo" | "Solicitar nova licença" |
| Lista vazia | "Nenhum registro" | "Nenhuma licença cadastrada" |
```
---
## PASSO 3 — Validar com o Operador
Antes de consolidar:
- [ ] Os termos do glossário estão corretos para o contexto do módulo?
- [ ] As entidades cobrem os conceitos centrais?
- [ ] A legislação listada é a principal (não exaustiva)?
- [ ] O vocabulário de interface reflete como o usuário fala no dia a dia?
---
## PASSO 4 — Conectar com as Rules
Após documentar o domínio:
1. Verificar se labels e mensagens no código usam o vocabulário definido (rule `mensagens-usuario.global.md`)
2. Verificar se schemas Zod têm mensagens de erro no idioma do domínio (rule `zod-validacao-padrao.global.md`)
3. Verificar se os arquivos `.ajuda.md` usam termos do glossário (workflow `adicionar-ajuda.global.md`)
4. Verificar se textos comerciais citam a legislação corretamente (workflow `comercial.global.md`)
---
## Checklist Final
- [ ] Arquivo `dominio-<modulo>.md` criado em `.agent/memoria/`?
- [ ] Glossário com pelo menos os 10 termos centrais do módulo?
- [ ] Entidades principais listadas com descrição?
- [ ] Legislação base documentada com formato de citação?
- [ ] Vocabulário de interface definido (labels, status, ações, empty states)?
- [ ] Operador validou a terminologia?
## Referências
- Rule: `.agent/rules/mensagens-usuario.global.md`
- Rule: `.agent/rules/regras-negocio-md.global.md`
- Workflow: `.agent/workflows/comercial.global.md`
- Workflow: `.agent/workflows/adicionar-ajuda.global.md`

View file

@ -1,5 +1,7 @@
---
name: documentar-regras-negocio.global
name: documentar-regras-negocio
scope: global
updatedAt: "2026-06-01"
description: >
Guia operacional para criar, preencher e manter arquivos {modulo}.regras-negocio.md
co-localizados com módulos TypeScript de lógica de negócio complexa.
@ -206,7 +208,5 @@ Percorrer cada regra listada no `.md` e verificar que há código correspondente
## Referências
- Rule: `.agent/rules/regras_negocio_md.global.md`
- Rule: `.agent/rules/regras-negocio-md.global.md`
- Workflow: `.agent/workflows/documentar-regras-negocio.global.md`
- Skill: `.agent/skills/consultar-banco-knex/SKILL.md` (orderBy em whereIn)
- ADR: `.agent/memoria/adr-003-crons-complexos-compilacao-execucao.md`

View file

@ -1,6 +1,7 @@
---
name: mapear-codigo-fonte
scope: global
updatedAt: "2026-06-01"
description: >
Guia operacional para mapear, navegar e compreender a estrutura de um monorepo TypeScript.
---
@ -156,4 +157,4 @@ Descrever como os dados fluem entre pacotes.
## Referências
- Rules: `.agent/rules/typescript-sem-any.global.md`
- Workflow: `.agent/workflows/documentar-codigo.global.md`
- Workflow: `.agent/workflows/otimizar-codigos.global.md`

View file

@ -98,7 +98,7 @@ Para instalar as ferramentas base de forma ágil e em lote, copie os blocos de c
```bash
# 1. Atualizar e instalar ferramentas base do sistema
sudo apt-get update
sudo apt-get install -y ripgrep fd-find jq shellcheck bat tree httpie gh
sudo apt-get install -y ripgrep fd-find jq shellcheck bat tree eza httpie gh
# 2. Instalar ferramentas em Node
npm install -g @ast-grep/cli @biomejs/biome

View file

@ -0,0 +1,41 @@
#!/usr/bin/env sh
# comando sh ../.agent/verificar-linhas.sh || exit 1
LIMITE=250
RAIZ=$(cd "$(dirname "$0")/.." && pwd)
RESULTADO=$(
find "$RAIZ" \
-type d \( \
-name node_modules -o \
-name .git -o \
-name dist -o \
-name build \
\) -prune \
-o \
-type f \( \
-name "*.ts" -o \
-name "*.tsx" -o \
-name "*.vue" -o \
-name "*.css" \
\) \
-print | while IFS= read -r arquivo; do
linhas=$(wc -l < "$arquivo" | tr -d ' ')
if [ "$linhas" -gt "$LIMITE" ]; then
relativo="${arquivo#$RAIZ/}"
printf "%5s linhas -> %s\n" "$linhas" "$relativo"
fi
done
)
if [ -n "$RESULTADO" ]; then
echo "$RESULTADO"
VIOLACOES=$(printf '%s\n' "$RESULTADO" | wc -l | tr -d ' ')
printf "\n❌ %s arquivo(s) violam o limite de %s linhas.\n" "$VIOLACOES" "$LIMITE"
printf "\n Resolva usando o arquivo: .agent/workflows/otimizar-codigos.global.md\n"
exit 1
fi
printf "✅ Todos os arquivos dentro do limite de %s linhas.\n" "$LIMITE"

View file

@ -1,5 +1,9 @@
---
type: workflow
name: adicionar-ajuda
description: Adicionar ou atualizar botão de ajuda e documentação em telas (Frontend)
scope: global
updatedAt: "2026-06-01"
---
# Workflow: Adicionar Ajuda em Telas

View file

@ -1,5 +1,9 @@
---
type: workflow
name: aprimorar-IA
description: Consolidar aprendizados da conversa atual e evoluir continuamente as regras, skills e fluxos da aplicação
scope: global
updatedAt: "2026-06-01"
---
# Workflow: Aprimorar Conhecimento da Aplicação
@ -74,7 +78,7 @@ Para cada item identificado, classificar obrigatoriamente:
### Estrutura obrigatória:
- Um arquivo = um único assunto.
- Nome claro em `snake_case`.
- Nome claro em `kebab-case`.
- Limite máximo: ~10.000 caracteres.
### É obrigatório:
@ -196,105 +200,64 @@ Exemplos de skills de projeto:
Sempre que decisões críticas sobre a arquitetura da aplicação ou o contexto do domínio precisarem ser arquivadas temporária ou permanentemente de maneira descritiva (e não diretamente injetável em código ou regra de lint):
* Registre o novo documento no formato adequado mantendo a pasta limpa em `.agent/memoria/`.
* Exemplo: Manter diário de pendências (`diario_pendencias_modulo_x.md`) ou Arquivo de Registro de Decisão Arquitetural (`adr-001-migracao-nuxt-4.md`).
* Atenção! Tudo que puder ser abstraído para um formato técnico estrito/guiado, deve virar **Skill** ou **Rule**. Use a Memória apenas como extensão de base factual.
* Exemplo: Manter diário de pendências (`diario-pendencias-modulo-x.md`) ou ADR (`adr-001-migracao-nuxt-4.md`).
* Atenção! Tudo que puder ser abstraído para formato técnico estrito deve virar **Skill** ou **Rule**. Use a Memória apenas como extensão de base factual.
---
## 6️⃣ Revisão Global de TODO o Sistema de Conhecimento (OBRIGATÓRIA E INEGOCIÁVEL)
## 6️⃣ Revisão de Impacto (Dirigida por Grafo)
Esta é a etapa mais crítica.
Quando um artefato é alterado, a revisão deve ser **dirigida**, não exaustiva.
Sempre que qualquer alteração ocorrer, é **OBRIGATÓRIO revisar TODO o sistema de conhecimento**, incluindo:
### Processo:
* **TODOS os arquivos em `.agent/rules/`**
* **TODOS os arquivos em `.agent/workflows/`**
* **TODOS os arquivos em `.agent/skills/`**
* **TODOS os arquivos em `.agent/memoria/`**
1. Identificar o artefato alterado
2. Consultar a seção `Referências` do artefato para encontrar dependentes diretos
3. Verificar consistência APENAS entre o artefato alterado e seus dependentes
4. Se uma inconsistência for encontrada, propagar para o próximo nível
### Regras de escopo:
| Se alterou... | Revisar... |
|---|---|
| Uma **Rule** | Workflows e skills que a **referenciam** |
| Um **Workflow** | Skills que o **referenciam** |
| Uma **Skill** | Rules e workflows na seção Referências |
| **Memória/ADR** | Nada — memória é registro, não dependência |
### É PROIBIDO:
* Atualizar apenas um arquivo isolado.
* Assumir que o impacto é local.
* Ignorar arquivos existentes.
* Criar artefatos sem validar o restante do sistema.
* Revisar artefatos **sem relação** com a mudança
* Afirmar que "tudo foi revisado" sem listar quais artefatos foram verificados
* Criar artefatos sem verificar se já existe um com o mesmo escopo
### A revisão DEVE verificar:
* Não existem regras conflitantes entre o artefato alterado e seus dependentes
* Referências cruzadas continuam válidas (arquivos existem, nomes corretos)
* Skills refletem fielmente as rules que referenciam
* Não há duplicação de conteúdo entre artefatos
---
### A revisão DEVE obrigatoriamente verificar:
#### Consistência
* Não existem regras conflitantes.
* Não existem interpretações ambíguas.
* Skills refletem fielmente as rules e workflows atuais.
#### Atualização
* Nenhuma rule está desatualizada.
* Nenhum workflow usa regra antiga.
* Nenhuma skill contém padrões obsoletos.
#### Não duplicação
* Não existem regras repetidas em arquivos diferentes.
* Conteúdos redundantes foram consolidados.
* Skills referenciam rules (não copiam o conteúdo).
#### Propagação de impacto
* Toda mudança foi refletida em:
* Rules relacionadas
* Workflows relacionados
* Skills relacionadas
* Memória afetada pela mudança (ADRs, Diários)
#### Coerência estrutural
* Nomes de arquivos continuam válidos.
* Assuntos estão bem separados.
* Arquivos grandes foram divididos.
* Skills estão organizadas em diretórios individuais.
---
### Regras obrigatórias desta etapa:
* **Toda alteração é considerada global até prova contrária.**
* **Se uma rule muda, todos os workflows e skills relacionados DEVEM ser revisados.**
* **Se um workflow muda, todas as rules e skills relacionadas DEVEM ser revisadas.**
* **Se uma skill muda, as rules e workflows que ela referencia DEVEM ser verificados.**
* **Atualizações de contexto devem garantir que toda a memória conflitante seja suprimida ou tagueada.**
* **Nenhuma inconsistência pode permanecer.**
* **Nenhuma duplicação é aceitável.**
---
## 7️⃣ Verificação Final (Checklist obrigatório)
## 7⃣ Verificação Final (Checklist obrigatório)
Só é permitido encerrar se TODOS forem verdadeiros:
* [ ] Todas as rules foram revisadas
* [ ] Todos os workflows foram revisados
* [ ] Todas as skills foram revisadas
* [ ] Não existe conflito entre artefatos
* [ ] Artefatos dependentes do alterado foram revisados
* [ ] Não existe conflito entre artefatos revisados
* [ ] Não existe duplicação de conteúdo
* [ ] Nenhuma regra ficou implícita
* [ ] Todas as mudanças foram propagadas (rules ↔ workflows ↔ skills)
* [ ] Referências cruzadas estão válidas
* [ ] Estrutura está organizada por assunto
* [ ] Nenhum arquivo excede ~10k caracteres
* [ ] Workflows refletem fielmente as rules atuais
* [ ] Skills referenciam (não duplicam) rules
* [ ] O sistema está consistente como um todo
---
## ⚠️ Regra Suprema
> Se existir qualquer dúvida, conflito, duplicação ou inconsistência,
> Se existir qualquer dúvida, conflito ou inconsistência
> entre o artefato alterado e seus dependentes,
> o trabalho é considerado **INCOMPLETO**.
Não existe atualização parcial.
Não existe impacto local.
Não existe exceção.
A revisão é dirigida, não opcional.

View file

@ -1,5 +1,9 @@
---
type: workflow
name: atualizar-agente
description: Aplica atualizações globais para o agente de IA
scope: global
updatedAt: "2026-06-01"
---
# Workflow: Atualizar Agente Global

View file

@ -1,5 +1,9 @@
---
type: workflow
name: comercial
description: Criar uma descrição comercial das funcionalidades e do sistema
scope: global
updatedAt: "2026-06-01"
---
# Workflow: Criar Descrição Comercial
@ -137,4 +141,4 @@ O arquivo `./COMERCIAL/<nome-do-modulo>.md` deve seguir esta estrutura:
- Regra global: `.agent/rules/escrita-natural.global.md` (padrão obrigatório de redação)
- Workflow: `.agent/workflows/orcamento.global.md` (texto para documentos de orçamento e proposta — apresentação + lista de funcionalidades)
- Workflow: `.agent/workflows/marketing.global.md` (peças de marketing digital — LinkedIn, Instagram, WhatsApp, E-mail)
- Workflow: `.agent/workflows/documentar-codigo.global.md` (para documentação técnica, não comercial)
- Workflow: `.agent/workflows/otimizar-codigos.global.md` (para documentação técnica, não comercial)

View file

@ -1,5 +1,9 @@
---
type: workflow
name: criar-fluxograma
description: Criar ou atualizar uma página de fluxograma interativo em qualquer sistema
scope: global
updatedAt: "2026-06-01"
---
# Workflow: Criar Fluxograma de Decisões
@ -181,5 +185,5 @@ Caso contrário, seguir os passos acima para criar a primeira implementação.
## Referências
- Workflow: `.agent/workflows/documentar-codigo.global.md`
- Skill: `.agent/skills/documentar-decisao-tecnica/SKILL.md` (para decisões que geram fluxogramas)
- Workflow: `.agent/workflows/otimizar-codigos.global.md`
- Skill: `.agent/skills/documentar-decisao-tecnica.global/SKILL.md` (para decisões que geram fluxogramas)

View file

@ -1,5 +1,9 @@
---
type: workflow
name: documentar-regras-negocio
description: Criar ou atualizar o arquivo {modulo}.regras-negocio.md co-localizado com um módulo TypeScript e garantir consistência entre código e documentação
scope: global
updatedAt: "2026-06-01"
---
# Workflow: Documentar Regras de Negócio de um Módulo
@ -86,7 +90,7 @@ Para cada regra listada no `.md`:
// turbo
```bash
cd e-licencie-back && node_modules/.bin/tsc --noEmit
npx tsc --noEmit
```
Zero erros = concluído.

View file

@ -1,5 +1,9 @@
---
type: workflow
name: marketing
description: Criar peças de marketing digital para funcionalidades e módulos do sistema
scope: global
updatedAt: "2026-06-01"
---
# Workflow: Criar Peças de Marketing

View file

@ -1,5 +1,9 @@
---
type: workflow
name: orcamento
description: Criar texto de orçamento pronto para copiar e colar em peças de orçamento/proposta
scope: global
updatedAt: "2026-06-01"
---
# Workflow: Criar Texto de Orçamento

View file

@ -1,221 +1,76 @@
---
description: Otimizar código e documentar padrões de qualidade (estrutura, limites, JSDoc e tipagem)
type: workflow
name: otimizar-codigos
description: Processo de refatoração e validação de qualidade de código
scope: global
updatedAt: "2026-06-01"
---
# Workflow: Qualidade e Documentação de Código
# Workflow: Qualidade e Otimizaçã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`.
Aplicar as rules de qualidade do ecossistema Azteca em código existente ou novo, garantindo que todos os padrões sejam cumpridos antes de finalizar uma tarefa.
---
## 1️⃣ Limite de Linhas por Arquivo
## Passos
Nenhum arquivo `*.vue`, `*.ts` ou `*.tsx` pode ultrapassar **250 linhas**.
### 1. Verificar limite de 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>
```bash
sh .agent/verificar-linhas.sh
```
**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
Se houver violações, aplicar estratégias da rule `limite-linhas.global.md`:
- Extrair subcomponentes, mover CSS, usar Dispatch Table
- Se necessário, converter em pasta com `index.vue`
---
### 2. Validar estrutura Vue
### `setup()` não pode ser externo
Para cada componente `.vue` modificado, verificar conforme `estrutura-vue.global.md`:
- `defineComponent` presente?
- `setup()` contém lógica própria (não delegada)?
- CSS extenso foi movido para arquivo co-localizado?
O `setup()` deve estar sempre dentro do componente `.vue`. Não é permitido extrair ou delegar o `setup` para nenhum arquivo externo.
### 3. Documentar código
```ts
// ❌ Proibido — setup delegado inteiramente para arquivo externo
// useEliTabela.ts
export function useEliTabela(props, emit) { ... }
Para cada função compartilhada criada/modificada, aplicar `jsdoc-padrao.global.md`:
- JSDoc com `@param` e `@returns`?
- Casts temporários têm comentário justificando?
- TODOs no formato padronizado?
// EliTabela.vue
export default defineComponent({
setup(props, { emit }) {
return useEliTabela(props, emit) // setup é apenas um proxy vazio
}
})
### 4. Validar tipagem
```bash
npx tsc --noEmit
```
```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 }
}
})
```
Zero erros = prosseguir. Se houver erros, aplicar `typescript-sem-any.global.md` — nunca resolver com `any`.
> 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`.
### 5. Formatar
---
### 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" />
```bash
npx biome check --write .
```
---
### Dispatch Table em Arquivo Separado
## Checklist Final
O uso de Dispatch Table (mapa de funções) em arquivos separados é bem-vindo para eliminar blocos longos de `if/else` ou `switch`.
- [ ] `verificar-linhas.sh` passa sem violações?
- [ ] Componentes Vue usam `defineComponent`?
- [ ] Funções compartilhadas têm JSDoc?
- [ ] Casts temporários estão justificados?
- [ ] `tsc --noEmit` zero erros?
- [ ] `biome check` sem problemas?
```ts
// handlers.ts
export const acaoHandlers: Record<string, () => void> = {
salvar: () => { ... },
cancelar: () => { ... },
excluir: () => { ... },
}
```
## Rules Aplicadas
```ts
// MeuComponente.vue
import { acaoHandlers } from './handlers'
Este workflow orquestra a verificação das seguintes rules:
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)
- `.agent/rules/limite-linhas.global.md` — máximo 250 linhas
- `.agent/rules/estrutura-vue.global.md` — defineComponent, setup, CSS, dispatch
- `.agent/rules/jsdoc-padrao.global.md` — JSDoc, casts, TODOs
- `.agent/rules/typescript-sem-any.global.md` — proibição de any
- `.agent/rules/zod-validacao-padrao.global.md` — validação de entradas externas

View file

@ -1,5 +1,9 @@
---
type: workflow
name: reels
description: Criar cadeia de roteiros de Reels para divulgação de funcionalidades e módulos
scope: global
updatedAt: "2026-06-01"
---
# Workflow: Criar Roteiros de Reels

View file

@ -1,5 +1,9 @@
---
type: workflow
name: ux
description: Revisar e otimizar UX de componentes e telas Vue (Frontend)
scope: global
updatedAt: "2026-06-01"
---
# Workflow: Revisão de UX
@ -191,44 +195,8 @@ import { Filter, Plus, X, Trash2, Eye } from "@lucide/vue"
**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.
> Os tokens CSS estão documentados nas tabelas da seção **Paleta de Cores Institucional** acima.
> No código, usar `var(--token, #fallback)` — sempre com fallback hardcoded em componentes de biblioteca.
- Classes devem seguir BEM: `eli-componente__elemento--modificador`
- Evitar `!important` — resolver especificidade via seletor mais específico

6271
dist/eli-vue.es.js vendored

File diff suppressed because one or more lines are too long

104
dist/eli-vue.umd.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

View file

@ -187,14 +187,14 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
};
}>> & Readonly<{
onInput?: ((_v: string | null) => any) | undefined;
"onUpdate:modelValue"?: ((_v: string | null) => any) | undefined;
onBlur?: (() => any) | undefined;
"onUpdate:value"?: ((_v: string | null) => any) | undefined;
onChange?: ((_v: string | null) => any) | undefined;
"onUpdate:modelValue"?: ((_v: string | null) => any) | undefined;
onFocus?: (() => 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;
@ -206,6 +206,7 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
max: string | undefined;
densidade: CampoDensidade;
variante: CampoVariante;
desabilitado: boolean;
opcoes: {
rotulo: string;
placeholder?: string;
@ -225,5 +226,4 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
modelValue: string | null;
rotulo: string;
placeholder: string;
desabilitado: boolean;
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;

View file

@ -16,7 +16,7 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
};
}>, {
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);
emit: ((event: "input", _v: number | null | undefined) => void) & ((event: "blur") => void) & ((event: "update:value", _v: number | null | undefined) => void) & ((event: "change", _v: number | null | undefined) => void) & ((event: "focus") => void);
displayValue: import("vue").Ref<string, string>;
isInteiro: import("vue").ComputedRef<boolean>;
onUpdateModelValue: (texto: string) => void;
@ -39,10 +39,10 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
};
}>> & Readonly<{
onInput?: ((_v: number | null | undefined) => any) | undefined;
onBlur?: (() => 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>;

View file

@ -14,7 +14,7 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
};
}>, {
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);
emit: ((event: "input", _v: string | null | undefined) => void) & ((event: "blur") => void) & ((event: "update:value", _v: string | null | undefined) => void) & ((event: "change", _v: string | null | undefined) => void) & ((event: "focus") => void);
localValue: import("vue").WritableComputedRef<string | null | undefined, string | null | undefined>;
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
"update:value": (_v: EntradaParagrafo["value"]) => true;
@ -33,10 +33,10 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
};
}>> & Readonly<{
onInput?: ((_v: string | null | undefined) => any) | undefined;
onBlur?: (() => 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;
}, {}, {
@ -1290,7 +1290,7 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
} & import("vue").ComponentCustomProperties & import("vuetify/lib/util/defineComponent.mjs").GenericProps<{
modelValue?: unknown;
"onUpdate:modelValue"?: ((value: unknown) => void) | undefined;
}, import("vuetify/lib/components/VInput/VInput.mjs").VInputSlots>, "name" | "color" | "disabled" | "error" | "label" | "style" | "class" | keyof import("vue").VNodeProps | "modelValue" | "onUpdate:modelValue" | "centerAffix" | "density" | "direction" | "errorMessages" | "focused" | "glow" | "hideSpinButtons" | "maxErrors" | "messages" | "persistentHint" | "readonly" | "rules" | "theme" | "onUpdate:focused" | "validateOn" | "validationValue" | "maxWidth" | "minWidth" | "width" | "id" | "appendIcon" | "baseColor" | "iconColor" | "prependIcon" | "hideDetails" | "hint" | "onClick:prepend" | "onClick:append" | "$children" | "v-slot:append" | "v-slot:default" | "v-slot:details" | "v-slot:message" | "v-slot:prepend" | "v-slots">, `$${any}`> & {
}, import("vuetify/lib/components/VInput/VInput.mjs").VInputSlots>, "name" | "color" | "disabled" | "error" | "label" | "style" | "class" | keyof import("vue").VNodeProps | "centerAffix" | "density" | "direction" | "errorMessages" | "focused" | "glow" | "hideSpinButtons" | "maxErrors" | "messages" | "persistentHint" | "readonly" | "rules" | "theme" | "onUpdate:focused" | "validateOn" | "validationValue" | "maxWidth" | "minWidth" | "width" | "id" | "appendIcon" | "baseColor" | "iconColor" | "prependIcon" | "hideDetails" | "hint" | "onClick:prepend" | "onClick:append" | "$children" | "modelValue" | "onUpdate:modelValue" | "v-slot:append" | "v-slot:default" | "v-slot:details" | "v-slot:message" | "v-slot:prepend" | "v-slots">, `$${any}`> & {
_allExposed: {
reset: () => Promise<void>;
resetValidation: () => Promise<void>;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -15,7 +15,7 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
};
}>, {
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);
emit: ((event: "input", _v: string | null | undefined) => void) & ((event: "blur") => void) & ((event: "update:value", _v: string | null | undefined) => void) & ((event: "change", _v: string | null | undefined) => void) & ((event: "focus") => void);
localValue: import("vue").WritableComputedRef<string | null | undefined, string | null | undefined>;
inputHtmlType: import("vue").ComputedRef<"text" | "email" | "url">;
inputMode: import("vue").ComputedRef<string | undefined>;
@ -39,10 +39,10 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
};
}>> & Readonly<{
onInput?: ((_v: string | null | undefined) => any) | undefined;
onBlur?: (() => 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>;

View file

@ -1,7 +1,9 @@
import EliEntradaConfirmacao from "./EliEntradaConfirmacao.vue";
import EliEntradaDataHora from "./EliEntradaDataHora.vue";
import EliEntradaNumero from "./EliEntradaNumero.vue";
import EliEntradaParagrafo from "./EliEntradaParagrafo.vue";
import EliEntradaSelecao from "./EliEntradaSelecao.vue";
import EliEntradaSelecaoMultipla from "./EliEntradaSelecaoMultipla.vue";
import EliEntradaTexto from "./EliEntradaTexto.vue";
export type { PadroesEntradas, TipoEntrada } from "./tiposEntradas";
export { EliEntradaDataHora, EliEntradaNumero, EliEntradaParagrafo, EliEntradaSelecao, EliEntradaTexto, };
export { EliEntradaConfirmacao, EliEntradaDataHora, EliEntradaNumero, EliEntradaParagrafo, EliEntradaSelecao, EliEntradaSelecaoMultipla, EliEntradaTexto, };

File diff suppressed because one or more lines are too long

View file

@ -131,6 +131,45 @@ export type PadroesEntradas = {
/** Variante do v-text-field (Vuetify). */
variante?: import("../../tipos").CampoVariante;
}>;
selecaoMultipla: tipoPadraoEntrada<string[] | null | undefined, {
/**
* Carrega os itens da seleção (sincrono ou async).
* - Cada item precisa ter uma chave estável (value) e um rótulo (title).
*/
itens: () => {
chave: string;
rotulo: string;
}[] | Promise<{
chave: string;
rotulo: string;
}[]>;
/** Se true, mostra ícone para limpar o valor (Vuetify clearable). */
limpavel?: boolean;
/** Estado de erro (visual). */
erro?: boolean;
/** Mensagens de erro. */
mensagensErro?: string | string[];
/** Texto de apoio. */
dica?: string;
/** Mantém a dica sempre visível. */
dicaPersistente?: boolean;
/** Densidade do campo (Vuetify). */
densidade?: import("../../tipos").CampoDensidade;
/** Variante do v-text-field (Vuetify). */
variante?: import("../../tipos").CampoVariante;
}>;
confirmacao: tipoPadraoEntrada<boolean, {
/** Cor do switch quando ativo (Vuetify color). */
cor?: string;
/** Se true, desabilita a interação. */
desabilitado?: boolean;
/** Texto exibido ao lado do switch quando ativo. */
rotuloAtivo?: string;
/** Texto exibido ao lado do switch quando inativo. */
rotuloInativo?: string;
/** Densidade do campo (Vuetify). */
densidade?: import("../../tipos").CampoDensidade;
}>;
};
/**
* União dos tipos de entrada suportados.

View file

@ -96,6 +96,10 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
modalFiltroAberto: import("vue").Ref<boolean, boolean>;
exibirFiltroAvancado: import("vue").ComputedRef<boolean>;
filtrosAvancadosAtivos: import("vue").ComputedRef<import("p-comuns").tipoFiltroDeltaColunaTipada<any>[]>;
filtrosAtivosResumo: import("vue").ComputedRef<{
quantidade: number;
rotulos: string[];
}>;
abrirModalFiltro: () => void;
fecharModalFiltro: () => void;
limparFiltrosAvancados: () => void;
@ -191,6 +195,17 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
required: false;
default: boolean;
};
filtrosAtivosResumo: {
type: PropType<{
quantidade: number;
rotulos: string[];
}>;
required: false;
default: () => {
quantidade: number;
rotulos: never[];
};
};
valorBusca: {
type: StringConstructor;
required: true;
@ -210,6 +225,7 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
};
}>, {
temAcoesCabecalho: import("vue").ComputedRef<boolean>;
tooltipFiltro: import("vue").ComputedRef<string>;
emitBuscar: (texto: string) => void;
emitColunas: () => void;
emitFiltroAvancado: () => void;
@ -232,6 +248,17 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
required: false;
default: boolean;
};
filtrosAtivosResumo: {
type: PropType<{
quantidade: number;
rotulos: string[];
}>;
required: false;
default: () => {
quantidade: number;
rotulos: never[];
};
};
valorBusca: {
type: StringConstructor;
required: true;
@ -256,6 +283,10 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
}>, {
exibirBotaoColunas: boolean;
exibirBotaoFiltroAvancado: boolean;
filtrosAtivosResumo: {
quantidade: number;
rotulos: string[];
};
}, {}, {
Columns3: import("@lucide/vue").LucideIcon;
EliTabelaCaixaDeBusca: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
@ -995,6 +1026,31 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
dicaPersistente?: boolean | undefined;
densidade?: import("../..").CampoDensidade | undefined;
variante?: import("../..").CampoVariante | undefined;
}] | readonly ["selecaoMultipla", {
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;
}] | readonly ["confirmacao", {
rotulo: string;
placeholder?: string | undefined;
cor?: string | undefined;
desabilitado?: boolean | undefined;
rotuloAtivo?: string | undefined;
rotuloInativo?: string | undefined;
densidade?: import("../..").CampoDensidade | undefined;
}];
valor: unknown;
}[], import("./useFiltroAvancado").LinhaFiltro[] | {
@ -1052,357 +1108,44 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
dicaPersistente?: boolean | undefined;
densidade?: import("../..").CampoDensidade | undefined;
variante?: import("../..").CampoVariante | undefined;
}] | readonly ["selecaoMultipla", {
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;
}] | readonly ["confirmacao", {
rotulo: string;
placeholder?: string | undefined;
cor?: string | undefined;
desabilitado?: boolean | undefined;
rotuloAtivo?: string | undefined;
rotuloInativo?: string | undefined;
densidade?: import("../..").CampoDensidade | 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;
filtro: (valor: unknown) => Pick<import("p-comuns").tipoCondicaoFiltroDeltaTipado<"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">, "valor" | "coluna" | "operador"> | undefined;
entrada: import("../EliEntrada/tiposEntradas").ComponenteEntrada;
}[]>;
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<{
value: {
type: PropType<string | null | undefined>;
default: undefined;
};
opcoes: {
type: 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: PropType<string | null>;
default: null;
};
modo: {
type: 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: PropType<string | string[]>;
default: undefined;
};
dica: {
type: StringConstructor;
default: undefined;
};
dicaPersistente: {
type: BooleanConstructor;
default: undefined;
};
densidade: {
type: PropType<import("../..").CampoDensidade>;
default: undefined;
};
variante: {
type: PropType<import("../..").CampoVariante>;
default: undefined;
};
min: {
type: PropType<string | undefined>;
default: undefined;
};
max: {
type: 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: PropType<string | null | undefined>;
default: undefined;
};
opcoes: {
type: 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: PropType<string | null>;
default: null;
};
modo: {
type: 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: PropType<string | string[]>;
default: undefined;
};
dica: {
type: StringConstructor;
default: undefined;
};
dicaPersistente: {
type: BooleanConstructor;
default: undefined;
};
densidade: {
type: PropType<import("../..").CampoDensidade>;
default: undefined;
};
variante: {
type: PropType<import("../..").CampoVariante>;
default: undefined;
};
min: {
type: PropType<string | undefined>;
default: undefined;
};
max: {
type: 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: PropType<number | null | undefined>;
default: undefined;
};
opcoes: {
type: 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: import("vue").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: PropType<number | null | undefined>;
default: undefined;
};
opcoes: {
type: 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: PropType<string | null | undefined>;
default: undefined;
};
opcoes: {
type: 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: PropType<string | null | undefined>;
default: undefined;
};
opcoes: {
type: 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>;
componenteEntrada: (entrada: import("../EliEntrada/tiposEntradas").ComponenteEntrada) => import("vue").Component;
opcoesEntrada: (entrada: import("../EliEntrada/tiposEntradas").ComponenteEntrada) => {
rotulo: string;
};

View file

@ -16,6 +16,17 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
required: false;
default: boolean;
};
filtrosAtivosResumo: {
type: PropType<{
quantidade: number;
rotulos: string[];
}>;
required: false;
default: () => {
quantidade: number;
rotulos: never[];
};
};
valorBusca: {
type: StringConstructor;
required: true;
@ -35,6 +46,7 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
};
}>, {
temAcoesCabecalho: import("vue").ComputedRef<boolean>;
tooltipFiltro: import("vue").ComputedRef<string>;
emitBuscar: (texto: string) => void;
emitColunas: () => void;
emitFiltroAvancado: () => void;
@ -57,6 +69,17 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
required: false;
default: boolean;
};
filtrosAtivosResumo: {
type: PropType<{
quantidade: number;
rotulos: string[];
}>;
required: false;
default: () => {
quantidade: number;
rotulos: never[];
};
};
valorBusca: {
type: StringConstructor;
required: true;
@ -81,6 +104,10 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
}>, {
exibirBotaoColunas: boolean;
exibirBotaoFiltroAvancado: boolean;
filtrosAtivosResumo: {
quantidade: number;
rotulos: string[];
};
}, {}, {
Columns3: import("@lucide/vue").LucideIcon;
EliTabelaCaixaDeBusca: import("vue").DefineComponent<import("vue").ExtractPropTypes<{

View file

@ -81,6 +81,31 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
dicaPersistente?: boolean | undefined;
densidade?: import("../..").CampoDensidade | undefined;
variante?: import("../..").CampoVariante | undefined;
}] | readonly ["selecaoMultipla", {
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;
}] | readonly ["confirmacao", {
rotulo: string;
placeholder?: string | undefined;
cor?: string | undefined;
desabilitado?: boolean | undefined;
rotuloAtivo?: string | undefined;
rotuloInativo?: string | undefined;
densidade?: import("../..").CampoDensidade | undefined;
}];
valor: unknown;
}[], import("./useFiltroAvancado").LinhaFiltro[] | {
@ -138,357 +163,44 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
dicaPersistente?: boolean | undefined;
densidade?: import("../..").CampoDensidade | undefined;
variante?: import("../..").CampoVariante | undefined;
}] | readonly ["selecaoMultipla", {
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;
}] | readonly ["confirmacao", {
rotulo: string;
placeholder?: string | undefined;
cor?: string | undefined;
desabilitado?: boolean | undefined;
rotuloAtivo?: string | undefined;
rotuloInativo?: string | undefined;
densidade?: import("../..").CampoDensidade | 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;
filtro: (valor: unknown) => Pick<import("p-comuns").tipoCondicaoFiltroDeltaTipado<"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">, "valor" | "coluna" | "operador"> | undefined;
entrada: import("../EliEntrada/tiposEntradas").ComponenteEntrada;
}[]>;
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<{
value: {
type: PropType<string | null | undefined>;
default: undefined;
};
opcoes: {
type: 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: PropType<string | null>;
default: null;
};
modo: {
type: 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: PropType<string | string[]>;
default: undefined;
};
dica: {
type: StringConstructor;
default: undefined;
};
dicaPersistente: {
type: BooleanConstructor;
default: undefined;
};
densidade: {
type: PropType<import("../..").CampoDensidade>;
default: undefined;
};
variante: {
type: PropType<import("../..").CampoVariante>;
default: undefined;
};
min: {
type: PropType<string | undefined>;
default: undefined;
};
max: {
type: 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: PropType<string | null | undefined>;
default: undefined;
};
opcoes: {
type: 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: PropType<string | null>;
default: null;
};
modo: {
type: 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: PropType<string | string[]>;
default: undefined;
};
dica: {
type: StringConstructor;
default: undefined;
};
dicaPersistente: {
type: BooleanConstructor;
default: undefined;
};
densidade: {
type: PropType<import("../..").CampoDensidade>;
default: undefined;
};
variante: {
type: PropType<import("../..").CampoVariante>;
default: undefined;
};
min: {
type: PropType<string | undefined>;
default: undefined;
};
max: {
type: 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: PropType<number | null | undefined>;
default: undefined;
};
opcoes: {
type: 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: import("vue").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: PropType<number | null | undefined>;
default: undefined;
};
opcoes: {
type: 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: PropType<string | null | undefined>;
default: undefined;
};
opcoes: {
type: 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: PropType<string | null | undefined>;
default: undefined;
};
opcoes: {
type: 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>;
componenteEntrada: (entrada: import("../EliEntrada/tiposEntradas").ComponenteEntrada) => import("vue").Component;
opcoesEntrada: (entrada: import("../EliEntrada/tiposEntradas").ComponenteEntrada) => {
rotulo: string;
};

View file

@ -1,5 +1,5 @@
import type { LucideIcon } from "@lucide/vue";
import type { tipoFiltroDeltaColunaTipada } from "p-comuns";
import type { tipoCondicaoFiltroDeltaTipado, tipoFiltroDeltaColunaTipada } from "p-comuns";
import type { tipoResposta } from "p-respostas";
import type { ComponenteEntrada } from "../EliEntrada/tiposEntradas";
import type { tiposTabelaCelulas, tipoTabelaCelula } from "./celulas/tiposTabelaCelulas";
@ -136,8 +136,8 @@ export type tipoEliTabelaConsulta<T> = {
chave: string;
/** Rótulo exibido no select de adicionar filtro */
rotulo: string;
/** vai gerar filtro e mescla com os filtros já existentes */
filtro: (valor: unknown) => tipoFiltroDeltaColunaTipada<Extract<keyof T, string>> | undefined;
/** vai gerar filtro e mescla com os filtros já existentes , id é gerado de forma altomatica para esse local*/
filtro: (valor: unknown) => Pick<tipoCondicaoFiltroDeltaTipado<Extract<keyof T, string>>, "coluna" | "operador" | "valor"> | undefined;
/** componente de entrada que vai receber o valor para o filtro */
entrada: ComponenteEntrada;
}[];

View file

@ -70,6 +70,10 @@ export declare function useEliTabela(props: {
modalFiltroAberto: import("vue").Ref<boolean, boolean>;
exibirFiltroAvancado: import("vue").ComputedRef<boolean>;
filtrosAvancadosAtivos: import("vue").ComputedRef<import("p-comuns").tipoFiltroDeltaColunaTipada<any>[]>;
filtrosAtivosResumo: import("vue").ComputedRef<{
quantidade: number;
rotulos: string[];
}>;
abrirModalFiltro: () => void;
fecharModalFiltro: () => void;
limparFiltrosAvancados: () => void;

View file

@ -37,6 +37,10 @@ export declare function useEliTabelaFiltrosAvancados(props: PropsFiltros, pagina
modalFiltroAberto: Ref<boolean, boolean>;
exibirFiltroAvancado: import("vue").ComputedRef<boolean>;
filtrosAvancadosAtivos: import("vue").ComputedRef<tipoFiltroDeltaColunaTipada<any>[]>;
filtrosAtivosResumo: import("vue").ComputedRef<{
quantidade: number;
rotulos: string[];
}>;
abrirModalFiltro: () => void;
fecharModalFiltro: () => void;
limparFiltrosAvancados: () => void;

View file

@ -10,7 +10,7 @@
*
* Extraído de `EliTabelaModalFiltroAvancado.vue` para manter o componente abaixo de 250 linhas.
*/
import { type Ref } from "vue";
import { type Component, 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];
@ -112,6 +112,31 @@ export declare function useFiltroAvancado({ aberto, filtrosBase, modelo, modeloF
dicaPersistente?: boolean | undefined;
densidade?: import("../..").CampoDensidade | undefined;
variante?: import("../..").CampoVariante | undefined;
}] | readonly ["selecaoMultipla", {
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;
}] | readonly ["confirmacao", {
rotulo: string;
placeholder?: string | undefined;
cor?: string | undefined;
desabilitado?: boolean | undefined;
rotuloAtivo?: string | undefined;
rotuloInativo?: string | undefined;
densidade?: import("../..").CampoDensidade | undefined;
}];
valor: unknown;
}[], LinhaFiltro[] | {
@ -169,357 +194,44 @@ export declare function useFiltroAvancado({ aberto, filtrosBase, modelo, modeloF
dicaPersistente?: boolean | undefined;
densidade?: import("../..").CampoDensidade | undefined;
variante?: import("../..").CampoVariante | undefined;
}] | readonly ["selecaoMultipla", {
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;
}] | readonly ["confirmacao", {
rotulo: string;
placeholder?: string | undefined;
cor?: string | undefined;
desabilitado?: boolean | undefined;
rotuloAtivo?: string | undefined;
rotuloInativo?: string | undefined;
densidade?: import("../..").CampoDensidade | 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;
filtro: (valor: unknown) => Pick<import("p-comuns").tipoCondicaoFiltroDeltaTipado<"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">, "valor" | "coluna" | "operador"> | 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>;
componenteEntrada: (entrada: ComponenteEntrada) => Component;
opcoesEntrada: (entrada: ComponenteEntrada) => {
rotulo: string;
};

View file

@ -154,7 +154,7 @@ declare const __VLS_export: import("vue").DefineComponent<{}, {
};
}>, {
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);
emit: ((event: "input", _v: string | null | undefined) => void) & ((event: "blur") => void) & ((event: "update:value", _v: string | null | undefined) => void) & ((event: "change", _v: string | null | undefined) => void) & ((event: "focus") => void);
localValue: import("vue").WritableComputedRef<string | null | undefined, string | null | undefined>;
inputHtmlType: import("vue").ComputedRef<"text" | "email" | "url">;
inputMode: import("vue").ComputedRef<string | undefined>;
@ -182,10 +182,10 @@ declare const __VLS_export: import("vue").DefineComponent<{}, {
};
}>> & Readonly<{
onInput?: ((_v: string | null | undefined) => any) | undefined;
onBlur?: (() => 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>;

View file

@ -1,6 +1,6 @@
{
"name": "eli-vue",
"version": "0.1.184",
"version": "0.1.187",
"private": false,
"main": "./dist/eli-vue.umd.js",
"module": "./dist/eli-vue.es.js",
@ -15,10 +15,10 @@
"scripts": {
"biome": "pnpm exec biome check --write",
"check": "pnpm run biome && npx tsc --noEmit",
"prebuild": "npm version patch --no-git-tag-version && bash scripts/check-linhas.sh",
"prebuild": "npm version patch --no-git-tag-version",
"build:types": "vue-tsc -p tsconfig.build.json --declaration --emitDeclarationOnly",
"build": "pnpm run biome && vite build && npm run build:types",
"dev": "pnpm run biome && pnpm run fechar && vite",
"build": "sh ./.agent/verificar-linhas.sh || exit 1 && pnpm run biome && vite build && npm run build:types",
"dev": "sh ./.agent/verificar-linhas.sh || exit 1 && pnpm run biome && pnpm run fechar && vite",
"preview": "vite preview",
"fechar": "fuser -k 5555/tcp || true"
},

30
pnpm-lock.yaml generated
View file

@ -22,10 +22,10 @@ importers:
version: 1.11.21
p-comuns:
specifier: git+https://git2.idz.one/publico/_comuns.git
version: git+https://git2.idz.one/publico/_comuns.git#4aa775266f009b085dd9067e47a3206792812863(dayjs@1.11.21)(uuid@13.0.2)(zod@4.4.3)
version: git+https://git2.idz.one/publico/_comuns.git#48125b53699708bc3747015dfda380473c1298d8(dayjs@1.11.21)(uuid@13.0.2)(zod@4.4.3)
p-respostas:
specifier: git+https://git2.idz.one/publico/_respostas.git
version: git+https://git2.idz.one/publico/_respostas.git#9aa36bcba98de8efd9babec6ab84099da5d46234(p-comuns@git+https://git2.idz.one/publico/_comuns.git#4aa775266f009b085dd9067e47a3206792812863(dayjs@1.11.21)(uuid@13.0.2)(zod@4.4.3))(zod@4.4.3)
version: git+https://git2.idz.one/publico/_respostas.git#9aa36bcba98de8efd9babec6ab84099da5d46234(p-comuns@git+https://git2.idz.one/publico/_comuns.git#48125b53699708bc3747015dfda380473c1298d8(dayjs@1.11.21)(uuid@13.0.2)(zod@4.4.3))(zod@4.4.3)
uuid:
specifier: ^13.0.0
version: 13.0.2
@ -1272,9 +1272,9 @@ packages:
resolution: {integrity: sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==}
engines: {node: '>=8'}
p-comuns@git+https://git2.idz.one/publico/_comuns.git#4aa775266f009b085dd9067e47a3206792812863:
resolution: {commit: 4aa775266f009b085dd9067e47a3206792812863, repo: https://git2.idz.one/publico/_comuns.git, type: git}
version: 0.342.0
p-comuns@git+https://git2.idz.one/publico/_comuns.git#48125b53699708bc3747015dfda380473c1298d8:
resolution: {commit: 48125b53699708bc3747015dfda380473c1298d8, repo: https://git2.idz.one/publico/_comuns.git, type: git}
version: 0.346.0
peerDependencies:
dayjs: ^1.11.18
uuid: ^11.1.0
@ -1483,6 +1483,10 @@ packages:
resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==}
engines: {node: '>=12.0.0'}
tinyglobby@0.2.17:
resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==}
engines: {node: '>=12.0.0'}
tmpl@1.0.5:
resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==}
@ -2040,7 +2044,7 @@ snapshots:
hermes-parser: 0.33.3
invariant: 2.2.4
nullthrows: 1.1.1
tinyglobby: 0.2.16
tinyglobby: 0.2.17
yargs: 17.7.2
optional: true
@ -3034,15 +3038,15 @@ snapshots:
is-wsl: 2.2.0
optional: true
p-comuns@git+https://git2.idz.one/publico/_comuns.git#4aa775266f009b085dd9067e47a3206792812863(dayjs@1.11.21)(uuid@13.0.2)(zod@4.4.3):
p-comuns@git+https://git2.idz.one/publico/_comuns.git#48125b53699708bc3747015dfda380473c1298d8(dayjs@1.11.21)(uuid@13.0.2)(zod@4.4.3):
dependencies:
dayjs: 1.11.21
uuid: 13.0.2
zod: 4.4.3
p-respostas@git+https://git2.idz.one/publico/_respostas.git#9aa36bcba98de8efd9babec6ab84099da5d46234(p-comuns@git+https://git2.idz.one/publico/_comuns.git#4aa775266f009b085dd9067e47a3206792812863(dayjs@1.11.21)(uuid@13.0.2)(zod@4.4.3))(zod@4.4.3):
p-respostas@git+https://git2.idz.one/publico/_respostas.git#9aa36bcba98de8efd9babec6ab84099da5d46234(p-comuns@git+https://git2.idz.one/publico/_comuns.git#48125b53699708bc3747015dfda380473c1298d8(dayjs@1.11.21)(uuid@13.0.2)(zod@4.4.3))(zod@4.4.3):
dependencies:
p-comuns: git+https://git2.idz.one/publico/_comuns.git#4aa775266f009b085dd9067e47a3206792812863(dayjs@1.11.21)(uuid@13.0.2)(zod@4.4.3)
p-comuns: git+https://git2.idz.one/publico/_comuns.git#48125b53699708bc3747015dfda380473c1298d8(dayjs@1.11.21)(uuid@13.0.2)(zod@4.4.3)
zod: 4.4.3
parseurl@1.3.3:
@ -3136,7 +3140,7 @@ snapshots:
scheduler: 0.27.0
semver: 7.8.1
stacktrace-parser: 0.1.11
tinyglobby: 0.2.16
tinyglobby: 0.2.17
whatwg-fetch: 3.6.20
ws: 7.5.11
yargs: 17.7.2
@ -3320,6 +3324,12 @@ snapshots:
fdir: 6.5.0(picomatch@4.0.4)
picomatch: 4.0.4
tinyglobby@0.2.17:
dependencies:
fdir: 6.5.0(picomatch@4.0.4)
picomatch: 4.0.4
optional: true
tmpl@1.0.5:
optional: true

View file

@ -1,49 +0,0 @@
#!/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."

View file

@ -1,59 +0,0 @@
#!/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."

View file

@ -0,0 +1,62 @@
<template>
<v-switch
v-model="localValue"
:label="rotuloExibido"
:color="opcoes?.cor ?? 'primary'"
:disabled="Boolean(opcoes?.desabilitado)"
:density="opcoes?.densidade ?? 'comfortable'"
hide-details
v-bind="attrs"
@focus="() => emit('focus')"
@blur="() => emit('blur')"
/>
</template>
<script lang="ts">
import { computed, defineComponent, type PropType } from "vue"
import { VSwitch } from "vuetify/components"
import type { PadroesEntradas } from "./tiposEntradas"
type EntradaConfirmacao = PadroesEntradas["confirmacao"]
export default defineComponent({
name: "EliEntradaConfirmacao",
components: { VSwitch },
inheritAttrs: false,
props: {
value: {
type: Boolean as PropType<EntradaConfirmacao["value"]>,
default: false,
},
opcoes: {
type: Object as PropType<EntradaConfirmacao["opcoes"]>,
required: true,
},
},
emits: {
"update:value": (_v: EntradaConfirmacao["value"]) => true,
input: (_v: EntradaConfirmacao["value"]) => true,
change: (_v: EntradaConfirmacao["value"]) => true,
focus: () => true,
blur: () => true,
},
setup(props, { attrs, emit }) {
const localValue = computed<EntradaConfirmacao["value"]>({
get: () => props.value,
set: (v) => {
emit("update:value", v)
emit("input", v)
emit("change", v)
},
})
const rotuloExibido = computed(() => {
if (localValue.value && props.opcoes?.rotuloAtivo) return props.opcoes.rotuloAtivo
if (!localValue.value && props.opcoes?.rotuloInativo) return props.opcoes.rotuloInativo
return props.opcoes?.rotulo ?? ""
})
return { attrs, emit, localValue, rotuloExibido }
},
})
</script>

View file

@ -0,0 +1,95 @@
<template>
<v-select
v-model="localValue"
:label="opcoes?.rotulo"
:placeholder="opcoes?.placeholder"
:items="itens"
item-title="rotulo"
item-value="chave"
:loading="carregando"
:disabled="carregando"
:menu-props="{ maxHeight: 320, zIndex: 99999 }"
:clearable="Boolean(opcoes?.limpavel)"
:error="Boolean(opcoes?.erro)"
:error-messages="opcoes?.mensagensErro"
:hint="opcoes?.dica"
:persistent-hint="Boolean(opcoes?.dicaPersistente)"
:density="opcoes?.densidade ?? 'comfortable'"
:variant="opcoes?.variante ?? 'outlined'"
multiple
chips
closable-chips
v-bind="attrs"
@focus="() => emit('focus')"
@blur="() => emit('blur')"
/>
</template>
<script lang="ts">
import { computed, defineComponent, onMounted, type PropType, ref, watch } from "vue"
import { VSelect } from "vuetify/components"
import type { PadroesEntradas } from "./tiposEntradas"
type EntradaSelecaoMultipla = PadroesEntradas["selecaoMultipla"]
type ItemSelecao = { chave: string; rotulo: string }
export default defineComponent({
name: "EliEntradaSelecaoMultipla",
components: { VSelect },
inheritAttrs: false,
props: {
value: {
type: [Array, null] as unknown as PropType<EntradaSelecaoMultipla["value"]>,
default: () => [],
},
opcoes: {
type: Object as PropType<EntradaSelecaoMultipla["opcoes"]>,
required: true,
},
},
emits: {
"update:value": (_v: EntradaSelecaoMultipla["value"]) => true,
input: (_v: EntradaSelecaoMultipla["value"]) => true,
change: (_v: EntradaSelecaoMultipla["value"]) => true,
focus: () => true,
blur: () => true,
},
setup(props, { attrs, emit }) {
const itens = ref<ItemSelecao[]>([])
const carregando = ref(false)
const localValue = computed<EntradaSelecaoMultipla["value"]>({
get: () => props.value ?? [],
set: (v) => {
const valor = Array.isArray(v) ? v : []
emit("update:value", valor)
emit("input", valor)
emit("change", valor)
},
})
async function carregarItens() {
carregando.value = true
try {
const resultado = await props.opcoes.itens()
itens.value = [...(Array.isArray(resultado) ? resultado : [])]
} finally {
carregando.value = false
}
}
watch(
() => props.opcoes.itens,
() => {
void carregarItens()
},
)
onMounted(() => {
void carregarItens()
})
return { attrs, emit, localValue, itens, carregando }
},
})
</script>

View file

@ -1,14 +1,18 @@
import EliEntradaConfirmacao from "./EliEntradaConfirmacao.vue"
import EliEntradaDataHora from "./EliEntradaDataHora.vue"
import EliEntradaNumero from "./EliEntradaNumero.vue"
import EliEntradaParagrafo from "./EliEntradaParagrafo.vue"
import EliEntradaSelecao from "./EliEntradaSelecao.vue"
import EliEntradaSelecaoMultipla from "./EliEntradaSelecaoMultipla.vue"
import EliEntradaTexto from "./EliEntradaTexto.vue"
export type { PadroesEntradas, TipoEntrada } from "./tiposEntradas"
export {
EliEntradaConfirmacao,
EliEntradaDataHora,
EliEntradaNumero,
EliEntradaParagrafo,
EliEntradaSelecao,
EliEntradaSelecaoMultipla,
EliEntradaTexto,
}

View file

@ -1,8 +1,10 @@
import type { Component } from "vue"
import EliEntradaConfirmacao from "./EliEntradaConfirmacao.vue"
import EliEntradaDataHora from "./EliEntradaDataHora.vue"
import EliEntradaNumero from "./EliEntradaNumero.vue"
import EliEntradaParagrafo from "./EliEntradaParagrafo.vue"
import EliEntradaSelecao from "./EliEntradaSelecao.vue"
import EliEntradaSelecaoMultipla from "./EliEntradaSelecaoMultipla.vue"
import EliEntradaTexto from "./EliEntradaTexto.vue"
import type { TipoEntrada } from "./tiposEntradas"
@ -17,10 +19,12 @@ import type { TipoEntrada } from "./tiposEntradas"
* 2. Adicionar a chave em `TipoEntrada` (tiposEntradas.ts)
* 3. Registrar aqui no mapa
*/
export const registryTabelaCelulas = {
export const registryTabelaCelulas: Record<TipoEntrada, Component> = {
texto: EliEntradaTexto,
numero: EliEntradaNumero,
dataHora: EliEntradaDataHora,
paragrafo: EliEntradaParagrafo,
selecao: EliEntradaSelecao,
} as const satisfies Record<TipoEntrada, Component>
selecaoMultipla: EliEntradaSelecaoMultipla,
confirmacao: EliEntradaConfirmacao,
}

View file

@ -177,6 +177,60 @@ export type PadroesEntradas = {
variante?: import("../../tipos").CampoVariante
}
>
selecaoMultipla: tipoPadraoEntrada<
string[] | null | undefined,
{
/**
* Carrega os itens da seleção (sincrono ou async).
* - Cada item precisa ter uma chave estável (value) e um rótulo (title).
*/
itens: () =>
| { chave: string; rotulo: string }[]
| Promise<{ chave: string; rotulo: string }[]>
/** Se true, mostra ícone para limpar o valor (Vuetify clearable). */
limpavel?: boolean
/** Estado de erro (visual). */
erro?: boolean
/** Mensagens de erro. */
mensagensErro?: string | string[]
/** Texto de apoio. */
dica?: string
/** Mantém a dica sempre visível. */
dicaPersistente?: boolean
/** Densidade do campo (Vuetify). */
densidade?: import("../../tipos").CampoDensidade
/** Variante do v-text-field (Vuetify). */
variante?: import("../../tipos").CampoVariante
}
>
confirmacao: tipoPadraoEntrada<
boolean,
{
/** Cor do switch quando ativo (Vuetify color). */
cor?: string
/** Se true, desabilita a interação. */
desabilitado?: boolean
/** Texto exibido ao lado do switch quando ativo. */
rotuloAtivo?: string
/** Texto exibido ao lado do switch quando inativo. */
rotuloInativo?: string
/** Densidade do campo (Vuetify). */
densidade?: import("../../tipos").CampoDensidade
}
>
}
/**

View file

@ -1,3 +1,5 @@
@import "./EliTabelaAcoes.css";
@import "./EliTabelaSelecao.css";
.eli-tabela {
width: 100%;
}
@ -132,7 +134,6 @@
background: rgba(0, 0, 0, 0.03);
}
.eli-tabela--erro {
display: flex;
align-items: flex-start;
@ -244,284 +245,6 @@
gap: 12px;
margin-bottom: 12px;
flex-wrap: wrap;
/* Altura padrão para controles do cabeçalho (busca e botões)
* - Mantém alinhamento visual entre input de busca e ações
* - Pode ser sobrescrita via CSS no consumidor, se necessário
*/
--eli-tabela-cabecalho-controle-altura: 34px;
/* Borda dos controles do cabeçalho: menos "pílula", mais quadrado.
* Ajuste fino via CSS no consumidor, se necessário.
*/
--eli-tabela-cabecalho-controle-radius: 8px;
}
.eli-tabela__acoes-cabecalho {
display: inline-flex;
gap: 8px;
flex-wrap: wrap;
}
.eli-tabela__acoes-cabecalho-botao {
display: inline-flex;
align-items: center;
gap: 6px;
height: var(--eli-tabela-cabecalho-controle-altura);
padding: 0 14px;
border-radius: var(--eli-tabela-cabecalho-controle-radius);
border: none;
background: var(--cor-eli-primaria-bg);
color: var(--cor-eli-primaria-escura);
font-size: 0.875rem;
font-weight: 500;
line-height: 1;
cursor: pointer;
transition: background-color 0.2s ease, color 0.2s ease;
}
.eli-tabela__acoes-cabecalho-botao:hover,
.eli-tabela__acoes-cabecalho-botao:focus-visible {
background: var(--cor-eli-primaria-clara);
}
.eli-tabela__acoes-cabecalho-botao:focus-visible {
outline: 2px solid var(--cor-eli-primaria-clara);
outline-offset: 2px;
}
.eli-tabela__acoes-cabecalho-icone {
display: inline-block;
}
.eli-tabela__acoes-cabecalho-rotulo {
line-height: 1;
}
.eli-tabela__acoes-toggle {
display: inline-flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
border-radius: 9999px;
border: none;
background: transparent;
color: rgba(15, 23, 42, 0.72);
cursor: pointer;
transition: background-color 0.2s ease, color 0.2s ease;
}
.eli-tabela__acoes-toggle-icone {
display: block;
}
.eli-tabela__acoes-toggle:hover,
.eli-tabela__acoes-toggle:focus-visible {
background-color: rgba(15, 23, 42, 0.08);
color: rgba(15, 23, 42, 0.95);
}
.eli-tabela__acoes-toggle:focus-visible {
outline: 2px solid var(--cor-eli-primaria-clara);
outline-offset: 2px;
}
.eli-tabela__acoes-toggle:disabled {
cursor: default;
color: rgba(148, 163, 184, 0.8);
background: transparent;
}
.eli-tabela__acoes-menu {
min-width: 180px;
padding: 6px 0;
margin: 0;
list-style: none;
background: var(--cor-eli-superficie);
border: 1px solid rgba(15, 23, 42, 0.08);
border-radius: 10px;
box-shadow: 0 12px 30px rgba(15, 23, 42, 0.18);
z-index: 1000;
}
.eli-tabela__acoes-item {
margin: 0;
}
.eli-tabela__acoes-item-botao {
display: flex;
align-items: center;
gap: 8px;
width: 100%;
padding: 8px 12px;
border: none;
background: transparent;
font-size: 0.9rem;
cursor: pointer;
transition: background-color 0.2s ease;
}
.eli-tabela__acoes-item-botao:hover,
.eli-tabela__acoes-item-botao:focus-visible {
background-color: rgba(15, 23, 42, 0.06);
}
.eli-tabela__acoes-item-botao:focus-visible {
outline: 2px solid currentColor;
outline-offset: -2px;
}
.eli-tabela__acoes-item-icone {
flex-shrink: 0;
}
.eli-tabela__acoes-item-texto {
flex: 1;
text-align: left;
}
/* =========================
* Expander (colunas invisíveis)
* ========================= */
.eli-tabela__th--expander,
.eli-tabela__td--expander {
width: 42px;
padding: 6px 8px;
text-align: center;
vertical-align: middle;
}
.eli-tabela__expander-botao {
display: inline-flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
border-radius: 9999px;
border: none;
background: transparent;
color: rgba(15, 23, 42, 0.72);
cursor: pointer;
transition: background-color 0.2s ease, color 0.2s ease;
}
.eli-tabela__expander-botao:hover,
.eli-tabela__expander-botao:focus-visible {
background-color: rgba(15, 23, 42, 0.08);
color: rgba(15, 23, 42, 0.95);
}
.eli-tabela__expander-botao:focus-visible {
outline: 2px solid var(--cor-eli-primaria-clara);
outline-offset: 2px;
}
.eli-tabela__expander-botao--ativo {
background-color: rgba(15, 23, 42, 0.06);
color: rgba(15, 23, 42, 0.95);
}
.eli-tabela__td--detalhes {
padding: 12px;
}
.eli-tabela__tr--detalhes .eli-tabela__td {
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
}
.eli-tabela__detalhes {
display: grid;
gap: 10px;
padding-left: 4px;
}
.eli-tabela__detalhe {
display: grid;
grid-template-columns: 180px 1fr;
gap: 10px;
align-items: start;
}
.eli-tabela__detalhe-rotulo {
font-weight: 600;
color: rgba(15, 23, 42, 0.85);
}
.eli-tabela__detalhe-valor {
min-width: 0;
}
@media (max-width: 720px) {
.eli-tabela__detalhe {
grid-template-columns: 1fr;
}
}
/* =========================================
* Seleção (Checkboxes)
* ========================================= */
.eli-tabela__th--selecao,
.eli-tabela__td--selecao {
width: 48px;
min-width: 48px;
max-width: 48px;
text-align: center;
padding: 0 8px;
vertical-align: middle;
}
.eli-tabela__checkbox {
appearance: none;
-webkit-appearance: none;
width: 15px;
height: 15px;
cursor: pointer;
border: 2px solid rgba(0, 0, 0, 0.54);
border-radius: 2px;
margin: 0;
vertical-align: middle;
position: relative;
transition: all 0.2s ease-in-out;
background-color: transparent;
outline: none;
}
.eli-tabela__checkbox:hover {
border-color: rgba(0, 0, 0, 0.87);
}
.eli-tabela__checkbox:checked,
.eli-tabela__checkbox:indeterminate {
background-color: var(--cor-eli-primaria);
border-color: var(--cor-eli-primaria);
}
.eli-tabela__checkbox:checked::after {
content: '';
position: absolute;
top: 0px;
left: 4px;
width: 4px;
height: 9px;
border: solid white;
border-width: 0 2px 2px 0;
transform: rotate(45deg);
}
.eli-tabela__checkbox:indeterminate::after {
content: '';
position: absolute;
top: 5px;
left: 2px;
width: 8px;
height: 2px;
background-color: white;
border-radius: 1px;
}
.eli-tabela__checkbox:focus-visible {
box-shadow: 0 0 0 2px var(--cor-eli-primaria-clara);
}

View file

@ -12,6 +12,7 @@
v-if="exibirBusca || temAcoesSuperiores"
:exibirBusca="exibirBusca"
:exibirBotaoFiltroAvancado="exibirFiltroAvancado"
:filtrosAtivosResumo="filtrosAtivosResumo"
:valorBusca="valorBusca"
:acoesCabecalho="acoesSuperiores"
:parametrosConsulta="parametrosConsultaAtuais"

View file

@ -0,0 +1,207 @@
/* =========================
* EliTabela — Ações de linha, Menu e Seleção (Checkboxes)
* Extraído de EliTabela.css para manter cada arquivo CSS abaixo de 250 linhas.
* ========================= */
.eli-tabela__acoes-cabecalho {
display: inline-flex;
gap: 8px;
flex-wrap: wrap;
}
.eli-tabela__acoes-cabecalho-botao {
display: inline-flex;
align-items: center;
gap: 6px;
height: var(--eli-tabela-cabecalho-controle-altura);
padding: 0 14px;
border-radius: var(--eli-tabela-cabecalho-controle-radius);
border: none;
background: var(--cor-eli-primaria-bg);
color: var(--cor-eli-primaria-escura);
font-size: 0.875rem;
font-weight: 500;
line-height: 1;
cursor: pointer;
transition: background-color 0.2s ease, color 0.2s ease;
}
.eli-tabela__acoes-cabecalho-botao:hover,
.eli-tabela__acoes-cabecalho-botao:focus-visible {
background: var(--cor-eli-primaria-clara);
}
.eli-tabela__acoes-cabecalho-botao:focus-visible {
outline: 2px solid var(--cor-eli-primaria-clara);
outline-offset: 2px;
}
.eli-tabela__acoes-cabecalho-icone {
display: inline-block;
}
.eli-tabela__acoes-cabecalho-rotulo {
line-height: 1;
}
.eli-tabela__acoes-toggle {
display: inline-flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
border-radius: 9999px;
border: none;
background: transparent;
color: rgba(15, 23, 42, 0.72);
cursor: pointer;
transition: background-color 0.2s ease, color 0.2s ease;
}
.eli-tabela__acoes-toggle-icone {
display: block;
}
.eli-tabela__acoes-toggle:hover,
.eli-tabela__acoes-toggle:focus-visible {
background-color: rgba(15, 23, 42, 0.08);
color: rgba(15, 23, 42, 0.95);
}
.eli-tabela__acoes-toggle:focus-visible {
outline: 2px solid var(--cor-eli-primaria-clara);
outline-offset: 2px;
}
.eli-tabela__acoes-toggle:disabled {
cursor: default;
color: rgba(148, 163, 184, 0.8);
background: transparent;
}
.eli-tabela__acoes-menu {
min-width: 180px;
padding: 6px 0;
margin: 0;
list-style: none;
background: var(--cor-eli-superficie);
border: 1px solid rgba(15, 23, 42, 0.08);
border-radius: 10px;
box-shadow: 0 12px 30px rgba(15, 23, 42, 0.18);
z-index: 1000;
}
.eli-tabela__acoes-item {
margin: 0;
}
.eli-tabela__acoes-item-botao {
display: flex;
align-items: center;
gap: 8px;
width: 100%;
padding: 8px 12px;
border: none;
background: transparent;
font-size: 0.9rem;
cursor: pointer;
transition: background-color 0.2s ease;
}
.eli-tabela__acoes-item-botao:hover,
.eli-tabela__acoes-item-botao:focus-visible {
background-color: rgba(15, 23, 42, 0.06);
}
.eli-tabela__acoes-item-botao:focus-visible {
outline: 2px solid currentColor;
outline-offset: -2px;
}
.eli-tabela__acoes-item-icone {
flex-shrink: 0;
}
.eli-tabela__acoes-item-texto {
flex: 1;
text-align: left;
}
/* =========================
* Expander (colunas invisíveis)
* ========================= */
.eli-tabela__th--expander,
.eli-tabela__td--expander {
width: 42px;
padding: 6px 8px;
text-align: center;
vertical-align: middle;
}
.eli-tabela__expander-botao {
display: inline-flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
border-radius: 9999px;
border: none;
background: transparent;
color: rgba(15, 23, 42, 0.72);
cursor: pointer;
transition: background-color 0.2s ease, color 0.2s ease;
}
.eli-tabela__expander-botao:hover,
.eli-tabela__expander-botao:focus-visible {
background-color: rgba(15, 23, 42, 0.08);
color: rgba(15, 23, 42, 0.95);
}
.eli-tabela__expander-botao:focus-visible {
outline: 2px solid var(--cor-eli-primaria-clara);
outline-offset: 2px;
}
.eli-tabela__expander-botao--ativo {
background-color: rgba(15, 23, 42, 0.06);
color: rgba(15, 23, 42, 0.95);
}
.eli-tabela__td--detalhes {
padding: 12px;
}
.eli-tabela__tr--detalhes .eli-tabela__td {
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
}
.eli-tabela__detalhes {
display: grid;
gap: 10px;
padding-left: 4px;
}
.eli-tabela__detalhe {
display: grid;
grid-template-columns: 180px 1fr;
gap: 10px;
align-items: start;
}
.eli-tabela__detalhe-rotulo {
font-weight: 600;
color: rgba(15, 23, 42, 0.85);
}
.eli-tabela__detalhe-valor {
min-width: 0;
}
@media (max-width: 720px) {
.eli-tabela__detalhe {
grid-template-columns: 1fr;
}
}

View file

@ -16,9 +16,16 @@
v-if="exibirBotaoFiltroAvancado"
type="button"
class="eli-tabela__acoes-cabecalho-botao eli-tabela__acoes-cabecalho-botao--filtro"
:title="tooltipFiltro"
@click="emitFiltroAvancado"
>
<Filter :size="15" :stroke-width="2" />
<span class="eli-tabela__filtro-icone-container">
<Filter :size="15" :stroke-width="2" />
<span
v-if="filtrosAtivosResumo.quantidade > 0"
class="eli-tabela__filtro-badge"
>{{ filtrosAtivosResumo.quantidade }}</span>
</span>
Filtro
</button>
<EliTabelaCaixaDeBusca :modelo="valorBusca" @buscar="emitBuscar" />
@ -70,6 +77,11 @@ export default defineComponent({
required: false,
default: false,
},
filtrosAtivosResumo: {
type: Object as PropType<{ quantidade: number; rotulos: string[] }>,
required: false,
default: () => ({ quantidade: 0, rotulos: [] }),
},
valorBusca: {
type: String,
required: true,
@ -107,6 +119,12 @@ export default defineComponent({
setup(props, { emit }) {
const temAcoesCabecalho = computed(() => props.acoesCabecalho.length > 0)
const tooltipFiltro = computed(() => {
const { quantidade, rotulos } = props.filtrosAtivosResumo
if (quantidade === 0) return "Filtro avançado"
return `Filtros ativos (${quantidade}):\n${rotulos.map((r) => `• ${r}`).join("\n")}`
})
function emitBuscar(texto: string) {
emit("buscar", texto)
}
@ -119,7 +137,7 @@ export default defineComponent({
emit("filtroAvancado")
}
return { temAcoesCabecalho, emitBuscar, emitColunas, emitFiltroAvancado }
return { temAcoesCabecalho, tooltipFiltro, emitBuscar, emitColunas, emitFiltroAvancado }
},
})
</script>
@ -131,4 +149,28 @@ export default defineComponent({
gap: 8px;
flex-wrap: wrap;
}
.eli-tabela__filtro-icone-container {
position: relative;
display: inline-flex;
align-items: center;
}
.eli-tabela__filtro-badge {
position: absolute;
top: -7px;
right: -9px;
min-width: 16px;
height: 16px;
padding: 0 4px;
border-radius: 9999px;
background: var(--cor-eli-erro, #dc3545);
color: #fff;
font-size: 0.65rem;
font-weight: 700;
line-height: 16px;
text-align: center;
pointer-events: none;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
}
</style>

View file

@ -1,3 +1,5 @@
@import "./EliTabelaModalColunasItens.css";
.eli-tabela-modal-colunas__overlay {
position: fixed;
inset: 0;
@ -162,181 +164,3 @@
.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);
}
}

View file

@ -0,0 +1,182 @@
/* =========================
* EliTabelaModalColunas — Itens, estados e footer
* Extraído de EliTabelaModalColunas.css para manter cada arquivo abaixo de 250 linhas.
* ========================= */
/* ── 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);
}
}

View file

@ -1,3 +1,5 @@
@import "./EliTabelaModalFiltroFormula.css";
.eli-tabela-modal-filtro__overlay {
position: fixed;
inset: 0;
@ -217,227 +219,3 @@
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;
}

View file

@ -0,0 +1,225 @@
/* =========================
* EliTabelaModalFiltroAvancado — Fórmula, tooltip, botões e transições
* Extraído de EliTabelaModalFiltroAvancado.css para manter cada arquivo abaixo de 250 linhas.
* ========================= */
/* ── 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;
}

View file

@ -0,0 +1,66 @@
/* =========================================
* Seleção (Checkboxes)
* Extraído de EliTabela.css para manter cada arquivo abaixo de 250 linhas.
* ========================================= */
.eli-tabela__th--selecao,
.eli-tabela__td--selecao {
width: 48px;
min-width: 48px;
max-width: 48px;
text-align: center;
padding: 0 8px;
vertical-align: middle;
}
.eli-tabela__checkbox {
appearance: none;
-webkit-appearance: none;
width: 15px;
height: 15px;
cursor: pointer;
border: 2px solid rgba(0, 0, 0, 0.54);
border-radius: 2px;
margin: 0;
vertical-align: middle;
position: relative;
transition: all 0.2s ease-in-out;
background-color: transparent;
outline: none;
}
.eli-tabela__checkbox:hover {
border-color: rgba(0, 0, 0, 0.87);
}
.eli-tabela__checkbox:checked,
.eli-tabela__checkbox:indeterminate {
background-color: var(--cor-eli-primaria);
border-color: var(--cor-eli-primaria);
}
.eli-tabela__checkbox:checked::after {
content: '';
position: absolute;
top: 0px;
left: 4px;
width: 4px;
height: 9px;
border: solid white;
border-width: 0 2px 2px 0;
transform: rotate(45deg);
}
.eli-tabela__checkbox:indeterminate::after {
content: '';
position: absolute;
top: 5px;
left: 2px;
width: 8px;
height: 2px;
background-color: white;
border-radius: 1px;
}
.eli-tabela__checkbox:focus-visible {
box-shadow: 0 0 0 2px var(--cor-eli-primaria-clara);
}

View file

@ -1,5 +1,5 @@
import type { LucideIcon } from "@lucide/vue"
import type { tipoFiltroDeltaColunaTipada } from "p-comuns"
import type { tipoCondicaoFiltroDeltaTipado, tipoFiltroDeltaColunaTipada } from "p-comuns"
import type { tipoResposta } from "p-respostas"
import type { ComponenteEntrada } from "../EliEntrada/tiposEntradas"
@ -160,9 +160,17 @@ export type tipoEliTabelaConsulta<T> = {
chave: string
/** Rótulo exibido no select de adicionar filtro */
rotulo: string
/** vai gerar filtro e mescla com os filtros já existentes */
filtro: (valor: unknown) => tipoFiltroDeltaColunaTipada<Extract<keyof T, string>> | undefined
/** vai gerar filtro e mescla com os filtros já existentes , id é gerado de forma altomatica para esse local*/
filtro: (
valor: unknown,
) =>
| Pick<
tipoCondicaoFiltroDeltaTipado<Extract<keyof T, string>>,
"coluna" | "operador" | "valor"
>
| undefined
/** componente de entrada que vai receber o valor para o filtro */
entrada: ComponenteEntrada
}[]
}
;("")

View file

@ -49,12 +49,13 @@ export function useEliTabelaFiltrosAvancados(
.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) }
const condicao = b.filtro(f.valor)
if (!condicao) return null
return { chave: f.chave, condicao }
})
.filter(
(f): f is { chave: string; filtro: tipoFiltroDeltaColunaTipada<string> } =>
f !== null && f.filtro !== undefined && f.filtro !== null,
)
.filter((f): f is { chave: string; condicao: tipoCondicaoFiltroDelta } => f !== null)
if (filtrosIndividuais.length === 0) return []
if (filtrosFormula.value && filtrosIndividuais.length > 1) {
const condicoesFinais: tipoCondicaoFiltroDelta[] = []
@ -62,25 +63,22 @@ export function useEliTabelaFiltrosAvancados(
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)
}
filtrosIndividuais.forEach(({ condicao }, i) => {
const novoId = `{c${i + 1}}`
condicoesFinais.push({ ...condicao, id: novoId })
})
return [{ condicoes: condicoesFinais, formula: formulaFinal }]
}
return filtrosIndividuais.map((f) => f.filtro)
/** Sem fórmula customizada: monta condições com IDs sequenciais e fórmula "E" */
const condicoesFinais: tipoCondicaoFiltroDelta[] = filtrosIndividuais.map(
({ condicao }, i) => ({
...condicao,
id: `{c${i + 1}}`,
}),
)
const formula = condicoesFinais.map((c) => c.id).join(" E ")
return [{ condicoes: condicoesFinais, formula }]
})
function abrirModalFiltro() {
@ -114,12 +112,82 @@ export function useEliTabelaFiltrosAvancados(
else void carregar()
}
const nomesOperadores: Record<string, string> = {
"=": "igual a",
"!=": "diferente de",
">": "maior que",
"<": "menor que",
">=": "maior ou igual a",
"<=": "menor ou igual a",
like: "contém",
in: "em",
"∩": "possui",
}
function formatarFiltroAtivo(entrada: unknown, valor: unknown): string {
const [tipo, opcoes] = Array.isArray(entrada)
? (entrada as [string, Record<string, unknown>])
: [entrada as string, {} as Record<string, unknown>]
if (tipo === "confirmacao") {
const opt = opcoes || {}
if (valor === true) return (opt.rotuloAtivo as string) || "Sim"
if (valor === false) return (opt.rotuloInativo as string) || "Não"
return String(valor)
}
if ((tipo === "selecao" || tipo === "selecaoMultipla") && typeof opcoes?.itens === "function") {
try {
const itensResult = opcoes.itens()
if (Array.isArray(itensResult)) {
const findLabel = (key: string) => {
const found = (itensResult as { chave: string; rotulo: string }[]).find(
(item) => String(item.chave) === String(key),
)
return found ? found.rotulo : key
}
if (Array.isArray(valor)) {
return valor.map((v) => findLabel(String(v))).join(", ")
}
return findLabel(String(valor))
}
} catch {
// Fallback
}
}
if (Array.isArray(valor)) {
return valor.map(String).join(", ")
}
return String(valor)
}
/** Resumo dos filtros ativos para badge e tooltip do cabecalho */
const filtrosAtivosResumo = computed(() => {
const base = props.tabela.filtroAvancado ?? []
const ativos = (filtrosUi.value ?? []).filter((f) => f && f.chave !== undefined)
const rotulos: string[] = []
for (const f of ativos) {
const b = base.find((x) => String(x.chave) === String(f.chave))
if (b) {
const condicao = b.filtro(f.valor)
if (condicao) {
const opStr = nomesOperadores[condicao.operador] || condicao.operador
const valStr = formatarFiltroAtivo(b.entrada, condicao.valor)
rotulos.push(`${b.rotulo} ${opStr} "${valStr}"`)
}
}
}
return { quantidade: rotulos.length, rotulos }
})
return {
filtrosUi,
filtrosFormula,
modalFiltroAberto,
exibirFiltroAvancado,
filtrosAvancadosAtivos,
filtrosAtivosResumo,
abrirModalFiltro,
fecharModalFiltro,
limparFiltrosAvancados,

View file

@ -10,8 +10,16 @@
*
* 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 Component, computed, type Ref, ref, watch } from "vue"
import {
EliEntradaConfirmacao,
EliEntradaDataHora,
EliEntradaNumero,
EliEntradaParagrafo,
EliEntradaSelecao,
EliEntradaSelecaoMultipla,
EliEntradaTexto,
} from "../EliEntrada"
import type { ComponenteEntrada } from "../EliEntrada/tiposEntradas"
import type { tipoEliTabelaConsulta } from "./types-eli-tabela"
@ -97,10 +105,14 @@ export function useFiltroAvancado({
return (filtrosBase.value ?? []).filter((b) => !usadas.has(String(b.chave)))
})
function componenteEntrada(entrada: ComponenteEntrada) {
function componenteEntrada(entrada: ComponenteEntrada): Component {
const tipo = entrada?.[0]
if (tipo === "numero") return EliEntradaNumero
if (tipo === "dataHora") return EliEntradaDataHora
if (tipo === "selecao") return EliEntradaSelecao
if (tipo === "selecaoMultipla") return EliEntradaSelecaoMultipla
if (tipo === "paragrafo") return EliEntradaParagrafo
if (tipo === "confirmacao") return EliEntradaConfirmacao
return EliEntradaTexto
}
@ -114,6 +126,9 @@ export function useFiltroAvancado({
function valorInicialPorEntrada(entrada: ComponenteEntrada) {
if (entrada?.[0] === "numero") return null
if (entrada?.[0] === "selecao") return null
if (entrada?.[0] === "selecaoMultipla") return []
if (entrada?.[0] === "confirmacao") return false
return ""
}

View file

@ -3,6 +3,26 @@ import type { tipoCondicaoFiltroDelta, zFiltroDelta } from "p-comuns"
import type z from "zod"
import { _dados_empreendedores, type tipoEmpreendedores } from "./dados"
/**
* UDF registrada no Alasql para suportar o operador ∩ (interseção).
* Verifica se dois arrays possuem ao menos um elemento em comum.
*/
alasql.fn.arr_intersecta = (coluna: unknown, filtro: unknown): boolean => {
if (!Array.isArray(coluna) || !Array.isArray(filtro)) return false
return coluna.some((item) => filtro.includes(item))
}
/**
* UDF universal para o operador "in".
* - Coluna escalar: verifica se o valor está na lista de filtro (IN clássico).
* - Coluna array: verifica se há interseção entre o array da coluna e a lista de filtro.
*/
alasql.fn.arr_in = (coluna: unknown, filtro: unknown): boolean => {
const lista = Array.isArray(filtro) ? filtro : [filtro]
if (Array.isArray(coluna)) return coluna.some((item) => lista.includes(item))
return lista.includes(coluna)
}
const zCondicaoFiltroDeltaParaQuery = ({
condicao,
}: {
@ -39,14 +59,21 @@ const zCondicaoFiltroDeltaParaQuery = ({
? 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})`
// UDF universal: funciona tanto para colunas escalares (IN clássico)
// quanto para colunas array (interseção).
retorno.sql = `arr_in(${coluna}, ${condicao.id}) = TRUE`
retorno.parametro[condicao.id] = Array.isArray(condicao.valor)
? condicao.valor
: String(condicao.valor)
.split(",")
.map((s) => s.trim())
} else if (condicao.operador === "∩") {
// Interseção: verifica se o array da coluna contém ao menos um dos valores informados.
// Como o Alasql não suporta nativamente comparação de arrays, usamos uma UDF.
retorno.sql = `arr_intersecta(${coluna}, ${condicao.id}) = TRUE`
retorno.parametro[condicao.id] = Array.isArray(condicao.valor)
? condicao.valor
: [condicao.valor]
} else {
throw new Error(`Operador inválido: ${condicao.operador as string}`)
}

View file

@ -1,8 +1,13 @@
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++}`
export const zAreaAtuacao = z.enum([
"mineracao",
"agro",
"turismo",
"tecnologia",
"educacao",
"saude",
])
/** Schema Zod do registro de empreendedor — usado para validação e inferência de tipo no playground. */
export const zEmpreendedores = z.object({
@ -14,237 +19,21 @@ export const zEmpreendedores = z.object({
telefone: z.string(),
faturamento: z.number(),
peso_kg: z.number(),
areas_atuacao: z.array(
z.enum(["mineracao", "agro", "turismo", "tecnologia", "educacao", "saude"]),
),
ativo: z.boolean(),
areas_atuacao: z.array(zAreaAtuacao),
criado_em: z.string(),
atualizado_em: z.string(),
})
export const AreaAtuacaoDetalhes: Record<z.infer<typeof zAreaAtuacao>, string> = {
mineracao: "Mineração",
agro: "Agropecuária",
turismo: "Turismo",
tecnologia: "Tecnologia",
educacao: "Educação",
saude: "Saúde",
}
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",
},
]
export { _dados_empreendedores } from "./dadosSeed"

View file

@ -0,0 +1,249 @@
/** dadosSeed — dados de seed (mock) para o playground da EliTabela. */
import type { tipoEmpreendedores } from "./dados"
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++}`
// == 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,
ativo: true,
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,
ativo: true,
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,
ativo: false,
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,
ativo: true,
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,
ativo: true,
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,
ativo: false,
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,
ativo: true,
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,
ativo: true,
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,
ativo: false,
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,
ativo: true,
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,
ativo: true,
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,
ativo: false,
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,
ativo: true,
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,
ativo: true,
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,
ativo: false,
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,
ativo: true,
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,
ativo: true,
areas_atuacao: ["agro"],
criado_em: "2026-01-10T15:00:00-03:00",
atualizado_em: "2026-01-14T16:00:00-03:00",
},
]

View file

@ -39,6 +39,7 @@ const api_criar: tipoFuncaoApiSimulacao<"criar"> = async (dados) => {
telefone: dados.telefone || "(00) 0000-0000",
faturamento: dados.faturamento || 0,
peso_kg: dados.peso_kg || 0,
ativo: dados.ativo ?? true,
areas_atuacao: dados.areas_atuacao || [],
criado_em: new Date().toISOString(),
atualizado_em: new Date().toISOString(),

View file

@ -37,6 +37,7 @@ export function useTabelaPlayground() {
telefone: "",
faturamento: 0,
peso_kg: 0,
ativo: true,
areas_atuacao: [],
})
const erroCadastro = ref<string | undefined>(undefined)
@ -54,6 +55,7 @@ export function useTabelaPlayground() {
telefone: "",
faturamento: 0,
peso_kg: 0,
ativo: true,
areas_atuacao: [],
}
erroCadastro.value = undefined

View file

@ -6,15 +6,22 @@
* @see useTabelaPlayground.ts — composable que consome estas configurações
*/
import { BadgeCheck, Eye, Pencil, Trash2 } from "@lucide/vue"
import { iniciarFiltroDelta } from "p-comuns"
import type { ComponenteEntrada } from "@/componentes/EliEntrada/tiposEntradas"
import type { tipoEliTabelaConsulta } from "@/componentes/EliTabela"
import { celulaTabela } from "@/componentes/EliTabela"
import type { tipoEmpreendedores } from "./api_simulador/dados"
import { AreaAtuacaoDetalhes, type tipoEmpreendedores } from "./api_simulador/dados"
const { criarFiltroDelta } = iniciarFiltroDelta<Extract<keyof tipoEmpreendedores, string>>()
/** Mapeamento de cor por área de atuação para uso nos chips da tabela */
const corAreaAtuacao: Record<tipoEmpreendedores["areas_atuacao"][number], string> = {
mineracao: "#78909C",
agro: "#4CAF50",
turismo: "#FF9800",
tecnologia: "#2196F3",
educacao: "#9C27B0",
saude: "#E91E63",
}
/** Ações por linha usadas nas 3 instâncias de tabela do playground */
export function criarAcoesLinha(
@ -79,6 +86,17 @@ export const colunasTabela: tipoEliTabelaConsulta<tipoEmpreendedores>["colunas"]
coluna_ordem: "telefone",
visivel: true,
},
{
rotulo: "Áreas de Atuação",
celula: (l) =>
celulaTabela("tags", {
opcoes: l.areas_atuacao.map((area) => ({
rotulo: AreaAtuacaoDetalhes[area],
cor: corAreaAtuacao[area],
})),
}),
visivel: true,
},
{
rotulo: "Faturamento",
celula: (l) =>
@ -134,12 +152,7 @@ export const filtrosAvancados: tipoEliTabelaConsulta<tipoEmpreendedores>["filtro
rotulo: "Empreendedor",
filtro: (valor: unknown) => {
if (!valor) return undefined
return criarFiltroDelta({
condicoes: [
{ id: "{c1}", coluna: "empreendedor", operador: "like", valor: valor as string },
],
formula: "{c1}",
})
return { coluna: "empreendedor", operador: "like" as const, valor: valor as string }
},
entrada: ["texto", { rotulo: "Empreendedor" }] as ComponenteEntrada,
},
@ -148,12 +161,7 @@ export const filtrosAvancados: tipoEliTabelaConsulta<tipoEmpreendedores>["filtro
rotulo: "Empreendimento",
filtro: (valor: unknown) => {
if (!valor) return undefined
return criarFiltroDelta({
condicoes: [
{ id: "{c1}", coluna: "empreendimento", operador: "like", valor: valor as string },
],
formula: "{c1}",
})
return { coluna: "empreendimento", operador: "like" as const, valor: valor as string }
},
entrada: ["texto", { rotulo: "Empreendimento" }] as ComponenteEntrada,
},
@ -162,10 +170,7 @@ export const filtrosAvancados: tipoEliTabelaConsulta<tipoEmpreendedores>["filtro
rotulo: "Documento",
filtro: (valor: unknown) => {
if (!valor) return undefined
return criarFiltroDelta({
condicoes: [{ id: "{c1}", coluna: "documento", operador: "like", valor: valor as string }],
formula: "{c1}",
})
return { coluna: "documento", operador: "like" as const, valor: valor as string }
},
entrada: ["texto", { rotulo: "Documento", formato: "cpfCnpj" }] as ComponenteEntrada,
},
@ -174,13 +179,65 @@ export const filtrosAvancados: tipoEliTabelaConsulta<tipoEmpreendedores>["filtro
rotulo: "E-mail",
filtro: (valor: unknown) => {
if (!valor) return undefined
return criarFiltroDelta({
condicoes: [{ id: "{c1}", coluna: "email", operador: "like", valor: valor as string }],
formula: "{c1}",
})
return { coluna: "email", operador: "like" as const, valor: valor as string }
},
entrada: ["texto", { rotulo: "E-mail", formato: "email" }] as ComponenteEntrada,
},
{
chave: "ativo",
rotulo: "Ativo",
filtro: (valor: unknown) => {
return { coluna: "ativo", operador: "=" as const, valor: valor as boolean }
},
entrada: [
"confirmacao",
{ rotulo: "Ativo", rotuloAtivo: "Apenas ativos", rotuloInativo: "Apenas inativos" },
] as ComponenteEntrada,
},
{
chave: "faturamento_min",
rotulo: "Faturamento mínimo",
filtro: (valor: unknown) => {
if (valor === null || valor === undefined) return undefined
return { coluna: "faturamento", operador: ">=" as const, valor: valor as number }
},
entrada: ["numero", { rotulo: "Faturamento mínimo (R$)" }] as ComponenteEntrada,
},
{
chave: "areas_atuacao",
rotulo: "Área de Atuação",
filtro: (valor: unknown) => {
const arr = valor as string[]
if (!arr.length) return undefined
return { coluna: "areas_atuacao", operador: "∩" as const, valor: arr }
},
entrada: [
"selecaoMultipla",
{
rotulo: "Área de Atuação",
limpavel: true,
itens: () =>
Object.entries(AreaAtuacaoDetalhes).map(([chave, rotulo]) => ({ chave, rotulo })),
},
] as ComponenteEntrada,
},
{
chave: "areas_atuacao2",
rotulo: "Área de Atuação (2)",
filtro: (valor: unknown) => {
if (!valor) return undefined
return { coluna: "areas_atuacao", operador: "in" as const, valor }
},
entrada: [
"selecao",
{
rotulo: "Área de Atuação",
limpavel: true,
itens: () =>
Object.entries(AreaAtuacaoDetalhes).map(([chave, rotulo]) => ({ chave, rotulo })),
},
] as ComponenteEntrada,
},
]
/**