vue-componentes/src/componentes/EliTabela/EliTabelaModalFiltroAvancado.vue

182 lines
8 KiB
Vue

<template>
<Transition name="eli-tabela-modal-filtro">
<div v-if="aberto" class="eli-tabela-modal-filtro__overlay" role="presentation" @click.self="emitFechar">
<div class="eli-tabela-modal-filtro__modal" role="dialog" aria-modal="true" aria-label="Filtro avançado">
<header class="eli-tabela-modal-filtro__header">
<div class="eli-tabela-modal-filtro__header-titulo">
<Filter class="eli-tabela-modal-filtro__header-icone" :size="18" :stroke-width="2" />
<h3 class="eli-tabela-modal-filtro__titulo">Filtro avançado</h3>
<span v-if="linhas.length" class="eli-tabela-modal-filtro__contador">
{{ linhas.length }} {{ linhas.length === 1 ? 'filtro' : 'filtros' }}
</span>
</div>
<button type="button" class="eli-tabela-modal-filtro__fechar" aria-label="Fechar" @click="emitFechar">
<X :size="18" :stroke-width="2" />
</button>
</header>
<div class="eli-tabela-modal-filtro__conteudo">
<div v-if="!filtrosBase.length" class="eli-tabela-modal-filtro__vazio">
Nenhum filtro configurado na tabela.
</div>
<div v-else>
<!-- Lista de filtros ativos -->
<div v-if="linhas.length" class="eli-tabela-modal-filtro__lista">
<div
v-for="(linha, idx) in linhas"
:key="String(linha.chave)"
class="eli-tabela-modal-filtro__linha"
>
<div class="eli-tabela-modal-filtro__linha-header">
<span
class="eli-tabela-modal-filtro__id-badge"
:title="`Identificador na fórmula: {c${idx + 1}}`"
>
c{{ idx + 1 }}
</span>
<span class="eli-tabela-modal-filtro__rotulo-filtro">{{ rotuloDoFiltroAtivo(linha.chave) }}</span>
<button
type="button"
class="eli-tabela-modal-filtro__remover"
title="Remover filtro"
aria-label="Remover filtro"
@click="remover(idx)"
>
<Trash2 :size="14" :stroke-width="2" />
</button>
</div>
<div class="eli-tabela-modal-filtro__entrada">
<component
:is="componenteEntrada(linha.entrada)"
v-bind="({ value: linha.valor, opcoes: opcoesEntrada(linha.entrada), density: 'compact' } as Record<string, unknown>)"
@update:value="(v: unknown) => (linha.valor = v)"
@input="(v: unknown) => (linha.valor = v)"
/>
</div>
</div>
</div>
<!-- Estado vazio orientado -->
<div v-else class="eli-tabela-modal-filtro__estado-vazio">
<Filter :size="32" :stroke-width="1.5" />
<span>Selecione um filtro abaixo para começar</span>
</div>
<!-- Rodapé: adicionar filtro + fórmula lado a lado -->
<div class="eli-tabela-modal-filtro__rodape-filtros">
<div class="eli-tabela-modal-filtro__acoes">
<select
v-model="chaveParaAdicionar"
class="eli-tabela-modal-filtro__select"
:disabled="!opcoesParaAdicionar.length"
>
<option disabled value="">Selecione um filtro…</option>
<option v-for="o in opcoesParaAdicionar" :key="String(o.chave)" :value="String(o.chave)">
{{ rotuloDoFiltro(o) }}
</option>
</select>
<button
type="button"
class="eli-tabela-modal-filtro__botao eli-tabela-modal-filtro__botao--prim"
@click="adicionar"
:disabled="!chaveParaAdicionar"
>
<Plus :size="15" :stroke-width="2.5" />
Adicionar
</button>
</div>
<!-- Fórmula: só aparece com 2+ filtros (com 1 filtro, fórmula não faz sentido) -->
<div v-if="linhas.length >= 2" class="eli-tabela-modal-filtro__formula-container">
<label class="eli-tabela-modal-filtro__formula-label">Fórmula</label>
<input
v-model="formulaCustomizada"
type="text"
class="eli-tabela-modal-filtro__formula-input"
:placeholder="'Ex: ' + formulaGerada"
/>
<span class="eli-tabela-modal-filtro__formula-ajuda" tabindex="0" aria-label="Ajuda sobre fórmulas">
<CircleHelp :size="15" :stroke-width="2" />
<div class="eli-tabela-modal-filtro__formula-tooltip" role="tooltip">
<strong>Como usar a fórmula</strong>
<p>Use os identificadores <code>{c1}</code>, <code>{c2}</code>, <code>{c3}</code>… correspondentes a cada filtro acima:</p>
<ul>
<li><code>E</code> — ambos devem ser verdadeiros</li>
<li><code>OU</code> — pelo menos um deve ser verdadeiro</li>
<li><code>NAO</code> — negação de uma condição</li>
<li><code>( )</code> — agrupa condições</li>
</ul>
<p><strong>Exemplo:</strong></p>
<code>{c1} OU ({c2} E NAO {c3})</code>
<p class="eli-tabela-modal-filtro__formula-tooltip-nota">Deixe em branco para combinar todos com E.</p>
</div>
</span>
</div>
</div>
</div>
</div>
<footer class="eli-tabela-modal-filtro__footer">
<button
type="button"
class="eli-tabela-modal-filtro__botao eli-tabela-modal-filtro__botao--perigo"
:disabled="!linhas.length"
@click="emitLimpar"
>
Limpar
</button>
<div class="eli-tabela-modal-filtro__footer-spacer"></div>
<button type="button" class="eli-tabela-modal-filtro__botao eli-tabela-modal-filtro__botao--sec" @click="emitFechar">
Cancelar
</button>
<button type="button" class="eli-tabela-modal-filtro__botao eli-tabela-modal-filtro__botao--prim" @click="emitSalvar">
Aplicar
</button>
</footer>
</div>
</div>
</Transition>
</template>
<script lang="ts">
import { CircleHelp, Filter, Plus, Trash2, X } from "@lucide/vue"
import { computed, defineComponent, type PropType } from "vue"
import type { FiltroBase } from "./useFiltroAvancado"
// biome-ignore lint/correctness/noUnusedImports: rotuloDoFiltro é usado no template Vue (invisível ao linter estático)
import { rotuloDoFiltro, useFiltroAvancado } from "./useFiltroAvancado"
import "./EliTabelaModalFiltroAvancado.css"
export default defineComponent({
name: "EliTabelaModalFiltroAvancado",
components: { CircleHelp, Filter, Plus, Trash2, X },
props: {
aberto: { type: Boolean, required: true },
filtrosBase: { type: Array as PropType<FiltroBase<string>[]>, required: true },
modelo: {
/** Linhas persistidas do pai — normalizadas internamente via normalizarModelo() */
type: Array as PropType<Array<{ chave: string; valor: unknown }>>,
required: true,
},
modeloFormula: { type: String, default: "" },
},
emits: {
fechar: () => true,
limpar: () => true,
/** Dado serializável emitido pelo botão Aplicar */
salvar: (_linhas: Array<{ chave: string; valor: unknown }>, _formula?: string) => true,
},
setup(props, { emit }) {
return useFiltroAvancado({
aberto: computed(() => props.aberto),
filtrosBase: computed(() => props.filtrosBase),
modelo: computed(() => props.modelo),
modeloFormula: computed(() => props.modeloFormula),
emit,
})
},
})
</script>