bkp
This commit is contained in:
parent
052337b9da
commit
c4a0d31686
3 changed files with 569 additions and 194 deletions
|
|
@ -4,7 +4,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, h, onBeforeUnmount, onMounted, PropType, ref, watch } from "vue";
|
||||
import { computed, defineComponent, h, onBeforeUnmount, onMounted, PropType, ref, watch } from "vue";
|
||||
import type { ComponentPublicInstance } from "vue";
|
||||
import { ArrowDown, ArrowUp, MoreVertical } from "lucide-vue-next";
|
||||
import { codigosResposta } from "p-respostas";
|
||||
|
|
@ -30,8 +30,29 @@ export default defineComponent({
|
|||
const acoesVisiveis = ref<boolean[][]>([]);
|
||||
const menuAberto = ref<number | null>(null);
|
||||
const menuElementos = new Map<number, HTMLElement>();
|
||||
const paginaAtual = ref(1);
|
||||
const colunaOrdenacao = ref<string | null>(null);
|
||||
const direcaoOrdenacao = ref<"asc" | "desc">("asc");
|
||||
const registrosPorConsulta = computed(() => {
|
||||
const valor = props.tabela.registros_por_consulta;
|
||||
if (typeof valor === "number" && valor > 0) {
|
||||
return Math.floor(valor);
|
||||
}
|
||||
return 10;
|
||||
});
|
||||
const totalPaginas = computed(() => {
|
||||
const limite = registrosPorConsulta.value;
|
||||
if (!limite || limite <= 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
const total = quantidade.value;
|
||||
if (!total) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return Math.max(1, Math.ceil(total / limite));
|
||||
});
|
||||
let carregamentoSequencial = 0;
|
||||
|
||||
function registrarMenuElemento(indice: number, elemento: HTMLElement | null) {
|
||||
|
|
@ -60,12 +81,91 @@ export default defineComponent({
|
|||
if (colunaOrdenacao.value === chave) {
|
||||
direcaoOrdenacao.value =
|
||||
direcaoOrdenacao.value === "asc" ? "desc" : "asc";
|
||||
void carregar();
|
||||
} else {
|
||||
colunaOrdenacao.value = chave;
|
||||
direcaoOrdenacao.value = "asc";
|
||||
if (paginaAtual.value !== 1) {
|
||||
paginaAtual.value = 1;
|
||||
} else {
|
||||
void carregar();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void carregar();
|
||||
function paginaAnterior() {
|
||||
if (paginaAtual.value > 1) {
|
||||
paginaAtual.value -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
function proximaPagina() {
|
||||
if (paginaAtual.value < totalPaginas.value) {
|
||||
paginaAtual.value += 1;
|
||||
}
|
||||
}
|
||||
|
||||
function irParaPagina(pagina: number) {
|
||||
const alvo = Math.min(Math.max(1, pagina), totalPaginas.value);
|
||||
if (alvo !== paginaAtual.value) {
|
||||
paginaAtual.value = alvo;
|
||||
}
|
||||
}
|
||||
|
||||
function rangePaginacao(maximoBotoes = 7) {
|
||||
const total = totalPaginas.value;
|
||||
const atual = paginaAtual.value;
|
||||
const resultado: Array<{ label: string; pagina?: number; ativo?: boolean; disabled?: boolean }> = [];
|
||||
|
||||
if (total <= maximoBotoes) {
|
||||
for (let pagina = 1; pagina <= total; pagina += 1) {
|
||||
resultado.push({
|
||||
label: String(pagina),
|
||||
pagina,
|
||||
ativo: pagina === atual,
|
||||
});
|
||||
}
|
||||
return resultado;
|
||||
}
|
||||
|
||||
const adicionarPagina = (pagina: number) => {
|
||||
resultado.push({
|
||||
label: String(pagina),
|
||||
pagina,
|
||||
ativo: pagina === atual,
|
||||
});
|
||||
};
|
||||
|
||||
const adicionarEllipsis = () => {
|
||||
resultado.push({ label: "…", disabled: true });
|
||||
};
|
||||
|
||||
const visiveis = Math.max(3, maximoBotoes - 2);
|
||||
let inicio = Math.max(2, atual - Math.floor(visiveis / 2));
|
||||
let fim = inicio + visiveis - 1;
|
||||
|
||||
if (fim >= total) {
|
||||
fim = total - 1;
|
||||
inicio = fim - visiveis + 1;
|
||||
}
|
||||
|
||||
adicionarPagina(1);
|
||||
|
||||
if (inicio > 2) {
|
||||
adicionarEllipsis();
|
||||
}
|
||||
|
||||
for (let pagina = inicio; pagina <= fim; pagina += 1) {
|
||||
adicionarPagina(pagina);
|
||||
}
|
||||
|
||||
if (fim < total - 1) {
|
||||
adicionarEllipsis();
|
||||
}
|
||||
|
||||
adicionarPagina(total);
|
||||
|
||||
return resultado;
|
||||
}
|
||||
|
||||
function handleClickFora(evento: MouseEvent) {
|
||||
|
|
@ -133,16 +233,27 @@ export default defineComponent({
|
|||
menuAberto.value = null;
|
||||
menuElementos.clear();
|
||||
|
||||
const parametrosOrdenacao = colunaOrdenacao.value
|
||||
? {
|
||||
coluna_ordem: colunaOrdenacao.value as never,
|
||||
direcao_ordem: direcaoOrdenacao.value,
|
||||
const limite = Math.max(1, registrosPorConsulta.value);
|
||||
const offset = (paginaAtual.value - 1) * limite;
|
||||
|
||||
const parametrosConsulta: {
|
||||
coluna_ordem?: never;
|
||||
direcao_ordem?: "asc" | "desc";
|
||||
offSet: number;
|
||||
limit: number;
|
||||
} = {
|
||||
offSet: offset,
|
||||
limit: limite,
|
||||
};
|
||||
|
||||
if (colunaOrdenacao.value) {
|
||||
parametrosConsulta.coluna_ordem = colunaOrdenacao.value as never;
|
||||
parametrosConsulta.direcao_ordem = direcaoOrdenacao.value;
|
||||
}
|
||||
: undefined;
|
||||
|
||||
try {
|
||||
const tabelaConfig = props.tabela;
|
||||
const res = await tabelaConfig.consulta(parametrosOrdenacao);
|
||||
const res = await tabelaConfig.consulta(parametrosConsulta);
|
||||
|
||||
if (idCarregamento !== carregamentoSequencial) {
|
||||
return;
|
||||
|
|
@ -161,6 +272,16 @@ export default defineComponent({
|
|||
linhas.value = valores;
|
||||
quantidade.value = total;
|
||||
|
||||
const totalPaginasRecalculado = Math.max(
|
||||
1,
|
||||
Math.ceil((total || 0) / limite)
|
||||
);
|
||||
|
||||
if (paginaAtual.value > totalPaginasRecalculado) {
|
||||
paginaAtual.value = totalPaginasRecalculado;
|
||||
return;
|
||||
}
|
||||
|
||||
const acoes = tabelaConfig.acoes ?? [];
|
||||
|
||||
if (!acoes.length) {
|
||||
|
|
@ -230,6 +351,12 @@ export default defineComponent({
|
|||
void carregar();
|
||||
});
|
||||
|
||||
watch(paginaAtual, (nova, antiga) => {
|
||||
if (nova !== antiga) {
|
||||
void carregar();
|
||||
}
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
document.removeEventListener("click", handleClickFora);
|
||||
menuElementos.clear();
|
||||
|
|
@ -241,9 +368,23 @@ export default defineComponent({
|
|||
menuElementos.clear();
|
||||
colunaOrdenacao.value = null;
|
||||
direcaoOrdenacao.value = "asc";
|
||||
// Caso a definição de tabela/consulta mude
|
||||
if (paginaAtual.value !== 1) {
|
||||
paginaAtual.value = 1;
|
||||
} else {
|
||||
void carregar();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.tabela.registros_por_consulta,
|
||||
() => {
|
||||
if (paginaAtual.value !== 1) {
|
||||
paginaAtual.value = 1;
|
||||
} else {
|
||||
void carregar();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
watch(linhas, () => {
|
||||
|
|
@ -334,12 +475,11 @@ export default defineComponent({
|
|||
);
|
||||
}
|
||||
|
||||
return h(
|
||||
"div",
|
||||
{
|
||||
class: "eli-tabela",
|
||||
},
|
||||
[
|
||||
const podeVoltar = paginaAtual.value > 1;
|
||||
const podeAvancar = paginaAtual.value < totalPaginas.value;
|
||||
const botoesPaginacao = rangePaginacao();
|
||||
|
||||
const conteudoTabela = [
|
||||
h("table", { class: "eli-tabela__table" }, [
|
||||
h(
|
||||
"thead",
|
||||
|
|
@ -482,9 +622,7 @@ export default defineComponent({
|
|||
)
|
||||
: null;
|
||||
|
||||
const classesContainer = [
|
||||
"eli-tabela__acoes-container",
|
||||
];
|
||||
const classesContainer = ["eli-tabela__acoes-container"];
|
||||
|
||||
if (estaAberto) {
|
||||
classesContainer.push("eli-tabela__acoes-container--aberto");
|
||||
|
|
@ -517,7 +655,85 @@ export default defineComponent({
|
|||
})
|
||||
),
|
||||
]),
|
||||
];
|
||||
|
||||
if (totalPaginas.value > 1 && quantidade.value > 0) {
|
||||
conteudoTabela.push(
|
||||
h(
|
||||
"nav",
|
||||
{
|
||||
class: "eli-tabela__paginacao",
|
||||
role: "navigation",
|
||||
"aria-label": "Paginação de resultados",
|
||||
},
|
||||
[
|
||||
h(
|
||||
"button",
|
||||
{
|
||||
type: "button",
|
||||
class: "eli-tabela__pagina-botao",
|
||||
onClick: paginaAnterior,
|
||||
disabled: !podeVoltar,
|
||||
"aria-label": "Página anterior",
|
||||
},
|
||||
"<<"
|
||||
),
|
||||
...botoesPaginacao.map((item, indice) =>
|
||||
item.disabled || !item.pagina
|
||||
? h(
|
||||
"span",
|
||||
{
|
||||
key: `${item.label}-${indice}`,
|
||||
class: "eli-tabela__pagina-ellipsis",
|
||||
"aria-hidden": "true",
|
||||
},
|
||||
item.label
|
||||
)
|
||||
: h(
|
||||
"button",
|
||||
{
|
||||
key: `${item.label}-${indice}`,
|
||||
type: "button",
|
||||
class: [
|
||||
"eli-tabela__pagina-botao",
|
||||
item.ativo
|
||||
? "eli-tabela__pagina-botao--ativo"
|
||||
: undefined,
|
||||
],
|
||||
disabled: item.ativo,
|
||||
"aria-current": item.ativo ? "page" : undefined,
|
||||
"aria-label": `Ir para página ${item.label}`,
|
||||
onClick: () => {
|
||||
if (item.pagina) {
|
||||
irParaPagina(item.pagina);
|
||||
}
|
||||
},
|
||||
},
|
||||
item.label
|
||||
)
|
||||
),
|
||||
h(
|
||||
"button",
|
||||
{
|
||||
type: "button",
|
||||
class: "eli-tabela__pagina-botao",
|
||||
onClick: proximaPagina,
|
||||
disabled: !podeAvancar,
|
||||
"aria-label": "Próxima página",
|
||||
},
|
||||
">>"
|
||||
),
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return h(
|
||||
"div",
|
||||
{
|
||||
class: "eli-tabela",
|
||||
},
|
||||
conteudoTabela
|
||||
);
|
||||
};
|
||||
},
|
||||
|
|
@ -602,6 +818,66 @@ export default defineComponent({
|
|||
opacity: 0;
|
||||
}
|
||||
|
||||
.eli-tabela__paginacao {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
margin-top: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.eli-tabela__pagina-botao {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
padding: 6px 14px;
|
||||
border-radius: 9999px;
|
||||
border: 1px solid rgba(15, 23, 42, 0.12);
|
||||
background: #ffffff;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
color: rgba(15, 23, 42, 0.82);
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease, border-color 0.2s ease, color 0.2s ease;
|
||||
}
|
||||
|
||||
.eli-tabela__pagina-botao:hover,
|
||||
.eli-tabela__pagina-botao:focus-visible {
|
||||
background-color: rgba(37, 99, 235, 0.08);
|
||||
border-color: rgba(37, 99, 235, 0.4);
|
||||
color: rgba(37, 99, 235, 0.95);
|
||||
}
|
||||
|
||||
.eli-tabela__pagina-botao:focus-visible {
|
||||
outline: 2px solid rgba(37, 99, 235, 0.45);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.eli-tabela__pagina-botao:disabled {
|
||||
cursor: default;
|
||||
opacity: 0.5;
|
||||
background: rgba(148, 163, 184, 0.08);
|
||||
border-color: rgba(148, 163, 184, 0.18);
|
||||
color: rgba(71, 85, 105, 0.75);
|
||||
}
|
||||
|
||||
.eli-tabela__pagina-botao--ativo {
|
||||
background: rgba(37, 99, 235, 0.12);
|
||||
border-color: rgba(37, 99, 235, 0.4);
|
||||
color: rgba(37, 99, 235, 0.95);
|
||||
}
|
||||
|
||||
.eli-tabela__pagina-ellipsis {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 32px;
|
||||
color: rgba(107, 114, 128, 0.85);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.eli-tabela__tr:last-child .eli-tabela__td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,9 +32,12 @@ export type EliTabelaAcao<T> = {
|
|||
*/
|
||||
export type EliTabelaConsulta<T> = {
|
||||
colunas: EliColuna<T>[];
|
||||
registros_por_consulta?: number;
|
||||
consulta: (parametrosConsulta?: {
|
||||
coluna_ordem?: keyof T;
|
||||
direcao_ordem?: "asc" | "desc";
|
||||
offSet?: number;
|
||||
limit?: number;
|
||||
}) => Promise<tipoResposta<EliConsultaPaginada<T>>>;
|
||||
/** Mensagem exibida quando a consulta retorna ok porém sem dados. */
|
||||
mensagemVazio?: string;
|
||||
|
|
|
|||
|
|
@ -50,6 +50,84 @@ export default defineComponent({
|
|||
{ nome: "Ana", email: "ana@eli.com" },
|
||||
{ nome: "Bruno", email: "bruno@eli.com" },
|
||||
{ nome: "Carla", email: "carla@eli.com" },
|
||||
|
||||
{ nome: "Ana", email: "ana@eli.com" },
|
||||
{ nome: "Bruno", email: "bruno@eli.com" },
|
||||
{ nome: "Carla", email: "carla@eli.com" },
|
||||
|
||||
|
||||
{ nome: "Ana", email: "ana@eli.com" },
|
||||
{ nome: "Bruno", email: "bruno@eli.com" },
|
||||
{ nome: "Carla", email: "carla@eli.com" },
|
||||
|
||||
|
||||
{ nome: "Ana", email: "ana@eli.com" },
|
||||
{ nome: "Bruno", email: "bruno@eli.com" },
|
||||
{ nome: "Carla", email: "carla@eli.com" },
|
||||
|
||||
|
||||
{ nome: "Ana", email: "ana@eli.com" },
|
||||
{ nome: "Bruno", email: "bruno@eli.com" },
|
||||
{ nome: "Carla", email: "carla@eli.com" },
|
||||
|
||||
|
||||
{ nome: "Ana", email: "ana@eli.com" },
|
||||
{ nome: "Bruno", email: "bruno@eli.com" },
|
||||
{ nome: "Carla", email: "carla@eli.com" },
|
||||
|
||||
|
||||
{ nome: "Ana", email: "ana@eli.com" },
|
||||
{ nome: "Bruno", email: "bruno@eli.com" },
|
||||
{ nome: "Carla", email: "carla@eli.com" },
|
||||
|
||||
|
||||
|
||||
{ nome: "Ana", email: "ana@eli.com" },
|
||||
{ nome: "Bruno", email: "bruno@eli.com" },
|
||||
{ nome: "Carla", email: "carla@eli.com" },
|
||||
|
||||
|
||||
|
||||
{ nome: "Ana", email: "ana@eli.com" },
|
||||
{ nome: "Bruno", email: "bruno@eli.com" },
|
||||
{ nome: "Carla", email: "carla@eli.com" },
|
||||
|
||||
|
||||
|
||||
{ nome: "Ana", email: "ana@eli.com" },
|
||||
{ nome: "Bruno", email: "bruno@eli.com" },
|
||||
{ nome: "Carla", email: "carla@eli.com" },
|
||||
|
||||
|
||||
|
||||
{ nome: "Ana", email: "ana@eli.com" },
|
||||
{ nome: "Bruno", email: "bruno@eli.com" },
|
||||
{ nome: "Carla", email: "carla@eli.com" },
|
||||
|
||||
|
||||
{ nome: "Ana", email: "ana@eli.com" },
|
||||
{ nome: "Bruno", email: "bruno@eli.com" },
|
||||
{ nome: "Carla", email: "carla@eli.com" },
|
||||
|
||||
|
||||
|
||||
{ nome: "Ana", email: "ana@eli.com" },
|
||||
{ nome: "Bruno", email: "bruno@eli.com" },
|
||||
{ nome: "Carla", email: "carla@eli.com" },
|
||||
|
||||
|
||||
|
||||
{ nome: "Ana", email: "ana@eli.com" },
|
||||
{ nome: "Bruno", email: "bruno@eli.com" },
|
||||
{ nome: "Carla", email: "carla@eli.com" },
|
||||
|
||||
|
||||
{ nome: "Ana", email: "ana@eli.com" },
|
||||
{ nome: "Bruno", email: "bruno@eli.com" },
|
||||
{ nome: "Carla", email: "carla@eli.com" },
|
||||
|
||||
|
||||
|
||||
];
|
||||
|
||||
const ordenarLinhas = (
|
||||
|
|
@ -77,7 +155,22 @@ export default defineComponent({
|
|||
});
|
||||
};
|
||||
|
||||
const aplicarPaginacao = (
|
||||
linhas: Linha[],
|
||||
parametros?: { offSet?: number; limit?: number }
|
||||
) => {
|
||||
const offset = Math.max(0, parametros?.offSet ?? 0);
|
||||
const limit = parametros?.limit ?? linhas.length;
|
||||
|
||||
if (limit === undefined || limit <= 0) {
|
||||
return linhas.slice(offset);
|
||||
}
|
||||
|
||||
return linhas.slice(offset, offset + limit);
|
||||
};
|
||||
|
||||
const tabelaOk: EliTabelaConsulta<Linha> = {
|
||||
registros_por_consulta: 2,
|
||||
colunas: [
|
||||
{
|
||||
rotulo: "Nome",
|
||||
|
|
@ -96,7 +189,8 @@ export default defineComponent({
|
|||
],
|
||||
acoes: acoesTabela,
|
||||
consulta: async (parametrosConsulta) => {
|
||||
const valores = ordenarLinhas(linhasPadrao, parametrosConsulta);
|
||||
const ordenadas = ordenarLinhas(linhasPadrao, parametrosConsulta);
|
||||
const valores = aplicarPaginacao(ordenadas, parametrosConsulta);
|
||||
|
||||
return {
|
||||
cod: codigosResposta.sucesso,
|
||||
|
|
@ -104,7 +198,7 @@ export default defineComponent({
|
|||
eErro: false,
|
||||
mensagem: undefined,
|
||||
valor: {
|
||||
quantidade: valores.length,
|
||||
quantidade: linhasPadrao.length,
|
||||
valores,
|
||||
},
|
||||
};
|
||||
|
|
@ -112,8 +206,9 @@ export default defineComponent({
|
|||
};
|
||||
|
||||
const tabelaVazia: EliTabelaConsulta<Linha> = {
|
||||
registros_por_consulta: tabelaOk.registros_por_consulta,
|
||||
colunas: tabelaOk.colunas,
|
||||
consulta: async () => {
|
||||
consulta: async (_parametrosConsulta) => {
|
||||
return {
|
||||
cod: codigosResposta.sucesso,
|
||||
eCerto: true,
|
||||
|
|
@ -130,9 +225,10 @@ export default defineComponent({
|
|||
};
|
||||
|
||||
const tabelaErro: EliTabelaConsulta<Linha> = {
|
||||
registros_por_consulta: tabelaOk.registros_por_consulta,
|
||||
colunas: tabelaOk.colunas,
|
||||
acoes: acoesTabela,
|
||||
consulta: async () => {
|
||||
consulta: async (_parametrosConsulta) => {
|
||||
return {
|
||||
cod: codigosResposta.erroConhecido,
|
||||
eCerto: false,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue