bkp
This commit is contained in:
parent
92662a0b13
commit
933ba17ae8
1 changed files with 344 additions and 480 deletions
|
|
@ -1,11 +1,221 @@
|
|||
<template>
|
||||
<!-- Render é feito no script via função render para suportar VNodeChild em células -->
|
||||
<div />
|
||||
<div class="eli-tabela">
|
||||
<div v-if="carregando" class="eli-tabela eli-tabela--carregando" aria-busy="true">
|
||||
Carregando...
|
||||
</div>
|
||||
|
||||
<div v-else-if="erro" class="eli-tabela eli-tabela--erro" role="alert">
|
||||
<div class="eli-tabela__erro-titulo">Erro</div>
|
||||
<div class="eli-tabela__erro-mensagem">{{ erro }}</div>
|
||||
</div>
|
||||
|
||||
<div v-else-if="!linhas.length" class="eli-tabela eli-tabela--vazio">
|
||||
{{ tabela.mensagemVazio ?? "Nenhum registro encontrado." }}
|
||||
</div>
|
||||
|
||||
<template v-else>
|
||||
<div v-if="exibirBusca || temAcoesCabecalho" class="eli-tabela__cabecalho">
|
||||
<EliTabelaCaixaDeBusca
|
||||
v-if="exibirBusca"
|
||||
:modelo="valorBusca"
|
||||
@buscar="atualizarBusca"
|
||||
/>
|
||||
|
||||
<div v-if="temAcoesCabecalho" class="eli-tabela__acoes-cabecalho">
|
||||
<button
|
||||
v-for="(botao, indice) in acoesCabecalho"
|
||||
:key="`${botao.rotulo}-${indice}`"
|
||||
type="button"
|
||||
class="eli-tabela__acoes-cabecalho-botao"
|
||||
:style="botao.cor ? { backgroundColor: botao.cor, color: '#fff' } : undefined"
|
||||
@click="botao.acao"
|
||||
>
|
||||
<component
|
||||
v-if="botao.icone"
|
||||
:is="botao.icone"
|
||||
class="eli-tabela__acoes-cabecalho-icone"
|
||||
:size="16"
|
||||
:stroke-width="2"
|
||||
/>
|
||||
<span class="eli-tabela__acoes-cabecalho-rotulo">{{ botao.rotulo }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="eli-tabela__table">
|
||||
<thead class="eli-tabela__thead">
|
||||
<tr class="eli-tabela__tr eli-tabela__tr--header">
|
||||
<th
|
||||
v-for="(coluna, idx) in tabela.colunas"
|
||||
:key="`th-${idx}`"
|
||||
class="eli-tabela__th"
|
||||
:class="[
|
||||
isOrdenavel(coluna) ? 'eli-tabela__th--ordenavel' : undefined,
|
||||
obterClasseAlinhamento(coluna.alinhamento),
|
||||
]"
|
||||
scope="col"
|
||||
>
|
||||
<button
|
||||
v-if="isOrdenavel(coluna)"
|
||||
type="button"
|
||||
class="eli-tabela__th-botao"
|
||||
:class="[
|
||||
colunaOrdenacao === String(coluna.coluna_ordem) ? 'eli-tabela__th-botao--ativo' : undefined,
|
||||
]"
|
||||
@click="alternarOrdenacao(String(coluna.coluna_ordem))"
|
||||
>
|
||||
<span class="eli-tabela__th-texto">{{ coluna.rotulo }}</span>
|
||||
|
||||
<component
|
||||
v-if="colunaOrdenacao === String(coluna.coluna_ordem)"
|
||||
:is="direcaoOrdenacao === 'asc' ? ArrowUp : ArrowDown"
|
||||
class="eli-tabela__th-icone"
|
||||
:size="16"
|
||||
:stroke-width="2"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<ArrowUp
|
||||
v-else
|
||||
class="eli-tabela__th-icone eli-tabela__th-icone--oculto"
|
||||
:size="16"
|
||||
:stroke-width="2"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</button>
|
||||
|
||||
<span v-else class="eli-tabela__th-label">{{ coluna.rotulo }}</span>
|
||||
</th>
|
||||
|
||||
<th
|
||||
v-if="temAcoes"
|
||||
class="eli-tabela__th eli-tabela__th--acoes"
|
||||
scope="col"
|
||||
>
|
||||
Ações
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody class="eli-tabela__tbody">
|
||||
<tr
|
||||
v-for="(linha, i) in linhas"
|
||||
:key="`tr-${i}`"
|
||||
class="eli-tabela__tr"
|
||||
:class="[i % 2 === 1 ? 'eli-tabela__tr--zebra' : undefined]"
|
||||
>
|
||||
<td
|
||||
v-for="(coluna, j) in tabela.colunas"
|
||||
:key="`td-${i}-${j}`"
|
||||
class="eli-tabela__td"
|
||||
:class="[
|
||||
coluna.acao ? 'eli-tabela__td--clicavel' : undefined,
|
||||
obterClasseAlinhamento(coluna.alinhamento),
|
||||
]"
|
||||
@click="coluna.acao ? () => coluna.acao?.() : undefined"
|
||||
>
|
||||
<span
|
||||
v-if="Boolean(coluna.truncar)"
|
||||
class="eli-tabela__celula-conteudo"
|
||||
:style="coluna.largura_maxima ? { maxWidth: obterMaxWidth(coluna.largura_maxima) } : undefined"
|
||||
:title="obterTooltipCelula(coluna.celula(linha as never))"
|
||||
>
|
||||
<EliTabelaCelula :celula="(coluna.celula(linha as never) as never)" />
|
||||
</span>
|
||||
|
||||
<EliTabelaCelula
|
||||
v-else
|
||||
:celula="(coluna.celula(linha as never) as never)"
|
||||
/>
|
||||
</td>
|
||||
|
||||
<td
|
||||
v-if="temAcoes"
|
||||
class="eli-tabela__td eli-tabela__td--acoes"
|
||||
:key="`td-${i}-acoes`"
|
||||
>
|
||||
<div
|
||||
class="eli-tabela__acoes-container"
|
||||
:class="[menuAberto === i ? 'eli-tabela__acoes-container--aberto' : undefined]"
|
||||
>
|
||||
<button
|
||||
class="eli-tabela__acoes-toggle"
|
||||
type="button"
|
||||
:id="`eli-tabela-acoes-toggle-${i}`"
|
||||
:disabled="!possuiAcoes(i)"
|
||||
:aria-haspopup="'menu'"
|
||||
:aria-expanded="menuAberto === i ? 'true' : 'false'"
|
||||
:aria-controls="possuiAcoes(i) ? `eli-tabela-acoes-menu-${i}` : undefined"
|
||||
:aria-label="possuiAcoes(i) ? 'Ações da linha' : 'Nenhuma ação disponível'"
|
||||
:title="possuiAcoes(i) ? 'Ações' : 'Nenhuma ação disponível'"
|
||||
@click.stop="toggleMenu(i)"
|
||||
>
|
||||
<MoreVertical class="eli-tabela__acoes-toggle-icone" :size="18" :stroke-width="2" />
|
||||
</button>
|
||||
|
||||
<ul
|
||||
v-if="menuAberto === i && possuiAcoes(i)"
|
||||
:id="`eli-tabela-acoes-menu-${i}`"
|
||||
class="eli-tabela__acoes-menu"
|
||||
role="menu"
|
||||
:aria-labelledby="`eli-tabela-acoes-toggle-${i}`"
|
||||
>
|
||||
<li
|
||||
v-for="item in acoesDisponiveisPorLinha(i)"
|
||||
:key="`acao-${i}-${item.indice}`"
|
||||
class="eli-tabela__acoes-item"
|
||||
role="none"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="eli-tabela__acoes-item-botao"
|
||||
:style="{ color: item.acao.cor }"
|
||||
role="menuitem"
|
||||
:aria-label="item.acao.rotulo"
|
||||
:title="item.acao.rotulo"
|
||||
@click.stop="
|
||||
() => {
|
||||
menuAberto = null;
|
||||
item.acao.acao(linha as never);
|
||||
}
|
||||
"
|
||||
>
|
||||
<component
|
||||
:is="item.acao.icone"
|
||||
class="eli-tabela__acoes-item-icone"
|
||||
:size="16"
|
||||
:stroke-width="2"
|
||||
/>
|
||||
<span class="eli-tabela__acoes-item-texto">{{ item.acao.rotulo }}</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<EliTabelaPaginacao
|
||||
v-if="totalPaginas > 1 && quantidade > 0"
|
||||
:pagina="paginaAtual"
|
||||
:totalPaginas="totalPaginas"
|
||||
:maximoBotoes="tabela.maximo_botoes_paginacao"
|
||||
@alterar="irParaPagina"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, h, onBeforeUnmount, onMounted, PropType, ref, watch } from "vue";
|
||||
import type { ComponentPublicInstance } from "vue";
|
||||
import {
|
||||
computed,
|
||||
defineComponent,
|
||||
onBeforeUnmount,
|
||||
onMounted,
|
||||
PropType,
|
||||
ref,
|
||||
watch,
|
||||
} from "vue";
|
||||
import { ArrowDown, ArrowUp, MoreVertical } from "lucide-vue-next";
|
||||
import { codigosResposta } from "p-respostas";
|
||||
import EliTabelaCaixaDeBusca from "./EliTabelaCaixaDeBusca.vue";
|
||||
|
|
@ -16,11 +226,16 @@ import type { EliTabelaConsulta } from "./types-eli-tabela";
|
|||
export default defineComponent({
|
||||
name: "EliTabela",
|
||||
inheritAttrs: false,
|
||||
components: {
|
||||
EliTabelaCaixaDeBusca,
|
||||
EliTabelaPaginacao,
|
||||
EliTabelaCelula,
|
||||
ArrowUp,
|
||||
ArrowDown,
|
||||
MoreVertical,
|
||||
},
|
||||
props: {
|
||||
tabela: {
|
||||
// Observação: este componente é “generic-friendly”.
|
||||
// Usamos `any` aqui para permitir passar `EliTabelaConsulta<T>` de qualquer T
|
||||
// sem brigar com invariância do TS (por causa do callback `celula(linha: T)`).
|
||||
type: Object as PropType<EliTabelaConsulta<any>>,
|
||||
required: true,
|
||||
},
|
||||
|
|
@ -30,16 +245,22 @@ export default defineComponent({
|
|||
const erro = ref<string | null>(null);
|
||||
const linhas = ref<unknown[]>([]);
|
||||
const quantidade = ref<number>(0);
|
||||
|
||||
const acoesVisiveis = ref<boolean[][]>([]);
|
||||
const menuAberto = ref<number | null>(null);
|
||||
const menuElementos = new Map<number, HTMLElement>();
|
||||
|
||||
const valorBusca = ref<string>("");
|
||||
const paginaAtual = ref(1);
|
||||
const colunaOrdenacao = ref<string | null>(null);
|
||||
const direcaoOrdenacao = ref<"asc" | "desc">("asc");
|
||||
|
||||
const tabela = computed(() => props.tabela);
|
||||
|
||||
const exibirBusca = computed(() => Boolean(props.tabela.mostrarCaixaDeBusca));
|
||||
const acoesCabecalho = computed(() => props.tabela.acoesTabela ?? []);
|
||||
const temAcoesCabecalho = computed(() => acoesCabecalho.value.length > 0);
|
||||
|
||||
const registrosPorConsulta = computed(() => {
|
||||
const valor = props.tabela.registros_por_consulta;
|
||||
if (typeof valor === "number" && valor > 0) {
|
||||
|
|
@ -47,19 +268,19 @@ export default defineComponent({
|
|||
}
|
||||
return 10;
|
||||
});
|
||||
|
||||
const totalPaginas = computed(() => {
|
||||
const limite = registrosPorConsulta.value;
|
||||
if (!limite || limite <= 0) {
|
||||
return 1;
|
||||
}
|
||||
if (!limite || limite <= 0) return 1;
|
||||
|
||||
const total = quantidade.value;
|
||||
if (!total) {
|
||||
return 1;
|
||||
}
|
||||
if (!total) return 1;
|
||||
|
||||
return Math.max(1, Math.ceil(total / limite));
|
||||
});
|
||||
|
||||
const temAcoes = computed(() => (props.tabela.acoesLinha ?? []).length > 0);
|
||||
|
||||
let carregamentoSequencial = 0;
|
||||
|
||||
function registrarMenuElemento(indice: number, elemento: HTMLElement | null) {
|
||||
|
|
@ -70,26 +291,28 @@ export default defineComponent({
|
|||
}
|
||||
}
|
||||
|
||||
function criarRegistradorMenu(indice: number) {
|
||||
return (elemento: Element | ComponentPublicInstance | null) => {
|
||||
if (elemento instanceof HTMLElement) {
|
||||
registrarMenuElemento(indice, elemento);
|
||||
} else {
|
||||
registrarMenuElemento(indice, null);
|
||||
function handleClickFora(evento: MouseEvent) {
|
||||
if (menuAberto.value === null) return;
|
||||
|
||||
const container = menuElementos.get(menuAberto.value);
|
||||
if (container && container.contains(evento.target as Node)) return;
|
||||
|
||||
menuAberto.value = null;
|
||||
}
|
||||
};
|
||||
|
||||
function isOrdenavel(coluna: any) {
|
||||
return coluna?.coluna_ordem !== undefined && coluna?.coluna_ordem !== null;
|
||||
}
|
||||
|
||||
function alternarOrdenacao(chave?: string) {
|
||||
if (!chave) {
|
||||
if (!chave) return;
|
||||
|
||||
if (colunaOrdenacao.value === chave) {
|
||||
direcaoOrdenacao.value = direcaoOrdenacao.value === "asc" ? "desc" : "asc";
|
||||
void carregar();
|
||||
return;
|
||||
}
|
||||
|
||||
if (colunaOrdenacao.value === chave) {
|
||||
direcaoOrdenacao.value =
|
||||
direcaoOrdenacao.value === "asc" ? "desc" : "asc";
|
||||
void carregar();
|
||||
} else {
|
||||
colunaOrdenacao.value = chave;
|
||||
direcaoOrdenacao.value = "asc";
|
||||
if (paginaAtual.value !== 1) {
|
||||
|
|
@ -98,12 +321,10 @@ export default defineComponent({
|
|||
void carregar();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function atualizarBusca(texto: string) {
|
||||
if (valorBusca.value === texto) {
|
||||
return;
|
||||
}
|
||||
if (valorBusca.value === texto) return;
|
||||
|
||||
valorBusca.value = texto;
|
||||
if (paginaAtual.value !== 1) {
|
||||
paginaAtual.value = 1;
|
||||
|
|
@ -119,40 +340,19 @@ export default defineComponent({
|
|||
}
|
||||
}
|
||||
|
||||
function handleClickFora(evento: MouseEvent) {
|
||||
if (menuAberto.value === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const container = menuElementos.get(menuAberto.value);
|
||||
if (container && container.contains(evento.target as Node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
menuAberto.value = null;
|
||||
}
|
||||
|
||||
function obterClasseAlinhamento(alinhamento?: string) {
|
||||
if (alinhamento === "direita") {
|
||||
return "eli-tabela__celula--direita";
|
||||
}
|
||||
if (alinhamento === "centro") {
|
||||
return "eli-tabela__celula--centro";
|
||||
}
|
||||
if (alinhamento === "direita") return "eli-tabela__celula--direita";
|
||||
if (alinhamento === "centro") return "eli-tabela__celula--centro";
|
||||
return "eli-tabela__celula--esquerda";
|
||||
}
|
||||
|
||||
function obterMaxWidth(largura?: number | string) {
|
||||
if (largura === undefined || largura === null) {
|
||||
return undefined;
|
||||
}
|
||||
if (largura === undefined || largura === null) return undefined;
|
||||
return typeof largura === "number" ? `${largura}px` : String(largura);
|
||||
}
|
||||
|
||||
function obterTooltipCelula(celula: unknown) {
|
||||
if (!Array.isArray(celula)) {
|
||||
return undefined;
|
||||
}
|
||||
if (!Array.isArray(celula)) return undefined;
|
||||
|
||||
const tipo = celula[0];
|
||||
const dados = celula[1] as any;
|
||||
|
|
@ -168,39 +368,35 @@ export default defineComponent({
|
|||
return undefined;
|
||||
}
|
||||
|
||||
function renderErro(mensagem: string) {
|
||||
return h(
|
||||
"div",
|
||||
{
|
||||
class: "eli-tabela eli-tabela--erro",
|
||||
role: "alert",
|
||||
},
|
||||
[
|
||||
h("div", { class: "eli-tabela__erro-titulo" }, "Erro"),
|
||||
h("div", { class: "eli-tabela__erro-mensagem" }, mensagem),
|
||||
]
|
||||
);
|
||||
function acoesDisponiveisPorLinha(i: number) {
|
||||
const acoesLinha = props.tabela.acoesLinha ?? [];
|
||||
const visibilidade = acoesVisiveis.value[i] ?? [];
|
||||
|
||||
return acoesLinha
|
||||
.map((acao, indice) => {
|
||||
const fallbackVisivel =
|
||||
acao.exibir === undefined
|
||||
? true
|
||||
: typeof acao.exibir === "boolean"
|
||||
? acao.exibir
|
||||
: false;
|
||||
|
||||
return {
|
||||
acao,
|
||||
indice,
|
||||
visivel: visibilidade[indice] ?? fallbackVisivel,
|
||||
};
|
||||
})
|
||||
.filter((item) => item.visivel);
|
||||
}
|
||||
|
||||
function renderVazio(mensagem?: string) {
|
||||
return h(
|
||||
"div",
|
||||
{
|
||||
class: "eli-tabela eli-tabela--vazio",
|
||||
},
|
||||
mensagem ?? "Nenhum registro encontrado."
|
||||
);
|
||||
function possuiAcoes(i: number) {
|
||||
return acoesDisponiveisPorLinha(i).length > 0;
|
||||
}
|
||||
|
||||
function renderCarregando() {
|
||||
return h(
|
||||
"div",
|
||||
{
|
||||
class: "eli-tabela eli-tabela--carregando",
|
||||
"aria-busy": "true",
|
||||
},
|
||||
"Carregando..."
|
||||
);
|
||||
function toggleMenu(i: number) {
|
||||
if (!possuiAcoes(i)) return;
|
||||
menuAberto.value = menuAberto.value === i ? null : i;
|
||||
}
|
||||
|
||||
async function carregar() {
|
||||
|
|
@ -238,9 +434,7 @@ export default defineComponent({
|
|||
const tabelaConfig = props.tabela;
|
||||
const res = await tabelaConfig.consulta(parametrosConsulta);
|
||||
|
||||
if (idCarregamento !== carregamentoSequencial) {
|
||||
return;
|
||||
}
|
||||
if (idCarregamento !== carregamentoSequencial) return;
|
||||
|
||||
if (res.cod !== codigosResposta.sucesso) {
|
||||
linhas.value = [];
|
||||
|
|
@ -255,18 +449,13 @@ export default defineComponent({
|
|||
linhas.value = valores;
|
||||
quantidade.value = total;
|
||||
|
||||
const totalPaginasRecalculado = Math.max(
|
||||
1,
|
||||
Math.ceil((total || 0) / limite)
|
||||
);
|
||||
|
||||
const totalPaginasRecalculado = Math.max(1, Math.ceil((total || 0) / limite));
|
||||
if (paginaAtual.value > totalPaginasRecalculado) {
|
||||
paginaAtual.value = totalPaginasRecalculado;
|
||||
return;
|
||||
}
|
||||
|
||||
const acoesLinhaConfiguradas = tabelaConfig.acoesLinha ?? [];
|
||||
|
||||
if (!acoesLinhaConfiguradas.length) {
|
||||
acoesVisiveis.value = [];
|
||||
return;
|
||||
|
|
@ -274,14 +463,8 @@ export default defineComponent({
|
|||
|
||||
const preResultado = valores.map(() =>
|
||||
acoesLinhaConfiguradas.map((acao) => {
|
||||
if (acao.exibir === undefined) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeof acao.exibir === "boolean") {
|
||||
return acao.exibir;
|
||||
}
|
||||
|
||||
if (acao.exibir === undefined) return true;
|
||||
if (typeof acao.exibir === "boolean") return acao.exibir;
|
||||
return false;
|
||||
})
|
||||
);
|
||||
|
|
@ -292,13 +475,8 @@ export default defineComponent({
|
|||
valores.map(async (linha) =>
|
||||
Promise.all(
|
||||
acoesLinhaConfiguradas.map(async (acao) => {
|
||||
if (acao.exibir === undefined) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeof acao.exibir === "boolean") {
|
||||
return acao.exibir;
|
||||
}
|
||||
if (acao.exibir === undefined) return true;
|
||||
if (typeof acao.exibir === "boolean") return acao.exibir;
|
||||
|
||||
try {
|
||||
const resultado = acao.exibir(linha as never);
|
||||
|
|
@ -315,9 +493,7 @@ export default defineComponent({
|
|||
acoesVisiveis.value = visibilidade;
|
||||
}
|
||||
} catch (e) {
|
||||
if (idCarregamento !== carregamentoSequencial) {
|
||||
return;
|
||||
}
|
||||
if (idCarregamento !== carregamentoSequencial) return;
|
||||
|
||||
linhas.value = [];
|
||||
quantidade.value = 0;
|
||||
|
|
@ -334,6 +510,11 @@ export default defineComponent({
|
|||
void carregar();
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
document.removeEventListener("click", handleClickFora);
|
||||
menuElementos.clear();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.tabela.mostrarCaixaDeBusca,
|
||||
(mostrar) => {
|
||||
|
|
@ -349,15 +530,9 @@ export default defineComponent({
|
|||
);
|
||||
|
||||
watch(paginaAtual, (nova, antiga) => {
|
||||
if (nova !== antiga) {
|
||||
void carregar();
|
||||
}
|
||||
if (nova !== antiga) void carregar();
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
document.removeEventListener("click", handleClickFora);
|
||||
menuElementos.clear();
|
||||
});
|
||||
watch(
|
||||
() => props.tabela,
|
||||
() => {
|
||||
|
|
@ -390,366 +565,45 @@ export default defineComponent({
|
|||
menuElementos.clear();
|
||||
});
|
||||
|
||||
return () => {
|
||||
const tabela = props.tabela;
|
||||
|
||||
if (carregando.value) {
|
||||
return renderCarregando();
|
||||
}
|
||||
|
||||
if (erro.value) {
|
||||
return renderErro(erro.value);
|
||||
}
|
||||
|
||||
const colunas = tabela.colunas;
|
||||
const acoesLinha = tabela.acoesLinha ?? [];
|
||||
const temAcoes = acoesLinha.length > 0;
|
||||
|
||||
if (!linhas.value.length) {
|
||||
return renderVazio(tabela.mensagemVazio);
|
||||
}
|
||||
|
||||
const cabecalho = colunas.map((coluna) => {
|
||||
const chaveOrdenacao =
|
||||
coluna.coluna_ordem !== undefined
|
||||
? (coluna.coluna_ordem as unknown as string)
|
||||
: undefined;
|
||||
const ordenavel = Boolean(chaveOrdenacao);
|
||||
const ativa = ordenavel && colunaOrdenacao.value === chaveOrdenacao;
|
||||
const iconeOrdenacao = ordenavel
|
||||
? ativa
|
||||
? h(direcaoOrdenacao.value === "asc" ? ArrowUp : ArrowDown, {
|
||||
class: "eli-tabela__th-icone",
|
||||
size: 16,
|
||||
strokeWidth: 2,
|
||||
"aria-hidden": "true",
|
||||
})
|
||||
: h(ArrowUp, {
|
||||
class: "eli-tabela__th-icone eli-tabela__th-icone--oculto",
|
||||
size: 16,
|
||||
strokeWidth: 2,
|
||||
"aria-hidden": "true",
|
||||
})
|
||||
: null;
|
||||
|
||||
const conteudo = ordenavel
|
||||
? h(
|
||||
"button",
|
||||
{
|
||||
type: "button",
|
||||
class: [
|
||||
"eli-tabela__th-botao",
|
||||
ativa ? "eli-tabela__th-botao--ativo" : undefined,
|
||||
],
|
||||
onClick: () => alternarOrdenacao(chaveOrdenacao),
|
||||
},
|
||||
[
|
||||
h("span", { class: "eli-tabela__th-texto" }, coluna.rotulo),
|
||||
iconeOrdenacao,
|
||||
]
|
||||
)
|
||||
: h("span", { class: "eli-tabela__th-label" }, coluna.rotulo);
|
||||
|
||||
return h(
|
||||
"th",
|
||||
{
|
||||
class: [
|
||||
"eli-tabela__th",
|
||||
ordenavel ? "eli-tabela__th--ordenavel" : undefined,
|
||||
obterClasseAlinhamento(coluna.alinhamento),
|
||||
],
|
||||
scope: "col",
|
||||
},
|
||||
conteudo
|
||||
);
|
||||
});
|
||||
|
||||
if (temAcoes) {
|
||||
cabecalho.push(
|
||||
h(
|
||||
"th",
|
||||
{ class: "eli-tabela__th eli-tabela__th--acoes", scope: "col" },
|
||||
"Ações"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const conteudoTabela: ReturnType<typeof h>[] = [];
|
||||
|
||||
if (exibirBusca.value || temAcoesCabecalho.value) {
|
||||
const botoes = acoesCabecalho.value.map((botao, indice) =>
|
||||
h(
|
||||
"button",
|
||||
{
|
||||
key: `${botao.rotulo}-${indice}`,
|
||||
type: "button",
|
||||
class: "eli-tabela__acoes-cabecalho-botao",
|
||||
// Quando `cor` for informada, tratamos como cor de destaque do botão.
|
||||
// Também ajustamos o texto/ícone para branco para manter contraste.
|
||||
style: botao.cor
|
||||
? {
|
||||
backgroundColor: botao.cor,
|
||||
color: "#fff",
|
||||
}
|
||||
: undefined,
|
||||
onClick: botao.acao,
|
||||
},
|
||||
[
|
||||
botao.icone
|
||||
? h(botao.icone, {
|
||||
class: "eli-tabela__acoes-cabecalho-icone",
|
||||
size: 16,
|
||||
strokeWidth: 2,
|
||||
})
|
||||
: null,
|
||||
h("span", { class: "eli-tabela__acoes-cabecalho-rotulo" }, botao.rotulo),
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
conteudoTabela.push(
|
||||
h("div", { class: "eli-tabela__cabecalho" }, [
|
||||
exibirBusca.value
|
||||
? h(EliTabelaCaixaDeBusca, {
|
||||
modelo: valorBusca.value,
|
||||
onBuscar: atualizarBusca,
|
||||
})
|
||||
: null,
|
||||
temAcoesCabecalho.value
|
||||
? h("div", { class: "eli-tabela__acoes-cabecalho" }, botoes)
|
||||
: null,
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
conteudoTabela.push(
|
||||
h("table", { class: "eli-tabela__table" }, [
|
||||
h(
|
||||
"thead",
|
||||
{ class: "eli-tabela__thead" },
|
||||
h(
|
||||
"tr",
|
||||
{ class: "eli-tabela__tr eli-tabela__tr--header" },
|
||||
cabecalho
|
||||
)
|
||||
),
|
||||
h(
|
||||
"tbody",
|
||||
{ class: "eli-tabela__tbody" },
|
||||
linhas.value.map((linha, i) => {
|
||||
const celulas = colunas.map((coluna, j) =>
|
||||
(() => {
|
||||
const celula = coluna.celula(linha as never);
|
||||
const truncar = Boolean(coluna.truncar);
|
||||
const tooltip = truncar ? obterTooltipCelula(celula) : undefined;
|
||||
|
||||
const conteudo = h(EliTabelaCelula, {
|
||||
celula: celula as never,
|
||||
});
|
||||
|
||||
const conteudoFinal = truncar
|
||||
? h(
|
||||
"span",
|
||||
{
|
||||
class: "eli-tabela__celula-conteudo",
|
||||
style: coluna.largura_maxima
|
||||
? {
|
||||
maxWidth: obterMaxWidth(coluna.largura_maxima),
|
||||
}
|
||||
: undefined,
|
||||
title: tooltip,
|
||||
},
|
||||
conteudo
|
||||
)
|
||||
: conteudo;
|
||||
|
||||
return h(
|
||||
"td",
|
||||
{
|
||||
class: [
|
||||
"eli-tabela__td",
|
||||
coluna.acao ? "eli-tabela__td--clicavel" : undefined,
|
||||
obterClasseAlinhamento(coluna.alinhamento),
|
||||
],
|
||||
key: `${i}-${j}`,
|
||||
onClick: coluna.acao ? () => coluna.acao?.() : undefined,
|
||||
},
|
||||
conteudoFinal
|
||||
);
|
||||
})()
|
||||
);
|
||||
|
||||
if (temAcoes) {
|
||||
const visibilidade = acoesVisiveis.value[i] ?? [];
|
||||
const acoesDisponiveis = acoesLinha
|
||||
.map((acao, indice) => {
|
||||
const fallbackVisivel =
|
||||
acao.exibir === undefined
|
||||
? true
|
||||
: typeof acao.exibir === "boolean"
|
||||
? acao.exibir
|
||||
: false;
|
||||
|
||||
return {
|
||||
acao,
|
||||
indice,
|
||||
visivel: visibilidade[indice] ?? fallbackVisivel,
|
||||
};
|
||||
})
|
||||
.filter((item) => item.visivel);
|
||||
// state
|
||||
tabela,
|
||||
carregando,
|
||||
erro,
|
||||
linhas,
|
||||
quantidade,
|
||||
menuAberto,
|
||||
valorBusca,
|
||||
paginaAtual,
|
||||
colunaOrdenacao,
|
||||
direcaoOrdenacao,
|
||||
totalPaginas,
|
||||
|
||||
const possuiAcoes = acoesDisponiveis.length > 0;
|
||||
// computed
|
||||
exibirBusca,
|
||||
acoesCabecalho,
|
||||
temAcoesCabecalho,
|
||||
temAcoes,
|
||||
|
||||
if (!possuiAcoes && menuAberto.value === i) {
|
||||
menuAberto.value = null;
|
||||
}
|
||||
// icons
|
||||
ArrowUp,
|
||||
ArrowDown,
|
||||
MoreVertical,
|
||||
|
||||
const estaAberto = menuAberto.value === i;
|
||||
const toggleId = `eli-tabela-acoes-toggle-${i}`;
|
||||
const menuId = `eli-tabela-acoes-menu-${i}`;
|
||||
// helpers
|
||||
isOrdenavel,
|
||||
obterClasseAlinhamento,
|
||||
obterMaxWidth,
|
||||
obterTooltipCelula,
|
||||
|
||||
const botaoToggle = h(
|
||||
"button",
|
||||
{
|
||||
id: toggleId,
|
||||
class: "eli-tabela__acoes-toggle",
|
||||
type: "button",
|
||||
disabled: !possuiAcoes,
|
||||
onClick: (evento: MouseEvent) => {
|
||||
evento.stopPropagation();
|
||||
if (!possuiAcoes) {
|
||||
return;
|
||||
}
|
||||
menuAberto.value = estaAberto ? null : i;
|
||||
},
|
||||
"aria-haspopup": "menu",
|
||||
"aria-expanded": estaAberto ? "true" : "false",
|
||||
"aria-controls": possuiAcoes ? menuId : undefined,
|
||||
"aria-label": possuiAcoes
|
||||
? "Ações da linha"
|
||||
: "Nenhuma ação disponível",
|
||||
title: possuiAcoes ? "Ações" : "Nenhuma ação disponível",
|
||||
},
|
||||
[
|
||||
h(MoreVertical, {
|
||||
class: "eli-tabela__acoes-toggle-icone",
|
||||
size: 18,
|
||||
strokeWidth: 2,
|
||||
}),
|
||||
]
|
||||
);
|
||||
|
||||
const menu =
|
||||
estaAberto && possuiAcoes
|
||||
? h(
|
||||
"ul",
|
||||
{
|
||||
id: menuId,
|
||||
class: "eli-tabela__acoes-menu",
|
||||
role: "menu",
|
||||
"aria-labelledby": toggleId,
|
||||
},
|
||||
acoesDisponiveis.map(({ acao, indice }) =>
|
||||
h(
|
||||
"li",
|
||||
{
|
||||
key: `acao-${indice}`,
|
||||
class: "eli-tabela__acoes-item",
|
||||
role: "none",
|
||||
},
|
||||
h(
|
||||
"button",
|
||||
{
|
||||
type: "button",
|
||||
class: "eli-tabela__acoes-item-botao",
|
||||
style: {
|
||||
color: acao.cor,
|
||||
},
|
||||
onClick: (evento: MouseEvent) => {
|
||||
evento.stopPropagation();
|
||||
menuAberto.value = null;
|
||||
acao.acao(linha as never);
|
||||
},
|
||||
role: "menuitem",
|
||||
"aria-label": acao.rotulo,
|
||||
title: acao.rotulo,
|
||||
},
|
||||
[
|
||||
h(acao.icone, {
|
||||
class: "eli-tabela__acoes-item-icone",
|
||||
size: 16,
|
||||
strokeWidth: 2,
|
||||
}),
|
||||
h(
|
||||
"span",
|
||||
{ class: "eli-tabela__acoes-item-texto" },
|
||||
acao.rotulo
|
||||
),
|
||||
]
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
: null;
|
||||
|
||||
const classesContainer = ["eli-tabela__acoes-container"];
|
||||
|
||||
if (estaAberto) {
|
||||
classesContainer.push("eli-tabela__acoes-container--aberto");
|
||||
}
|
||||
|
||||
celulas.push(
|
||||
h(
|
||||
"td",
|
||||
{
|
||||
class: ["eli-tabela__td", "eli-tabela__td--acoes"],
|
||||
key: `${i}-acoes`,
|
||||
},
|
||||
h(
|
||||
"div",
|
||||
{
|
||||
class: classesContainer,
|
||||
ref: criarRegistradorMenu(i),
|
||||
},
|
||||
[botaoToggle, menu]
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return h(
|
||||
"tr",
|
||||
{
|
||||
class: ["eli-tabela__tr", i % 2 === 1 ? "eli-tabela__tr--zebra" : undefined],
|
||||
key: i,
|
||||
},
|
||||
celulas
|
||||
);
|
||||
})
|
||||
),
|
||||
])
|
||||
);
|
||||
|
||||
if (totalPaginas.value > 1 && quantidade.value > 0) {
|
||||
conteudoTabela.push(
|
||||
h(EliTabelaPaginacao, {
|
||||
pagina: paginaAtual.value,
|
||||
totalPaginas: totalPaginas.value,
|
||||
maximoBotoes: props.tabela.maximo_botoes_paginacao,
|
||||
onAlterar: (pagina: number) => {
|
||||
irParaPagina(pagina);
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return h(
|
||||
"div",
|
||||
{
|
||||
class: "eli-tabela",
|
||||
},
|
||||
conteudoTabela
|
||||
);
|
||||
// actions
|
||||
alternarOrdenacao,
|
||||
atualizarBusca,
|
||||
irParaPagina,
|
||||
registrarMenuElemento,
|
||||
acoesDisponiveisPorLinha,
|
||||
possuiAcoes,
|
||||
toggleMenu,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
@ -766,6 +620,15 @@ export default defineComponent({
|
|||
border-spacing: 0;
|
||||
border: 1px solid rgba(0, 0, 0, 0.12);
|
||||
border-radius: 12px;
|
||||
/*
|
||||
IMPORTANTE:
|
||||
O menu de ações de cada linha usa `position: absolute`. Caso a tabela (ou
|
||||
algum ancestral) tenha `overflow: hidden`, o menu é recortado e “some”.
|
||||
*/
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.eli-tabela__tbody {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
|
|
@ -906,6 +769,7 @@ export default defineComponent({
|
|||
|
||||
.eli-tabela__td--acoes {
|
||||
white-space: nowrap;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.eli-tabela__acoes-container {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue