182 lines
8 KiB
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>
|
|
|