bkp
This commit is contained in:
parent
0144788548
commit
e7357e064a
19 changed files with 14478 additions and 1364 deletions
397
src/componentes/EliTabela/EliTabelaModalFiltroAvancado.vue
Normal file
397
src/componentes/EliTabela/EliTabelaModalFiltroAvancado.vue
Normal file
|
|
@ -0,0 +1,397 @@
|
|||
<template>
|
||||
<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">
|
||||
<h3 class="eli-tabela-modal-filtro__titulo">Filtro avançado</h3>
|
||||
<button type="button" class="eli-tabela-modal-filtro__fechar" aria-label="Fechar" @click="emitFechar">
|
||||
×
|
||||
</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 class="eli-tabela-modal-filtro__lista">
|
||||
<div v-for="(linha, idx) in linhas" :key="idx" class="eli-tabela-modal-filtro__linha">
|
||||
<select v-model="linha.coluna" class="eli-tabela-modal-filtro__select">
|
||||
<option v-for="opt in colunasDisponiveis" :key="String(opt)" :value="opt">
|
||||
{{ String(opt) }}
|
||||
</option>
|
||||
</select>
|
||||
|
||||
<select v-model="linha.operador" class="eli-tabela-modal-filtro__select">
|
||||
<option v-for="op in operadoresDisponiveis" :key="op" :value="op">
|
||||
{{ op }}
|
||||
</option>
|
||||
</select>
|
||||
|
||||
<div class="eli-tabela-modal-filtro__entrada">
|
||||
<component
|
||||
:is="componenteEntrada(linha.entrada)"
|
||||
v-model:value="linha.valor"
|
||||
:opcoes="opcoesEntrada(linha.entrada)"
|
||||
density="compact"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="eli-tabela-modal-filtro__remover"
|
||||
title="Remover"
|
||||
aria-label="Remover"
|
||||
@click="remover(idx)"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="eli-tabela-modal-filtro__acoes">
|
||||
<button type="button" class="eli-tabela-modal-filtro__botao" @click="adicionar" :disabled="!filtrosBase.length">
|
||||
Adicionar filtro
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer class="eli-tabela-modal-filtro__footer">
|
||||
<button type="button" class="eli-tabela-modal-filtro__botao eli-tabela-modal-filtro__botao--sec" @click="emitLimpar">
|
||||
Limpar
|
||||
</button>
|
||||
<button type="button" class="eli-tabela-modal-filtro__botao eli-tabela-modal-filtro__botao--sec" @click="emitFechar">
|
||||
Cancelar
|
||||
</button>
|
||||
<button type="button" class="eli-tabela-modal-filtro__botao eli-tabela-modal-filtro__botao--prim" @click="emitSalvar">
|
||||
Aplicar
|
||||
</button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType, ref, watch } from "vue";
|
||||
import { operadores as Operadores } from "p-comuns";
|
||||
|
||||
import { EliEntradaTexto, EliEntradaNumero, EliEntradaDataHora } from "../EliEntrada";
|
||||
import type { ComponenteEntrada, TipoEntrada } from "../EliEntrada/tiposEntradas";
|
||||
import type { EliTabelaConsulta } from "./types-eli-tabela";
|
||||
|
||||
type Operador = keyof typeof Operadores;
|
||||
type FiltroBase<T> = NonNullable<EliTabelaConsulta<T>["filtroAvancado"]>[number];
|
||||
|
||||
type LinhaFiltro<T> = {
|
||||
coluna: keyof T;
|
||||
operador: Operador;
|
||||
entrada: ComponenteEntrada;
|
||||
valor: any;
|
||||
};
|
||||
|
||||
function isTipoEntrada(v: unknown): v is TipoEntrada {
|
||||
return v === "texto" || v === "numero" || v === "dataHora";
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: "EliTabelaModalFiltroAvancado",
|
||||
props: {
|
||||
aberto: { type: Boolean, required: true },
|
||||
filtrosBase: {
|
||||
type: Array as PropType<Array<FiltroBase<any>>>,
|
||||
required: true,
|
||||
},
|
||||
modelo: {
|
||||
type: Array as PropType<Array<any>>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
emits: {
|
||||
fechar: () => true,
|
||||
limpar: () => true,
|
||||
salvar: (_linhas: any[]) => true,
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const linhas = ref<Array<LinhaFiltro<any>>>([]);
|
||||
|
||||
const operadoresDisponiveis = Object.keys(Operadores) as Operador[];
|
||||
|
||||
const colunasDisponiveis = ref<string[]>([]);
|
||||
|
||||
function componenteEntrada(entrada: ComponenteEntrada) {
|
||||
const tipo = entrada?.[0];
|
||||
if (tipo === "numero") return EliEntradaNumero;
|
||||
if (tipo === "dataHora") return EliEntradaDataHora;
|
||||
return EliEntradaTexto;
|
||||
}
|
||||
|
||||
function opcoesEntrada(entrada: ComponenteEntrada) {
|
||||
const opcoes = entrada?.[1] as any;
|
||||
// garante rotulo para não ficar vazio visualmente dentro do modal
|
||||
if (opcoes && typeof opcoes === "object" && !opcoes.rotulo) {
|
||||
return { ...opcoes, rotulo: "Valor" };
|
||||
}
|
||||
return opcoes ?? { rotulo: "Valor" };
|
||||
}
|
||||
|
||||
function valorInicialPorEntrada(entrada: ComponenteEntrada) {
|
||||
const tipo = entrada?.[0];
|
||||
if (tipo === "numero") return null;
|
||||
return "";
|
||||
}
|
||||
|
||||
function normalizarModelo() {
|
||||
const base = props.filtrosBase ?? [];
|
||||
colunasDisponiveis.value = base.map((b) => String(b.coluna));
|
||||
|
||||
const modelo = Array.isArray(props.modelo) ? props.modelo : [];
|
||||
linhas.value = modelo.map((m: any) => {
|
||||
const entrada = m.entrada as ComponenteEntrada;
|
||||
const col = m.coluna as any;
|
||||
const op = (m.operador ?? "=") as Operador;
|
||||
const val = m.valor ?? valorInicialPorEntrada(entrada);
|
||||
|
||||
return {
|
||||
coluna: col,
|
||||
operador: op,
|
||||
entrada,
|
||||
valor: val,
|
||||
} as LinhaFiltro<any>;
|
||||
});
|
||||
|
||||
// se vazio e existe base, adiciona 1 linha default
|
||||
if (!linhas.value.length && base.length) {
|
||||
const b0 = base[0];
|
||||
linhas.value = [
|
||||
{
|
||||
coluna: b0.coluna as any,
|
||||
operador: (b0.operador as any) ?? "=",
|
||||
entrada: b0.entrada,
|
||||
valor: valorInicialPorEntrada(b0.entrada),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
// se algum filtro mudou a coluna para valor inválido, ajusta
|
||||
for (const l of linhas.value) {
|
||||
if (!colunasDisponiveis.value.includes(String(l.coluna))) {
|
||||
l.coluna = (base[0]?.coluna as any) ?? l.coluna;
|
||||
}
|
||||
if (!operadoresDisponiveis.includes(l.operador)) {
|
||||
l.operador = "=";
|
||||
}
|
||||
// sanity
|
||||
if (l.entrada && !isTipoEntrada(l.entrada[0])) {
|
||||
l.entrada = ["texto", { rotulo: "Valor" }] as any;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => [props.aberto, props.filtrosBase, props.modelo] as const,
|
||||
() => {
|
||||
if (props.aberto) normalizarModelo();
|
||||
},
|
||||
{ deep: true, immediate: true }
|
||||
);
|
||||
|
||||
function adicionar() {
|
||||
if (!props.filtrosBase.length) return;
|
||||
const b0 = props.filtrosBase[0];
|
||||
linhas.value.push({
|
||||
coluna: b0.coluna as any,
|
||||
operador: (b0.operador as any) ?? "=",
|
||||
entrada: b0.entrada,
|
||||
valor: valorInicialPorEntrada(b0.entrada),
|
||||
});
|
||||
}
|
||||
|
||||
function remover(idx: number) {
|
||||
linhas.value.splice(idx, 1);
|
||||
}
|
||||
|
||||
function emitFechar() {
|
||||
emit("fechar");
|
||||
}
|
||||
|
||||
function emitLimpar() {
|
||||
emit("limpar");
|
||||
}
|
||||
|
||||
function emitSalvar() {
|
||||
emit(
|
||||
"salvar",
|
||||
linhas.value.map((l) => ({
|
||||
coluna: l.coluna,
|
||||
operador: l.operador,
|
||||
entrada: l.entrada,
|
||||
valor: l.valor,
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
linhas,
|
||||
operadoresDisponiveis,
|
||||
colunasDisponiveis,
|
||||
componenteEntrada,
|
||||
opcoesEntrada,
|
||||
adicionar,
|
||||
remover,
|
||||
emitFechar,
|
||||
emitSalvar,
|
||||
emitLimpar,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.eli-tabela-modal-filtro__overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(15, 23, 42, 0.35);
|
||||
z-index: 4000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.eli-tabela-modal-filtro__modal {
|
||||
width: min(980px, 100%);
|
||||
background: #fff;
|
||||
border-radius: 14px;
|
||||
border: 1px solid rgba(15, 23, 42, 0.1);
|
||||
box-shadow: 0 18px 60px rgba(15, 23, 42, 0.25);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.eli-tabela-modal-filtro__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 14px 16px;
|
||||
border-bottom: 1px solid rgba(15, 23, 42, 0.08);
|
||||
}
|
||||
|
||||
.eli-tabela-modal-filtro__titulo {
|
||||
font-size: 1rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.eli-tabela-modal-filtro__fechar {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
border-radius: 10px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
font-size: 22px;
|
||||
line-height: 1;
|
||||
color: rgba(15, 23, 42, 0.8);
|
||||
}
|
||||
|
||||
.eli-tabela-modal-filtro__fechar:hover,
|
||||
.eli-tabela-modal-filtro__fechar:focus-visible {
|
||||
background: rgba(15, 23, 42, 0.06);
|
||||
}
|
||||
|
||||
.eli-tabela-modal-filtro__conteudo {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.eli-tabela-modal-filtro__vazio {
|
||||
opacity: 0.75;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.eli-tabela-modal-filtro__lista {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.eli-tabela-modal-filtro__linha {
|
||||
display: grid;
|
||||
grid-template-columns: 220px 120px 1fr 34px;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.eli-tabela-modal-filtro__select {
|
||||
height: 34px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid rgba(15, 23, 42, 0.12);
|
||||
padding: 0 10px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.eli-tabela-modal-filtro__entrada {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.eli-tabela-modal-filtro__remover {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid rgba(15, 23, 42, 0.12);
|
||||
background: #fff;
|
||||
cursor: pointer;
|
||||
font-size: 18px;
|
||||
line-height: 1;
|
||||
color: rgba(15, 23, 42, 0.8);
|
||||
}
|
||||
|
||||
.eli-tabela-modal-filtro__remover:hover,
|
||||
.eli-tabela-modal-filtro__remover:focus-visible {
|
||||
background: rgba(15, 23, 42, 0.06);
|
||||
}
|
||||
|
||||
.eli-tabela-modal-filtro__acoes {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.eli-tabela-modal-filtro__footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
padding: 14px 16px;
|
||||
border-top: 1px solid rgba(15, 23, 42, 0.08);
|
||||
}
|
||||
|
||||
.eli-tabela-modal-filtro__botao {
|
||||
height: 34px;
|
||||
padding: 0 14px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid rgba(15, 23, 42, 0.12);
|
||||
background: #fff;
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.eli-tabela-modal-filtro__botao:disabled {
|
||||
opacity: 0.55;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.eli-tabela-modal-filtro__botao--sec:hover,
|
||||
.eli-tabela-modal-filtro__botao--sec:focus-visible {
|
||||
background: rgba(15, 23, 42, 0.06);
|
||||
}
|
||||
|
||||
.eli-tabela-modal-filtro__botao--prim {
|
||||
border: none;
|
||||
background: rgba(37, 99, 235, 0.95);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.eli-tabela-modal-filtro__botao--prim:hover,
|
||||
.eli-tabela-modal-filtro__botao--prim:focus-visible {
|
||||
background: rgba(37, 99, 235, 1);
|
||||
}
|
||||
|
||||
@media (max-width: 880px) {
|
||||
.eli-tabela-modal-filtro__linha {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Loading…
Add table
Add a link
Reference in a new issue