bkp
This commit is contained in:
parent
24c07da6f8
commit
052337b9da
4 changed files with 201 additions and 32 deletions
|
|
@ -6,9 +6,9 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, h, onBeforeUnmount, onMounted, PropType, ref, watch } from "vue";
|
import { defineComponent, h, onBeforeUnmount, onMounted, PropType, ref, watch } from "vue";
|
||||||
import type { ComponentPublicInstance } from "vue";
|
import type { ComponentPublicInstance } from "vue";
|
||||||
import { MoreVertical } from "lucide-vue-next";
|
import { ArrowDown, ArrowUp, MoreVertical } from "lucide-vue-next";
|
||||||
import { codigosResposta } from "p-respostas";
|
import { codigosResposta } from "p-respostas";
|
||||||
import type { EliTabelaConsulta } from "./types";
|
import type { EliTabelaConsulta } from "./types-eli-tabela";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "EliTabela",
|
name: "EliTabela",
|
||||||
|
|
@ -30,6 +30,8 @@ export default defineComponent({
|
||||||
const acoesVisiveis = ref<boolean[][]>([]);
|
const acoesVisiveis = ref<boolean[][]>([]);
|
||||||
const menuAberto = ref<number | null>(null);
|
const menuAberto = ref<number | null>(null);
|
||||||
const menuElementos = new Map<number, HTMLElement>();
|
const menuElementos = new Map<number, HTMLElement>();
|
||||||
|
const colunaOrdenacao = ref<string | null>(null);
|
||||||
|
const direcaoOrdenacao = ref<"asc" | "desc">("asc");
|
||||||
let carregamentoSequencial = 0;
|
let carregamentoSequencial = 0;
|
||||||
|
|
||||||
function registrarMenuElemento(indice: number, elemento: HTMLElement | null) {
|
function registrarMenuElemento(indice: number, elemento: HTMLElement | null) {
|
||||||
|
|
@ -50,6 +52,22 @@ export default defineComponent({
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function alternarOrdenacao(chave?: string) {
|
||||||
|
if (!chave) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (colunaOrdenacao.value === chave) {
|
||||||
|
direcaoOrdenacao.value =
|
||||||
|
direcaoOrdenacao.value === "asc" ? "desc" : "asc";
|
||||||
|
} else {
|
||||||
|
colunaOrdenacao.value = chave;
|
||||||
|
direcaoOrdenacao.value = "asc";
|
||||||
|
}
|
||||||
|
|
||||||
|
void carregar();
|
||||||
|
}
|
||||||
|
|
||||||
function handleClickFora(evento: MouseEvent) {
|
function handleClickFora(evento: MouseEvent) {
|
||||||
if (menuAberto.value === null) {
|
if (menuAberto.value === null) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -115,9 +133,16 @@ export default defineComponent({
|
||||||
menuAberto.value = null;
|
menuAberto.value = null;
|
||||||
menuElementos.clear();
|
menuElementos.clear();
|
||||||
|
|
||||||
|
const parametrosOrdenacao = colunaOrdenacao.value
|
||||||
|
? {
|
||||||
|
coluna_ordem: colunaOrdenacao.value as never,
|
||||||
|
direcao_ordem: direcaoOrdenacao.value,
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const tabelaConfig = props.tabela;
|
const tabelaConfig = props.tabela;
|
||||||
const res = await tabelaConfig.resposta();
|
const res = await tabelaConfig.consulta(parametrosOrdenacao);
|
||||||
|
|
||||||
if (idCarregamento !== carregamentoSequencial) {
|
if (idCarregamento !== carregamentoSequencial) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -145,10 +170,15 @@ export default defineComponent({
|
||||||
|
|
||||||
const preResultado = valores.map(() =>
|
const preResultado = valores.map(() =>
|
||||||
acoes.map((acao) => {
|
acoes.map((acao) => {
|
||||||
|
if (acao.exibir === undefined) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (typeof acao.exibir === "boolean") {
|
if (typeof acao.exibir === "boolean") {
|
||||||
return acao.exibir;
|
return acao.exibir;
|
||||||
}
|
}
|
||||||
return acao.exibir ? false : true;
|
|
||||||
|
return false;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -209,6 +239,8 @@ export default defineComponent({
|
||||||
() => {
|
() => {
|
||||||
menuAberto.value = null;
|
menuAberto.value = null;
|
||||||
menuElementos.clear();
|
menuElementos.clear();
|
||||||
|
colunaOrdenacao.value = null;
|
||||||
|
direcaoOrdenacao.value = "asc";
|
||||||
// Caso a definição de tabela/consulta mude
|
// Caso a definição de tabela/consulta mude
|
||||||
void carregar();
|
void carregar();
|
||||||
}
|
}
|
||||||
|
|
@ -238,9 +270,59 @@ export default defineComponent({
|
||||||
return renderVazio(tabela.mensagemVazio);
|
return renderVazio(tabela.mensagemVazio);
|
||||||
}
|
}
|
||||||
|
|
||||||
const cabecalho = colunas.map((coluna) =>
|
const cabecalho = colunas.map((coluna) => {
|
||||||
h("th", { class: "eli-tabela__th", scope: "col" }, coluna.rotulo)
|
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,
|
||||||
|
],
|
||||||
|
scope: "col",
|
||||||
|
},
|
||||||
|
conteudo
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
if (temAcoes) {
|
if (temAcoes) {
|
||||||
cabecalho.push(
|
cabecalho.push(
|
||||||
|
|
@ -290,17 +372,20 @@ export default defineComponent({
|
||||||
if (temAcoes) {
|
if (temAcoes) {
|
||||||
const visibilidade = acoesVisiveis.value[i] ?? [];
|
const visibilidade = acoesVisiveis.value[i] ?? [];
|
||||||
const acoesDisponiveis = acoes
|
const acoesDisponiveis = acoes
|
||||||
.map((acao, indice) => ({
|
.map((acao, indice) => {
|
||||||
acao,
|
const fallbackVisivel =
|
||||||
indice,
|
acao.exibir === undefined
|
||||||
visivel:
|
? true
|
||||||
visibilidade[indice] ??
|
: typeof acao.exibir === "boolean"
|
||||||
(typeof acao.exibir === "boolean"
|
|
||||||
? acao.exibir
|
? acao.exibir
|
||||||
: acao.exibir
|
: false;
|
||||||
? false
|
|
||||||
: true),
|
return {
|
||||||
}))
|
acao,
|
||||||
|
indice,
|
||||||
|
visivel: visibilidade[indice] ?? fallbackVisivel,
|
||||||
|
};
|
||||||
|
})
|
||||||
.filter((item) => item.visivel);
|
.filter((item) => item.visivel);
|
||||||
|
|
||||||
const possuiAcoes = acoesDisponiveis.length > 0;
|
const possuiAcoes = acoesDisponiveis.length > 0;
|
||||||
|
|
@ -466,6 +551,57 @@ export default defineComponent({
|
||||||
background: rgba(0, 0, 0, 0.03);
|
background: rgba(0, 0, 0, 0.03);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.eli-tabela__th--ordenavel {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela__th--ordenavel .eli-tabela__th-botao {
|
||||||
|
padding: 10px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela__th-botao {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: 8px;
|
||||||
|
width: 100%;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
font: inherit;
|
||||||
|
color: inherit;
|
||||||
|
cursor: pointer;
|
||||||
|
text-align: left;
|
||||||
|
transition: color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela__th-botao:hover,
|
||||||
|
.eli-tabela__th-botao:focus-visible {
|
||||||
|
color: rgba(15, 23, 42, 0.85);
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela__th-botao:focus-visible {
|
||||||
|
outline: 2px solid rgba(37, 99, 235, 0.45);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela__th-botao--ativo {
|
||||||
|
color: rgba(37, 99, 235, 0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela__th-texto {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela__th-icone {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eli-tabela__th-icone--oculto {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.eli-tabela__tr:last-child .eli-tabela__td {
|
.eli-tabela__tr:last-child .eli-tabela__td {
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
export { default as EliTabela } from "./EliTabela.vue";
|
export { default as EliTabela } from "./EliTabela.vue";
|
||||||
|
|
||||||
export * from "./types";
|
export * from "./types-eli-tabela";
|
||||||
export * from "./celulas/EliCelulaTextoSimples";
|
export * from "./celulas/EliCelulaTextoSimples";
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ export type EliColuna<T> = {
|
||||||
rotulo: string;
|
rotulo: string;
|
||||||
celula: (linha: T) => ComponenteCelula;
|
celula: (linha: T) => ComponenteCelula;
|
||||||
acao?: () => void;
|
acao?: () => void;
|
||||||
|
coluna_ordem?: keyof T;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type EliConsultaPaginada<T> = {
|
export type EliConsultaPaginada<T> = {
|
||||||
|
|
@ -27,11 +28,14 @@ export type EliTabelaAcao<T> = {
|
||||||
* Estrutura de dados para uma tabela alimentada por uma consulta.
|
* Estrutura de dados para uma tabela alimentada por uma consulta.
|
||||||
*
|
*
|
||||||
* - `colunas`: definição de colunas e como renderizar cada célula
|
* - `colunas`: definição de colunas e como renderizar cada célula
|
||||||
* - `resposta`: função assíncrona que retorna uma resposta padronizada
|
* - `consulta`: função que recupera os dados, com suporte a ordenação
|
||||||
*/
|
*/
|
||||||
export type EliTabelaConsulta<T> = {
|
export type EliTabelaConsulta<T> = {
|
||||||
colunas: EliColuna<T>[];
|
colunas: EliColuna<T>[];
|
||||||
resposta: () => Promise<tipoResposta<EliConsultaPaginada<T>>>;
|
consulta: (parametrosConsulta?: {
|
||||||
|
coluna_ordem?: keyof T;
|
||||||
|
direcao_ordem?: "asc" | "desc";
|
||||||
|
}) => Promise<tipoResposta<EliConsultaPaginada<T>>>;
|
||||||
/** Mensagem exibida quando a consulta retorna ok porém sem dados. */
|
/** Mensagem exibida quando a consulta retorna ok porém sem dados. */
|
||||||
mensagemVazio?: string;
|
mensagemVazio?: string;
|
||||||
acoes?: EliTabelaAcao<T>[];
|
acoes?: EliTabelaAcao<T>[];
|
||||||
|
|
@ -42,22 +42,52 @@ export default defineComponent({
|
||||||
acao: (linha) => {
|
acao: (linha) => {
|
||||||
console.log("Remover registro de", linha.nome);
|
console.log("Remover registro de", linha.nome);
|
||||||
},
|
},
|
||||||
exibir: async () => {
|
exibir: (linha) => linha.email !== "ana@eli.com",
|
||||||
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const linhasPadrao: Linha[] = [
|
||||||
|
{ nome: "Ana", email: "ana@eli.com" },
|
||||||
|
{ nome: "Bruno", email: "bruno@eli.com" },
|
||||||
|
{ nome: "Carla", email: "carla@eli.com" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const ordenarLinhas = (
|
||||||
|
linhas: Linha[],
|
||||||
|
parametros?: { coluna_ordem?: keyof Linha; direcao_ordem?: "asc" | "desc" }
|
||||||
|
) => {
|
||||||
|
if (!parametros?.coluna_ordem) {
|
||||||
|
return [...linhas];
|
||||||
|
}
|
||||||
|
|
||||||
|
const direcao = parametros.direcao_ordem ?? "asc";
|
||||||
|
const chave = parametros.coluna_ordem;
|
||||||
|
const multiplicador = direcao === "asc" ? 1 : -1;
|
||||||
|
|
||||||
|
return [...linhas].sort((a, b) => {
|
||||||
|
const valorA = a[chave];
|
||||||
|
const valorB = b[chave];
|
||||||
|
|
||||||
|
return (
|
||||||
|
multiplicador *
|
||||||
|
String(valorA ?? "").localeCompare(String(valorB ?? ""), "pt-BR", {
|
||||||
|
sensitivity: "base",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const tabelaOk: EliTabelaConsulta<Linha> = {
|
const tabelaOk: EliTabelaConsulta<Linha> = {
|
||||||
colunas: [
|
colunas: [
|
||||||
{
|
{
|
||||||
rotulo: "Nome",
|
rotulo: "Nome",
|
||||||
celula: (l) => l.nome,
|
celula: (l) => l.nome,
|
||||||
|
coluna_ordem: "nome",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
rotulo: "E-mail",
|
rotulo: "E-mail",
|
||||||
celula: (l) => l.email,
|
celula: (l) => l.email,
|
||||||
|
coluna_ordem: "email",
|
||||||
acao: () => {
|
acao: () => {
|
||||||
// Exemplo de ação: poderia abrir detalhes
|
// Exemplo de ação: poderia abrir detalhes
|
||||||
console.log("clicou na coluna e-mail");
|
console.log("clicou na coluna e-mail");
|
||||||
|
|
@ -65,18 +95,17 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
acoes: acoesTabela,
|
acoes: acoesTabela,
|
||||||
resposta: async () => {
|
consulta: async (parametrosConsulta) => {
|
||||||
|
const valores = ordenarLinhas(linhasPadrao, parametrosConsulta);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
cod: codigosResposta.sucesso,
|
cod: codigosResposta.sucesso,
|
||||||
eCerto: true,
|
eCerto: true,
|
||||||
eErro: false,
|
eErro: false,
|
||||||
mensagem: undefined,
|
mensagem: undefined,
|
||||||
valor: {
|
valor: {
|
||||||
quantidade: 2,
|
quantidade: valores.length,
|
||||||
valores: [
|
valores,
|
||||||
{ nome: "Ana", email: "ana@eli.com" },
|
|
||||||
{ nome: "Bruno", email: "bruno@eli.com" },
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
@ -84,7 +113,7 @@ export default defineComponent({
|
||||||
|
|
||||||
const tabelaVazia: EliTabelaConsulta<Linha> = {
|
const tabelaVazia: EliTabelaConsulta<Linha> = {
|
||||||
colunas: tabelaOk.colunas,
|
colunas: tabelaOk.colunas,
|
||||||
resposta: async () => {
|
consulta: async () => {
|
||||||
return {
|
return {
|
||||||
cod: codigosResposta.sucesso,
|
cod: codigosResposta.sucesso,
|
||||||
eCerto: true,
|
eCerto: true,
|
||||||
|
|
@ -103,7 +132,7 @@ export default defineComponent({
|
||||||
const tabelaErro: EliTabelaConsulta<Linha> = {
|
const tabelaErro: EliTabelaConsulta<Linha> = {
|
||||||
colunas: tabelaOk.colunas,
|
colunas: tabelaOk.colunas,
|
||||||
acoes: acoesTabela,
|
acoes: acoesTabela,
|
||||||
resposta: async () => {
|
consulta: async () => {
|
||||||
return {
|
return {
|
||||||
cod: codigosResposta.erroConhecido,
|
cod: codigosResposta.erroConhecido,
|
||||||
eCerto: false,
|
eCerto: false,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue