vue-componentes/README.md
2026-02-17 11:33:44 -03:00

12 KiB

Documentação EliVue

EliVue é uma biblioteca de componentes Vue 3 construída sobre o Vuetify 3, projetada para padronizar padrões de UI como tabelas de dados (EliTabela) e campos de entrada (EliEntrada*). Ela segue a filosofia de "configuração sobre boilerplate", onde os componentes são guiados por objetos de configuração tipados em vez de propriedades de template extensas.


🤖 Diretrizes para Agentes de IA (Antigravity)

Ao gerar código usando EliVue, siga estas regras estritas:

  1. Importações: Sempre desestructure as importações do pacote raiz.
    import { EliTabela, EliEntradaTexto, EliBotao, celulaTabela } from "eli-vue";
    
  2. Objetos de Configuração: Os componentes EliTabela e EliEntrada dependem fortemente de objetos de configuração (prop tabela para tabelas, prop opcoes para entradas). Não tente passar props individuais (como headers ou items) para o EliTabela.
  3. Segurança de Tipos: Sempre defina objetos de configuração usando os tipos TypeScript exportados (ex: tipoEliTabelaConsulta, parametrosConsulta).
  4. Dados Assíncronos: O EliTabela gerencia sua própria busca de dados via callback consulta. Não busque dados manualmente para passar para a tabela. Forneça a função consulta no lugar.
  5. Ícones: Use ícones do pacote lucide-vue-next.
  6. Inicialização: Prefira usar o plugin global app.use(EliVue) ao configurar um novo projeto, em vez de importações individuais.
  7. Estilos: O CSS é injetado automaticamente. Não tente importar arquivos CSS manualmente.
  8. Idioma: Mantenha a documentação e comentários explicativos em Português do Brasil (pt-BR).

Instalação

pnpm add eli-vue
# Dependências (Peer Dependencies)
pnpm add vue vuetify lucide-vue-next

Certifique-se de que seu projeto esteja configurado com Vuetify 3.


📦 Referência de Componentes

1. EliTabela (Tabela de Dados)

O componente EliTabela é uma tabela poderosa com paginação no servidor (server-side), busca integrada, ordenação e menus de ação.

API

  • Prop: tabela (Obrigatório)
  • Tipo: tipoEliTabelaConsulta<T>

Interface de Configuração (tipoEliTabelaConsulta)

// De: eli-vue/src/componentes/EliTabela/types-eli-tabela.ts

export type tipoEliTabelaConsulta<T> = {
  // Identificador único para a tabela (usado para persistência local de visibilidade das colunas)
  nome: string;

  // Função para buscar dados. DEVE retornar um objeto de resposta padrão.
  consulta: (params: parametrosConsulta<T>) => Promise<tipoResposta<tipoEliConsultaPaginada<T>>>;

  // Definições das colunas
  colunas: tipoEliColuna<T>[];

  // Opções de UI
  mostrarCaixaDeBusca?: boolean;
  registros_por_consulta?: number;      // padrão: 10
  maximo_botoes_paginacao?: number;     // padrão: 7
  mensagemVazio?: string;

  // Ações
  acoesLinha?: tipoEliTabelaAcao<T>[]; // Ações para cada linha (menu de contexto)
  acoesTabela?: Array<{                 // Ações globais (superior/inferior)
  // Ações da Tabela (Botões Globais)
  acoesTabela?: Array<{
    /** 
     * Posição do botão:
     * - "superior": Ao lado da caixa de busca (canto superior direito).
     * - "inferior": No rodapé, alinhado à esquerda (canto inferior esquerdo).
     */
    posicao: "superior" | "inferior";
    
    /** Rótulo do botão */
    rotulo: string;
    
    /** Ícone (Lucide) */
    icone?: LucideIcon;
    
    /** Cor do botão (ex: "primary", "success", "error") */
    cor?: string;
    
    /** 
     * Função executada ao clicar.
     * Recebe um objeto contendo:
     * - Parâmetros atuais da consulta (offSet, limit, filtros, etc.)
     * - Helper `atualizarConsulta()`: Promise<void> para recarregar a tabela.
     */
    acao: (params: parametrosConsulta<T> & { atualizarConsulta: () => Promise<void> }) => void;
  }>;
  
  // Definição de Filtro Avançado
  filtroAvancado?: Array<{
    chave: string;
    rotulo: string;
    /** Função que gera o filtro com base no valor recebido do input */
    filtro: (valor: unknown) => tipoFiltro<T>;
    /** Definição do componente de entrada, ex: ["texto", { ... }] */
    entrada: any;
  }>;
};

Definindo Colunas (helper celulaTabela)

Use o helper celulaTabela para criar definições de células com segurança de tipo.

// Tipos de células disponíveis e seus dados:
// "textoSimples": { texto: string; acao?: () => void }
// "textoTruncado": { texto: string; acao?: () => void }
// "numero": { numero: number; prefixo?: string; sufixo?: string; acao?: () => void }
// "tags": { opcoes: Array<{ rotulo: string; cor?: string; icone?: LucideIcon; acao?: () => void }> }
// "data": { valor: string; formato: "data" | "data_hora" | "relativo"; acao?: () => void }
// "badge": { texto: string; cor: string }

{
  rotulo: "Nome",
  celula: (row) => celulaTabela("textoSimples", { texto: row.nome }),
  visivel: true
}

Exemplo de Uso com Filtros Avançados

<script setup lang="ts">
import { EliTabela, celulaTabela, criarFiltro26 } from "eli-vue";
import type { tipoEliTabelaConsulta } from "eli-vue"; 
import { UsuarioService } from "@/services/UsuarioService"; 
import { BadgeCheck, Pencil, Plus } from "lucide-vue-next";

type Usuario = { id: number; nome: string; email: string; ativo: boolean; created_at: string };

const tabelaConfig: tipoEliTabelaConsulta<Usuario> = {
  nome: "tabela-usuarios",
  mostrarCaixaDeBusca: true,
  registros_por_consulta: 10,
  
  colunas: [
    { 
      rotulo: "Nome", 
      celula: (usuario) => celulaTabela("textoSimples", { texto: usuario.nome }), 
      visivel: true, 
      coluna_ordem: "nome" 
    },
    {
      rotulo: "Status",
      visivel: true,
      celula: (usuario) => celulaTabela("tags", {
        opcoes: [
          usuario.ativo 
            ? { rotulo: "Ativo", cor: "success", icone: BadgeCheck }
            : { rotulo: "Inativo", cor: "error" }
        ]
      })
    },
    { 
      rotulo: "Criado em", 
      celula: (usuario) => celulaTabela("data", { valor: usuario.created_at, formato: "data_hora" }), 
      visivel: true 
    }
  ],
  
  // Configuração de filtros avançados
  filtroAvancado: [
    {
      rotulo: "Nome",
      chave: "nome",
      // Função que retorna o objeto de filtro estruturado
      filtro: (valor: unknown) => criarFiltro26({ nome: { like: valor as string } }),
      // Definição do input: ["tipo", opcoes]
      entrada: ["texto", { rotulo: "Nome do usuário" }] as any
    },
    {
      rotulo: "Ativo?",
      chave: "ativo",
      filtro: (valor: unknown) => criarFiltro26({ ativo: { "=": valor } }),
      entrada: ["selecao", { 
        rotulo: "Status",
        itens: () => [
          { chave: "true", rotulo: "Ativo" },
          { chave: "false", rotulo: "Inativo" } 
        ]
      }] as any
    }
  ],
  
  acoesTabela: [
    {
      rotulo: "Novo Usuário",
      icone: Plus,
      posicao: "superior",
      cor: "primary",
      // O callback recebe: offSet, limit, filtros, e o helper 'atualizarConsulta'
      acao: async ({ atualizarConsulta }) => {
        // Exemplo: Abrir modal de criação e depois atualizar a tabela
        await openCreateUserModal();
        await atualizarConsulta(); 
      }
    },
    {
      rotulo: "Exportar CSV",
      icone: Download,
      posicao: "inferior",
      cor: "success",
      acao: (params) => {
        // 'params' contém os filtros atuais aplicados na tabela
        UsuarioService.exportarCsv(params);
      }
    }
  ],

  consulta: async (params) => {
    // params contém: offSet, limit, texto_busca, coluna_ordem, direcao_ordem, filtros
    return await UsuarioService.listar(params);
  },

  acoesLinha: [
    {
      rotulo: "Editar",
      icone: Pencil,
      cor: "primary",
      acao: (usuario) => console.log("Editar", usuario)
    }
  ]
};
</script>

<template>
  <EliTabela :tabela="tabelaConfig" />
</template>

2. EliEntrada (Inputs)

Um conjunto de wrappers padronizados em torno dos campos do Vuetify.

Variantes

  • EliEntradaTexto: Texto, Email, URL, CPF, CNPJ, CEP.
  • EliEntradaNumero: Inteiros, Decimais, Moeda.
  • EliEntradaDataHora: Data, DataHora.
  • EliEntradaSelecao: Select/Dropdown (pode ser assíncrono).
  • EliEntradaParagrafo: Textarea.

API Comum

Todas as entradas aceitam duas props principais:

  1. value: O binding v-model.
  2. opcoes: Um objeto de configuração específico para o tipo de entrada.

Objetos de Configuração (tiposEntradas.ts)

EliEntradaTexto

opcoes: {
  rotulo: string;
  placeholder?: string;
  limiteCaracteres?: number;
  formato?: "texto" | "email" | "url" | "telefone" | "cpfCnpj" | "cep";
}

EliEntradaNumero

opcoes: {
  rotulo: string;
  precisao?: number; // 0=inteiro (padrão se null), 0.1=1 decimal, 0.01=2 decimais. Se undefined/null, assume decimal livre.
  prefixo?: string;  // ex: "R$"
  sufixo?: string;   // ex: "kg"
}

EliEntradaDataHora

opcoes: {
  rotulo: string;
  modo?: "data" | "dataHora"; // padrão: "dataHora"
  min?: string; // String ISO
  max?: string; // String ISO
}

EliEntradaSelecao

opcoes: {
  rotulo: string;
  // Função que retorna itens ou promise de itens. 
  // IMPORTANTE: Itens devem ser objetos { chave: string, rotulo: string }
  itens: () => Array<{ chave: string; rotulo: string }> | Promise<Array<{ chave: string; rotulo: string }>>;
  limpavel?: boolean;
}

Exemplo de Uso

<script setup lang="ts">
import { ref } from "vue";
import { EliEntradaTexto, EliEntradaNumero, EliEntradaDataHora } from "eli-vue";

const nome = ref("");
const salario = ref<number | null>(null);
const nascimento = ref<string | null>(null); // ISO string
</script>

<template>
  <EliEntradaTexto
    :value="nome"
    @update:value="nome = $event"
    :opcoes="{ rotulo: 'Nome Completo', formato: 'texto' }"
  />

  <EliEntradaNumero
    :value="salario"
    @update:value="salario = $event"
    :opcoes="{ rotulo: 'Salário', prefixo: 'R$', precisao: 0.01 }"
  />
  
  <!-- Exibe como data local, vincula como string ISO -->
  <EliEntradaDataHora
    :value="nascimento"
    @update:value="nascimento = $event"
    :opcoes="{ rotulo: 'Data de Nascimento', modo: 'data' }"
  />
</template>

3. EliBotao

Wrapper simples para v-btn.

  • Props: color, variant (elevated, flat, text, etc.), size, loading, disabled.
  • Slots: slot padrão para o conteúdo do botão.
<EliBotao color="primary" @click="salvar">
  Salvar
</EliBotao>

4. EliCartao & EliBadge

EliCartao Cartão padronizado para itens de domínio.

  • Props: titulo, status (tipo CartaoStatus: "novo" | "rascunho" | "vendido" | "cancelado"), variant.
  • Slots: default (conteúdo), acoes (ações de rodapé).

EliBadge Badge aprimorado com raios.

  • Props: badge (conteúdo), color, radius ("suave" | "pill").

🔧 Solução de Problemas e Dicas

1. "Failed to resolve component"

  • Certifique-se de que app.use(EliVue) foi chamado no main.ts.
  • Se importar diretamente, garanta importações padrão: import { EliTabela } from 'eli-vue'.

2. Problemas de Estilo

  • Injeção de CSS: O eli-vue injeta CSS automaticamente. Verifique sua CSP (Política de Segurança de Conteúdo) se os estilos estiverem bloqueados.
  • Vuetify: Certifique-se de que o Vuetify 3 está corretamente instalado e os estilos (vuetify/styles) estão importados no seu arquivo de entrada principal.

3. Erros de TypeScript no celulaTabela

  • Garanta que o segundo argumento corresponda ao esquema específico do tipo de célula.
  • Exemplo: celulaTabela("textoSimples", { texto: ... }) funciona, mas celulaTabela("textoSimples", { numero: ... }) falhará.

4. Ícones não aparecem

  • Passe o objeto do componente (ex: importado de lucide-vue-next), não o nome em string.
  • Exemplo: icone: Pencil (Correto) vs icone: "pencil" (Incorreto).

5. Valores do EliEntrada

  • EliEntradaTexto com formato='cpfCnpj' emite a string formatada (ex: "123.456.789-00").
  • EliEntradaNumero emite um number ou null.
  • EliEntradaDataHora trabalha com Strings ISO (ex: "2023-01-01T00:00:00Z"). A exibição é localizada, mas o valor do modelo é sempre ISO.