build
This commit is contained in:
parent
8a5596e860
commit
57325f6744
100 changed files with 16153 additions and 4623 deletions
|
|
@ -147,6 +147,31 @@
|
|||
z-index: 200;
|
||||
}
|
||||
|
||||
.eli-tabela__rodape {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
margin-top: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.eli-tabela__paginacao {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
margin-top: 0;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.eli-tabela__acoes-inferiores {
|
||||
display: inline-flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.eli-tabela__cabecalho {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -73,11 +73,11 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from "vue";
|
||||
import { ChevronDown, ChevronRight, MoreVertical } from "lucide-vue-next";
|
||||
import EliTabelaCelula from "./celulas/EliTabelaCelula.vue";
|
||||
import EliTabelaDetalhesLinha from "./EliTabelaDetalhesLinha.vue";
|
||||
import type { tipoEliColuna } from "./types-eli-tabela";
|
||||
import { ChevronDown, ChevronRight, MoreVertical } from "lucide-vue-next"
|
||||
import { defineComponent, type PropType } from "vue"
|
||||
import EliTabelaCelula from "./celulas/EliTabelaCelula.vue"
|
||||
import EliTabelaDetalhesLinha from "./EliTabelaDetalhesLinha.vue"
|
||||
import type { tipoEliColuna } from "./types-eli-tabela"
|
||||
|
||||
export default defineComponent({
|
||||
name: "EliTabelaBody",
|
||||
|
|
@ -90,10 +90,12 @@ export default defineComponent({
|
|||
},
|
||||
props: {
|
||||
colunas: {
|
||||
// biome-ignore lint/suspicious/noExplicitAny: dynamic column
|
||||
type: Array as PropType<Array<tipoEliColuna<any>>>,
|
||||
required: true,
|
||||
},
|
||||
colunasInvisiveis: {
|
||||
// biome-ignore lint/suspicious/noExplicitAny: dynamic column
|
||||
type: Array as PropType<Array<tipoEliColuna<any>>>,
|
||||
required: true,
|
||||
},
|
||||
|
|
@ -134,7 +136,7 @@ export default defineComponent({
|
|||
return {
|
||||
ChevronRight,
|
||||
ChevronDown,
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@
|
|||
type="button"
|
||||
class="eli-tabela__acoes-cabecalho-botao"
|
||||
:style="botao.cor ? { backgroundColor: botao.cor, color: '#fff' } : undefined"
|
||||
@click="botao.acao"
|
||||
@click="botao.acao(parametrosConsulta)"
|
||||
>
|
||||
<component
|
||||
v-if="botao.icone"
|
||||
|
|
@ -46,8 +46,8 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, PropType } from "vue";
|
||||
import EliTabelaCaixaDeBusca from "./EliTabelaCaixaDeBusca.vue";
|
||||
import { computed, defineComponent, type PropType } from "vue"
|
||||
import EliTabelaCaixaDeBusca from "./EliTabelaCaixaDeBusca.vue"
|
||||
|
||||
export default defineComponent({
|
||||
name: "EliTabelaCabecalho",
|
||||
|
|
@ -71,13 +71,20 @@ export default defineComponent({
|
|||
type: String,
|
||||
required: true,
|
||||
},
|
||||
parametrosConsulta: {
|
||||
// biome-ignore lint/suspicious/noExplicitAny: dynamic params
|
||||
type: Object as PropType<any>,
|
||||
required: false,
|
||||
},
|
||||
acoesCabecalho: {
|
||||
type: Array as PropType<
|
||||
Array<{
|
||||
icone?: any;
|
||||
cor?: string;
|
||||
rotulo: string;
|
||||
acao: () => void;
|
||||
// biome-ignore lint/suspicious/noExplicitAny: dynamic icon
|
||||
icone?: any
|
||||
cor?: string
|
||||
rotulo: string
|
||||
// biome-ignore lint/suspicious/noExplicitAny: dynamic action
|
||||
acao: (params?: any) => void
|
||||
}>
|
||||
>,
|
||||
required: true,
|
||||
|
|
@ -85,33 +92,33 @@ export default defineComponent({
|
|||
},
|
||||
emits: {
|
||||
buscar(valor: string) {
|
||||
return typeof valor === "string";
|
||||
return typeof valor === "string"
|
||||
},
|
||||
colunas() {
|
||||
return true;
|
||||
return true
|
||||
},
|
||||
filtroAvancado() {
|
||||
return true;
|
||||
return true
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const temAcoesCabecalho = computed(() => props.acoesCabecalho.length > 0);
|
||||
const temAcoesCabecalho = computed(() => props.acoesCabecalho.length > 0)
|
||||
|
||||
function emitBuscar(texto: string) {
|
||||
emit("buscar", texto);
|
||||
emit("buscar", texto)
|
||||
}
|
||||
|
||||
function emitColunas() {
|
||||
emit("colunas");
|
||||
emit("colunas")
|
||||
}
|
||||
|
||||
function emitFiltroAvancado() {
|
||||
emit("filtroAvancado");
|
||||
emit("filtroAvancado")
|
||||
}
|
||||
|
||||
return { temAcoesCabecalho, emitBuscar, emitColunas, emitFiltroAvancado };
|
||||
return { temAcoesCabecalho, emitBuscar, emitColunas, emitFiltroAvancado }
|
||||
},
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
|
|
|||
|
|
@ -23,8 +23,8 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, watch } from "vue";
|
||||
import { Search } from "lucide-vue-next";
|
||||
import { Search } from "lucide-vue-next"
|
||||
import { defineComponent, ref, watch } from "vue"
|
||||
|
||||
export default defineComponent({
|
||||
name: "EliTabelaCaixaDeBusca",
|
||||
|
|
@ -38,7 +38,7 @@ export default defineComponent({
|
|||
},
|
||||
emits: {
|
||||
buscar(valor: string) {
|
||||
return typeof valor === "string";
|
||||
return typeof valor === "string"
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
|
|
@ -46,24 +46,24 @@ export default defineComponent({
|
|||
* Estado local da entrada para que o usuário possa digitar livremente antes
|
||||
* de disparar uma nova consulta.
|
||||
*/
|
||||
const texto = ref(props.modelo ?? "");
|
||||
const texto = ref(props.modelo ?? "")
|
||||
|
||||
watch(
|
||||
() => props.modelo,
|
||||
(novo) => {
|
||||
if (novo !== undefined && novo !== texto.value) {
|
||||
texto.value = novo;
|
||||
texto.value = novo
|
||||
}
|
||||
}
|
||||
);
|
||||
},
|
||||
)
|
||||
|
||||
function emitirBusca() {
|
||||
emit("buscar", texto.value.trim());
|
||||
emit("buscar", texto.value.trim())
|
||||
}
|
||||
|
||||
return { texto, emitirBusca };
|
||||
return { texto, emitirBusca }
|
||||
},
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from "vue";
|
||||
import { defineComponent, type PropType } from "vue"
|
||||
|
||||
export default defineComponent({
|
||||
name: "EliTabelaDebug",
|
||||
|
|
@ -30,5 +30,5 @@ export default defineComponent({
|
|||
required: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -10,9 +10,9 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from "vue";
|
||||
import EliTabelaCelula from "./celulas/EliTabelaCelula.vue";
|
||||
import type { tipoEliColuna } from "./types-eli-tabela";
|
||||
import { defineComponent, type PropType } from "vue"
|
||||
import EliTabelaCelula from "./celulas/EliTabelaCelula.vue"
|
||||
import type { tipoEliColuna } from "./types-eli-tabela"
|
||||
|
||||
export default defineComponent({
|
||||
name: "EliTabelaDetalhesLinha",
|
||||
|
|
@ -23,11 +23,12 @@ export default defineComponent({
|
|||
required: true,
|
||||
},
|
||||
colunasInvisiveis: {
|
||||
// biome-ignore lint/suspicious/noExplicitAny: dynamic column
|
||||
type: Array as PropType<Array<tipoEliColuna<any>>>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from "vue";
|
||||
import { defineComponent, type PropType } from "vue"
|
||||
|
||||
export default defineComponent({
|
||||
name: "EliTabelaEstados",
|
||||
|
|
@ -36,5 +36,5 @@ export default defineComponent({
|
|||
default: undefined,
|
||||
},
|
||||
},
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -49,15 +49,16 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from "vue";
|
||||
import { ArrowDown, ArrowUp } from "lucide-vue-next";
|
||||
import type { tipoEliColuna } from "./types-eli-tabela";
|
||||
import { ArrowDown, ArrowUp } from "lucide-vue-next"
|
||||
import { defineComponent, type PropType } from "vue"
|
||||
import type { tipoEliColuna } from "./types-eli-tabela"
|
||||
|
||||
export default defineComponent({
|
||||
name: "EliTabelaHead",
|
||||
components: { ArrowUp, ArrowDown },
|
||||
props: {
|
||||
colunas: {
|
||||
// biome-ignore lint/suspicious/noExplicitAny: dynamic column
|
||||
type: Array as PropType<Array<tipoEliColuna<any>>>,
|
||||
required: true,
|
||||
},
|
||||
|
|
@ -80,16 +81,17 @@ export default defineComponent({
|
|||
},
|
||||
emits: {
|
||||
alternarOrdenacao(chave: string) {
|
||||
return typeof chave === "string" && chave.length > 0;
|
||||
return typeof chave === "string" && chave.length > 0
|
||||
},
|
||||
},
|
||||
setup(_props, { emit }) {
|
||||
// biome-ignore lint/suspicious/noExplicitAny: dynamic column
|
||||
function isOrdenavel(coluna: any) {
|
||||
return coluna?.coluna_ordem !== undefined && coluna?.coluna_ordem !== null;
|
||||
return coluna?.coluna_ordem !== undefined && coluna?.coluna_ordem !== null
|
||||
}
|
||||
|
||||
function emitAlternarOrdenacao(chave: string) {
|
||||
emit("alternarOrdenacao", chave);
|
||||
emit("alternarOrdenacao", chave)
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
@ -97,7 +99,7 @@ export default defineComponent({
|
|||
ArrowDown,
|
||||
isOrdenavel,
|
||||
emitAlternarOrdenacao,
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -43,14 +43,14 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, PropType, ref } from "vue";
|
||||
import type { tipoEliTabelaAcao } from "./types-eli-tabela";
|
||||
import { computed, defineComponent, type PropType, ref } from "vue"
|
||||
import type { tipoEliTabelaAcao } from "./types-eli-tabela"
|
||||
|
||||
type ItemAcao<T> = {
|
||||
acao: tipoEliTabelaAcao<T>;
|
||||
indice: number;
|
||||
visivel: boolean;
|
||||
};
|
||||
acao: tipoEliTabelaAcao<T>
|
||||
indice: number
|
||||
visivel: boolean
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: "EliTabelaMenuAcoes",
|
||||
|
|
@ -64,6 +64,7 @@ export default defineComponent({
|
|||
required: true,
|
||||
},
|
||||
acoes: {
|
||||
// biome-ignore lint/suspicious/noExplicitAny: dynamic typing needed
|
||||
type: Array as PropType<Array<ItemAcao<any>>>,
|
||||
required: true,
|
||||
},
|
||||
|
|
@ -74,22 +75,24 @@ export default defineComponent({
|
|||
},
|
||||
},
|
||||
emits: {
|
||||
// biome-ignore lint/suspicious/noExplicitAny: dynamic typing needed
|
||||
executar(payload: { acao: tipoEliTabelaAcao<any>; linha: unknown }) {
|
||||
return payload !== null && typeof payload === "object";
|
||||
return payload !== null && typeof payload === "object"
|
||||
},
|
||||
},
|
||||
setup(props, { emit, expose }) {
|
||||
const menuEl = ref<HTMLElement | null>(null);
|
||||
expose({ menuEl });
|
||||
const menuEl = ref<HTMLElement | null>(null)
|
||||
expose({ menuEl })
|
||||
|
||||
const possuiAcoes = computed(() => props.acoes.length > 0);
|
||||
const possuiAcoes = computed(() => props.acoes.length > 0)
|
||||
|
||||
// biome-ignore lint/suspicious/noExplicitAny: dynamic typing needed
|
||||
function emitExecutar(item: { acao: tipoEliTabelaAcao<any> }) {
|
||||
if (!props.linha) return;
|
||||
emit("executar", { acao: item.acao, linha: props.linha });
|
||||
if (!props.linha) return
|
||||
emit("executar", { acao: item.acao, linha: props.linha })
|
||||
}
|
||||
|
||||
return { menuEl, possuiAcoes, emitExecutar };
|
||||
return { menuEl, possuiAcoes, emitExecutar }
|
||||
},
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -72,19 +72,19 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType, ref, watch } from "vue";
|
||||
import type { EliTabelaColunasConfig } from "./colunasStorage";
|
||||
import type { tipoEliColuna } from "./types-eli-tabela";
|
||||
import { defineComponent, type PropType, ref, watch } from "vue"
|
||||
import type { EliTabelaColunasConfig } from "./colunasStorage"
|
||||
import type { tipoEliColuna } from "./types-eli-tabela"
|
||||
|
||||
type OrigemLista = "visiveis" | "invisiveis";
|
||||
type OrigemLista = "visiveis" | "invisiveis"
|
||||
|
||||
type DragPayload = {
|
||||
rotulo: string;
|
||||
origem: OrigemLista;
|
||||
index: number;
|
||||
};
|
||||
rotulo: string
|
||||
origem: OrigemLista
|
||||
index: number
|
||||
}
|
||||
|
||||
const DRAG_MIME = "application/x-eli-tabela-coluna";
|
||||
const DRAG_MIME = "application/x-eli-tabela-coluna"
|
||||
|
||||
export default defineComponent({
|
||||
name: "EliTabelaModalColunas",
|
||||
|
|
@ -102,80 +102,89 @@ export default defineComponent({
|
|||
required: true,
|
||||
},
|
||||
colunas: {
|
||||
// biome-ignore lint/suspicious/noExplicitAny: dynamic typing needed
|
||||
type: Array as PropType<Array<tipoEliColuna<any>>>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
emits: {
|
||||
fechar() {
|
||||
return true;
|
||||
return true
|
||||
},
|
||||
salvar(_config: EliTabelaColunasConfig) {
|
||||
return true;
|
||||
return true
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const visiveisLocal = ref<string[]>([]);
|
||||
const invisiveisLocal = ref<string[]>([]);
|
||||
const visiveisLocal = ref<string[]>([])
|
||||
const invisiveisLocal = ref<string[]>([])
|
||||
|
||||
function sincronizarEstado() {
|
||||
const todos = props.rotulosColunas;
|
||||
const todos = props.rotulosColunas
|
||||
const configTemDados =
|
||||
(props.configInicial.visiveis?.length ?? 0) > 0 ||
|
||||
(props.configInicial.invisiveis?.length ?? 0) > 0;
|
||||
(props.configInicial.invisiveis?.length ?? 0) > 0
|
||||
|
||||
const invisiveisPadraoSet = new Set(
|
||||
props.colunas.filter((c) => c.visivel === false).map((c) => c.rotulo)
|
||||
);
|
||||
props.colunas.filter((c) => c.visivel === false).map((c) => c.rotulo),
|
||||
)
|
||||
|
||||
const invisiveisSet = configTemDados
|
||||
? new Set(props.configInicial.invisiveis ?? [])
|
||||
: invisiveisPadraoSet;
|
||||
: invisiveisPadraoSet
|
||||
|
||||
const baseVisiveis = todos.filter((r) => !invisiveisSet.has(r));
|
||||
const baseVisiveis = todos.filter((r) => !invisiveisSet.has(r))
|
||||
|
||||
// ordenação: aplica ordem salva (visiveis) e adiciona novas ao final
|
||||
const ordemSalva = props.configInicial.visiveis ?? [];
|
||||
const setVis = new Set(baseVisiveis);
|
||||
const ordenadas: string[] = [];
|
||||
const ordemSalva = props.configInicial.visiveis ?? []
|
||||
const setVis = new Set(baseVisiveis)
|
||||
const ordenadas: string[] = []
|
||||
|
||||
for (const r of ordemSalva) {
|
||||
if (setVis.has(r)) ordenadas.push(r);
|
||||
if (setVis.has(r)) ordenadas.push(r)
|
||||
}
|
||||
for (const r of baseVisiveis) {
|
||||
if (!ordenadas.includes(r)) ordenadas.push(r);
|
||||
if (!ordenadas.includes(r)) ordenadas.push(r)
|
||||
}
|
||||
|
||||
visiveisLocal.value = ordenadas;
|
||||
visiveisLocal.value = ordenadas
|
||||
|
||||
// invisíveis: somente as que existem na tabela
|
||||
invisiveisLocal.value = todos.filter((r) => invisiveisSet.has(r));
|
||||
invisiveisLocal.value = todos.filter((r) => invisiveisSet.has(r))
|
||||
}
|
||||
|
||||
watch(
|
||||
() => [props.aberto, props.rotulosColunas, props.configInicial, props.colunas] as const,
|
||||
() =>
|
||||
[
|
||||
props.aberto,
|
||||
props.rotulosColunas,
|
||||
props.configInicial,
|
||||
props.colunas,
|
||||
] as const,
|
||||
() => {
|
||||
if (props.aberto) sincronizarEstado();
|
||||
if (props.aberto) sincronizarEstado()
|
||||
},
|
||||
{ deep: true, immediate: true }
|
||||
);
|
||||
{ deep: true, immediate: true },
|
||||
)
|
||||
|
||||
function emitFechar() {
|
||||
emit("fechar");
|
||||
emit("fechar")
|
||||
}
|
||||
|
||||
function emitSalvar() {
|
||||
emit("salvar", {
|
||||
visiveis: [...visiveisLocal.value],
|
||||
invisiveis: [...invisiveisLocal.value],
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
function writeDragData(e: DragEvent, payload: DragPayload) {
|
||||
try {
|
||||
e.dataTransfer?.setData(DRAG_MIME, JSON.stringify(payload));
|
||||
e.dataTransfer?.setData("text/plain", payload.rotulo);
|
||||
e.dataTransfer!.effectAllowed = "move";
|
||||
e.dataTransfer?.setData(DRAG_MIME, JSON.stringify(payload))
|
||||
e.dataTransfer?.setData("text/plain", payload.rotulo)
|
||||
if (e.dataTransfer) {
|
||||
e.dataTransfer.effectAllowed = "move"
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
|
|
@ -183,72 +192,93 @@ export default defineComponent({
|
|||
|
||||
function readDragData(e: DragEvent): DragPayload | null {
|
||||
try {
|
||||
const raw = e.dataTransfer?.getData(DRAG_MIME);
|
||||
if (!raw) return null;
|
||||
const parsed = JSON.parse(raw);
|
||||
if (!parsed || typeof parsed.rotulo !== "string" || (parsed.origem !== "visiveis" && parsed.origem !== "invisiveis")) {
|
||||
return null;
|
||||
const raw = e.dataTransfer?.getData(DRAG_MIME)
|
||||
if (!raw) return null
|
||||
const parsed = JSON.parse(raw)
|
||||
if (
|
||||
!parsed ||
|
||||
typeof parsed.rotulo !== "string" ||
|
||||
(parsed.origem !== "visiveis" && parsed.origem !== "invisiveis")
|
||||
) {
|
||||
return null
|
||||
}
|
||||
return parsed as DragPayload;
|
||||
return parsed as DragPayload
|
||||
} catch {
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function removerDaOrigem(payload: DragPayload) {
|
||||
const origemArr = payload.origem === "visiveis" ? visiveisLocal.value : invisiveisLocal.value;
|
||||
const idx = origemArr.indexOf(payload.rotulo);
|
||||
if (idx >= 0) origemArr.splice(idx, 1);
|
||||
const origemArr =
|
||||
payload.origem === "visiveis"
|
||||
? visiveisLocal.value
|
||||
: invisiveisLocal.value
|
||||
const idx = origemArr.indexOf(payload.rotulo)
|
||||
if (idx >= 0) origemArr.splice(idx, 1)
|
||||
}
|
||||
|
||||
function inserirNoDestino(destino: OrigemLista, rotulo: string, index: number | null) {
|
||||
const arr = destino === "visiveis" ? visiveisLocal.value : invisiveisLocal.value;
|
||||
function inserirNoDestino(
|
||||
destino: OrigemLista,
|
||||
rotulo: string,
|
||||
index: number | null,
|
||||
) {
|
||||
const arr =
|
||||
destino === "visiveis" ? visiveisLocal.value : invisiveisLocal.value
|
||||
// remove duplicatas
|
||||
const existing = arr.indexOf(rotulo);
|
||||
if (existing >= 0) arr.splice(existing, 1);
|
||||
const existing = arr.indexOf(rotulo)
|
||||
if (existing >= 0) arr.splice(existing, 1)
|
||||
|
||||
if (index === null || index < 0 || index > arr.length) {
|
||||
arr.push(rotulo);
|
||||
arr.push(rotulo)
|
||||
} else {
|
||||
arr.splice(index, 0, rotulo);
|
||||
arr.splice(index, 0, rotulo)
|
||||
}
|
||||
}
|
||||
|
||||
function onDragStart(e: DragEvent, rotulo: string, origem: OrigemLista, index: number) {
|
||||
writeDragData(e, { rotulo, origem, index });
|
||||
function onDragStart(
|
||||
e: DragEvent,
|
||||
rotulo: string,
|
||||
origem: OrigemLista,
|
||||
index: number,
|
||||
) {
|
||||
writeDragData(e, { rotulo, origem, index })
|
||||
}
|
||||
|
||||
function onDropItem(e: DragEvent, destino: OrigemLista, index: number) {
|
||||
const payload = readDragData(e);
|
||||
if (!payload) return;
|
||||
const payload = readDragData(e)
|
||||
if (!payload) return
|
||||
|
||||
// remove da origem e insere no destino na posição do item
|
||||
removerDaOrigem(payload);
|
||||
inserirNoDestino(destino, payload.rotulo, index);
|
||||
removerDaOrigem(payload)
|
||||
inserirNoDestino(destino, payload.rotulo, index)
|
||||
|
||||
// garante que uma coluna não fique nos 2 lados
|
||||
if (destino === "visiveis") {
|
||||
const idxInv = invisiveisLocal.value.indexOf(payload.rotulo);
|
||||
if (idxInv >= 0) invisiveisLocal.value.splice(idxInv, 1);
|
||||
const idxInv = invisiveisLocal.value.indexOf(payload.rotulo)
|
||||
if (idxInv >= 0) invisiveisLocal.value.splice(idxInv, 1)
|
||||
} else {
|
||||
const idxVis = visiveisLocal.value.indexOf(payload.rotulo);
|
||||
if (idxVis >= 0) visiveisLocal.value.splice(idxVis, 1);
|
||||
const idxVis = visiveisLocal.value.indexOf(payload.rotulo)
|
||||
if (idxVis >= 0) visiveisLocal.value.splice(idxVis, 1)
|
||||
}
|
||||
}
|
||||
|
||||
function onDropLista(e: DragEvent, destino: OrigemLista, _index: number | null) {
|
||||
const payload = readDragData(e);
|
||||
if (!payload) return;
|
||||
function onDropLista(
|
||||
e: DragEvent,
|
||||
destino: OrigemLista,
|
||||
_index: number | null,
|
||||
) {
|
||||
const payload = readDragData(e)
|
||||
if (!payload) return
|
||||
|
||||
removerDaOrigem(payload);
|
||||
inserirNoDestino(destino, payload.rotulo, null);
|
||||
removerDaOrigem(payload)
|
||||
inserirNoDestino(destino, payload.rotulo, null)
|
||||
|
||||
if (destino === "visiveis") {
|
||||
const idxInv = invisiveisLocal.value.indexOf(payload.rotulo);
|
||||
if (idxInv >= 0) invisiveisLocal.value.splice(idxInv, 1);
|
||||
const idxInv = invisiveisLocal.value.indexOf(payload.rotulo)
|
||||
if (idxInv >= 0) invisiveisLocal.value.splice(idxInv, 1)
|
||||
} else {
|
||||
const idxVis = visiveisLocal.value.indexOf(payload.rotulo);
|
||||
if (idxVis >= 0) visiveisLocal.value.splice(idxVis, 1);
|
||||
const idxVis = visiveisLocal.value.indexOf(payload.rotulo)
|
||||
if (idxVis >= 0) visiveisLocal.value.splice(idxVis, 1)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -260,9 +290,9 @@ export default defineComponent({
|
|||
onDragStart,
|
||||
onDropItem,
|
||||
onDropLista,
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
|
|
|||
|
|
@ -74,27 +74,39 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, PropType, ref, watch } from "vue";
|
||||
import { EliEntradaTexto, EliEntradaNumero, EliEntradaDataHora } from "../EliEntrada";
|
||||
import type { ComponenteEntrada, TipoEntrada } from "../EliEntrada/tiposEntradas";
|
||||
import type { tipoEliTabelaConsulta } from "./types-eli-tabela";
|
||||
import { computed, defineComponent, type PropType, ref, watch } from "vue"
|
||||
import {
|
||||
EliEntradaDataHora,
|
||||
EliEntradaNumero,
|
||||
EliEntradaTexto,
|
||||
} from "../EliEntrada"
|
||||
import type {
|
||||
ComponenteEntrada,
|
||||
TipoEntrada,
|
||||
} from "../EliEntrada/tiposEntradas"
|
||||
import type { tipoEliTabelaConsulta } from "./types-eli-tabela"
|
||||
|
||||
type FiltroBase<T> = NonNullable<tipoEliTabelaConsulta<T>["filtroAvancado"]>[number];
|
||||
type FiltroBase<T> = NonNullable<
|
||||
tipoEliTabelaConsulta<T>["filtroAvancado"]
|
||||
>[number]
|
||||
|
||||
type LinhaFiltro<T> = {
|
||||
coluna: keyof T;
|
||||
entrada: ComponenteEntrada;
|
||||
operador: string;
|
||||
valor: any;
|
||||
};
|
||||
|
||||
function isTipoEntrada(v: unknown): v is TipoEntrada {
|
||||
return v === "texto" || v === "numero" || v === "dataHora";
|
||||
coluna: keyof T
|
||||
entrada: ComponenteEntrada
|
||||
operador: string
|
||||
// biome-ignore lint/suspicious/noExplicitAny: dynamic value
|
||||
valor: any
|
||||
}
|
||||
|
||||
function isTipoEntrada(v: unknown): v is TipoEntrada {
|
||||
return v === "texto" || v === "numero" || v === "dataHora"
|
||||
}
|
||||
|
||||
// biome-ignore lint/suspicious/noExplicitAny: dynamic typing needed
|
||||
function rotuloDoFiltro(f: FiltroBase<any>) {
|
||||
const rotulo = (f?.entrada?.[1] as any)?.rotulo;
|
||||
return rotulo ? String(rotulo) : String(f?.coluna ?? "Filtro");
|
||||
// biome-ignore lint/suspicious/noExplicitAny: dynamic access
|
||||
const rotulo = (f?.entrada?.[1] as any)?.rotulo
|
||||
return rotulo ? String(rotulo) : String(f?.coluna ?? "Filtro")
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
|
|
@ -102,10 +114,12 @@ export default defineComponent({
|
|||
props: {
|
||||
aberto: { type: Boolean, required: true },
|
||||
filtrosBase: {
|
||||
// biome-ignore lint/suspicious/noExplicitAny: dynamic filter type
|
||||
type: Array as PropType<Array<FiltroBase<any>>>,
|
||||
required: true,
|
||||
},
|
||||
modelo: {
|
||||
// biome-ignore lint/suspicious/noExplicitAny: dynamic model
|
||||
type: Array as PropType<Array<any>>,
|
||||
required: true,
|
||||
},
|
||||
|
|
@ -113,67 +127,82 @@ export default defineComponent({
|
|||
emits: {
|
||||
fechar: () => true,
|
||||
limpar: () => true,
|
||||
// biome-ignore lint/suspicious/noExplicitAny: dynamic lines
|
||||
salvar: (_linhas: any[]) => true,
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const linhas = ref<Array<LinhaFiltro<any>>>([]);
|
||||
// biome-ignore lint/suspicious/noExplicitAny: dynamic value
|
||||
const linhas = ref<Array<LinhaFiltro<any>>>([])
|
||||
|
||||
const colunaParaAdicionar = ref<string>("");
|
||||
const colunaParaAdicionar = ref<string>("")
|
||||
|
||||
const colunasDisponiveis = computed(() => (props.filtrosBase ?? []).map((b) => String(b.coluna)));
|
||||
const colunasDisponiveis = computed(() =>
|
||||
(props.filtrosBase ?? []).map((b) => String(b.coluna)),
|
||||
)
|
||||
|
||||
const opcoesParaAdicionar = computed(() => {
|
||||
const usadas = new Set(linhas.value.map((l) => String(l.coluna)));
|
||||
return (props.filtrosBase ?? []).filter((b) => !usadas.has(String(b.coluna)));
|
||||
});
|
||||
const usadas = new Set(linhas.value.map((l) => String(l.coluna)))
|
||||
return (props.filtrosBase ?? []).filter(
|
||||
(b) => !usadas.has(String(b.coluna)),
|
||||
)
|
||||
})
|
||||
|
||||
function componenteEntrada(entrada: ComponenteEntrada) {
|
||||
const tipo = entrada?.[0];
|
||||
if (tipo === "numero") return EliEntradaNumero;
|
||||
if (tipo === "dataHora") return EliEntradaDataHora;
|
||||
return EliEntradaTexto;
|
||||
const tipo = entrada?.[0]
|
||||
if (tipo === "numero") return EliEntradaNumero
|
||||
if (tipo === "dataHora") return EliEntradaDataHora
|
||||
return EliEntradaTexto
|
||||
}
|
||||
|
||||
function opcoesEntrada(entrada: ComponenteEntrada) {
|
||||
// o rótulo vem do próprio componente (entrada[1].rotulo)
|
||||
return (entrada?.[1] as any) ?? { rotulo: "" };
|
||||
// biome-ignore lint/suspicious/noExplicitAny: dynamic options
|
||||
return (entrada?.[1] as any) ?? { rotulo: "" }
|
||||
}
|
||||
|
||||
function valorInicialPorEntrada(entrada: ComponenteEntrada) {
|
||||
const tipo = entrada?.[0];
|
||||
if (tipo === "numero") return null;
|
||||
return "";
|
||||
const tipo = entrada?.[0]
|
||||
if (tipo === "numero") return null
|
||||
return ""
|
||||
}
|
||||
|
||||
function normalizarModelo() {
|
||||
const base = props.filtrosBase ?? [];
|
||||
const modelo = Array.isArray(props.modelo) ? props.modelo : [];
|
||||
const base = props.filtrosBase ?? []
|
||||
const modelo = Array.isArray(props.modelo) ? props.modelo : []
|
||||
// biome-ignore lint/suspicious/noExplicitAny: dynamic model
|
||||
linhas.value = modelo.map((m: any) => {
|
||||
// operador vem travado no base
|
||||
const baseItem = base.find((b) => String(b.coluna) === String(m.coluna)) ?? base[0];
|
||||
const entrada = (baseItem?.entrada ?? m.entrada) as ComponenteEntrada;
|
||||
const col = (baseItem?.coluna ?? m.coluna) as any;
|
||||
const op = String(baseItem?.operador ?? "=");
|
||||
const val = m.valor ?? valorInicialPorEntrada(entrada);
|
||||
const baseItem =
|
||||
base.find((b) => String(b.coluna) === String(m.coluna)) ?? base[0]
|
||||
const entrada = (baseItem?.entrada ?? m.entrada) as ComponenteEntrada
|
||||
// biome-ignore lint/suspicious/noExplicitAny: dynamic column
|
||||
const col = (baseItem?.coluna ?? m.coluna) as any
|
||||
const op = String(baseItem?.operador ?? "=")
|
||||
const val = m.valor ?? valorInicialPorEntrada(entrada)
|
||||
|
||||
return {
|
||||
coluna: col,
|
||||
operador: op,
|
||||
entrada,
|
||||
valor: val,
|
||||
} as LinhaFiltro<any>;
|
||||
});
|
||||
// biome-ignore lint/suspicious/noExplicitAny: dynamic cast
|
||||
} as LinhaFiltro<any>
|
||||
})
|
||||
|
||||
// se vazio e existe base, adiciona 1 linha default
|
||||
// não auto-adiciona; usuário escolhe quais filtros quer usar
|
||||
|
||||
// se algum filtro mudou a coluna para valor inválido, ajusta
|
||||
for (const l of linhas.value) {
|
||||
if (!colunasDisponiveis.value.includes(String(l.coluna))) continue;
|
||||
l.operador = String((base.find((b) => String(b.coluna) === String(l.coluna))?.operador ?? "="));
|
||||
if (!colunasDisponiveis.value.includes(String(l.coluna))) continue
|
||||
l.operador = String(
|
||||
base.find((b) => String(b.coluna) === String(l.coluna))?.operador ??
|
||||
"=",
|
||||
)
|
||||
// sanity
|
||||
if (l.entrada && !isTipoEntrada(l.entrada[0])) {
|
||||
l.entrada = ["texto", { rotulo: "Valor" }] as any;
|
||||
// biome-ignore lint/suspicious/noExplicitAny: dynamic sanity
|
||||
l.entrada = ["texto", { rotulo: "Valor" }] as any
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -181,38 +210,42 @@ export default defineComponent({
|
|||
watch(
|
||||
() => [props.aberto, props.filtrosBase, props.modelo] as const,
|
||||
() => {
|
||||
if (props.aberto) normalizarModelo();
|
||||
if (props.aberto) normalizarModelo()
|
||||
},
|
||||
{ deep: true, immediate: true }
|
||||
);
|
||||
{ deep: true, immediate: true },
|
||||
)
|
||||
|
||||
function adicionar() {
|
||||
if (!colunaParaAdicionar.value) return;
|
||||
const b0 = (props.filtrosBase ?? []).find((b) => String(b.coluna) === String(colunaParaAdicionar.value));
|
||||
if (!b0) return;
|
||||
if (!colunaParaAdicionar.value) return
|
||||
const b0 = (props.filtrosBase ?? []).find(
|
||||
(b) => String(b.coluna) === String(colunaParaAdicionar.value),
|
||||
)
|
||||
if (!b0) return
|
||||
// evita repetição
|
||||
if (linhas.value.some((l) => String(l.coluna) === String(b0.coluna))) return;
|
||||
if (linhas.value.some((l) => String(l.coluna) === String(b0.coluna)))
|
||||
return
|
||||
|
||||
linhas.value.push({
|
||||
// biome-ignore lint/suspicious/noExplicitAny: dynamic column
|
||||
coluna: b0.coluna as any,
|
||||
entrada: b0.entrada,
|
||||
operador: String(b0.operador ?? "="),
|
||||
valor: valorInicialPorEntrada(b0.entrada),
|
||||
});
|
||||
})
|
||||
|
||||
colunaParaAdicionar.value = "";
|
||||
colunaParaAdicionar.value = ""
|
||||
}
|
||||
|
||||
function remover(idx: number) {
|
||||
linhas.value.splice(idx, 1);
|
||||
linhas.value.splice(idx, 1)
|
||||
}
|
||||
|
||||
function emitFechar() {
|
||||
emit("fechar");
|
||||
emit("fechar")
|
||||
}
|
||||
|
||||
function emitLimpar() {
|
||||
emit("limpar");
|
||||
emit("limpar")
|
||||
}
|
||||
|
||||
function emitSalvar() {
|
||||
|
|
@ -221,8 +254,8 @@ export default defineComponent({
|
|||
linhas.value.map((l) => ({
|
||||
coluna: l.coluna,
|
||||
valor: l.valor,
|
||||
}))
|
||||
);
|
||||
})),
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
@ -238,9 +271,9 @@ export default defineComponent({
|
|||
emitSalvar,
|
||||
emitLimpar,
|
||||
rotuloDoFiltro,
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
|
|
|||
|
|
@ -1,56 +1,82 @@
|
|||
<template>
|
||||
<nav
|
||||
v-if="totalPaginasExibidas > 1"
|
||||
class="eli-tabela__paginacao"
|
||||
role="navigation"
|
||||
aria-label="Paginação de resultados"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="eli-tabela__pagina-botao"
|
||||
:disabled="anteriorDesabilitado"
|
||||
aria-label="Página anterior"
|
||||
@click="irParaPagina(paginaAtual - 1)"
|
||||
<div class="eli-tabela__rodape">
|
||||
<div
|
||||
v-if="acoes.length > 0"
|
||||
class="eli-tabela__acoes-inferiores"
|
||||
style="margin-right: auto"
|
||||
>
|
||||
<button
|
||||
v-for="(botao, indice) in acoes"
|
||||
:key="`${botao.rotulo}-${indice}`"
|
||||
type="button"
|
||||
class="eli-tabela__acao-inferior"
|
||||
:style="botao.cor ? { borderColor: botao.cor, color: botao.cor } : undefined"
|
||||
@click="botao.acao(parametrosConsulta)"
|
||||
>
|
||||
<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>
|
||||
|
||||
<nav
|
||||
v-if="totalPaginasExibidas > 1"
|
||||
class="eli-tabela__paginacao"
|
||||
role="navigation"
|
||||
aria-label="Paginação de resultados"
|
||||
>
|
||||
<<
|
||||
</button>
|
||||
|
||||
<template v-for="(item, index) in botoes" :key="`${item.label}-${index}`">
|
||||
<span
|
||||
v-if="item.ehEllipsis"
|
||||
class="eli-tabela__pagina-ellipsis"
|
||||
aria-hidden="true"
|
||||
>
|
||||
{{ item.label }}
|
||||
</span>
|
||||
<button
|
||||
v-else
|
||||
type="button"
|
||||
class="eli-tabela__pagina-botao"
|
||||
:class="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}`"
|
||||
@click="irParaPagina(item.pagina)"
|
||||
:disabled="anteriorDesabilitado"
|
||||
aria-label="Página anterior"
|
||||
@click="irParaPagina(paginaAtual - 1)"
|
||||
>
|
||||
{{ item.label }}
|
||||
<<
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="eli-tabela__pagina-botao"
|
||||
:disabled="proximaDesabilitada"
|
||||
aria-label="Próxima página"
|
||||
@click="irParaPagina(paginaAtual + 1)"
|
||||
>
|
||||
>>
|
||||
</button>
|
||||
</nav>
|
||||
<template v-for="(item, index) in botoes" :key="`${item.label}-${index}`">
|
||||
<span
|
||||
v-if="item.ehEllipsis"
|
||||
class="eli-tabela__pagina-ellipsis"
|
||||
aria-hidden="true"
|
||||
>
|
||||
{{ item.label }}
|
||||
</span>
|
||||
<button
|
||||
v-else
|
||||
type="button"
|
||||
class="eli-tabela__pagina-botao"
|
||||
:class="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}`"
|
||||
@click="irParaPagina(item.pagina)"
|
||||
>
|
||||
{{ item.label }}
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="eli-tabela__pagina-botao"
|
||||
:disabled="proximaDesabilitada"
|
||||
aria-label="Próxima página"
|
||||
@click="irParaPagina(paginaAtual + 1)"
|
||||
>
|
||||
>>
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent } from "vue";
|
||||
import { computed, defineComponent, type PropType } from "vue"
|
||||
|
||||
export default defineComponent({
|
||||
name: "EliTabelaPaginacao",
|
||||
|
|
@ -67,10 +93,29 @@ export default defineComponent({
|
|||
type: Number,
|
||||
required: false,
|
||||
},
|
||||
acoes: {
|
||||
type: Array as PropType<
|
||||
Array<{
|
||||
// biome-ignore lint/suspicious/noExplicitAny: dynamic icon
|
||||
icone?: any
|
||||
cor?: string
|
||||
rotulo: string
|
||||
// biome-ignore lint/suspicious/noExplicitAny: dynamic action
|
||||
acao: (params?: any) => void
|
||||
}>
|
||||
>,
|
||||
required: false,
|
||||
default: () => [],
|
||||
},
|
||||
parametrosConsulta: {
|
||||
// biome-ignore lint/suspicious/noExplicitAny: dynamic params
|
||||
type: Object as PropType<any>,
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
emits: {
|
||||
alterar(pagina: number) {
|
||||
return Number.isFinite(pagina);
|
||||
return Number.isFinite(pagina)
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
|
|
@ -79,12 +124,12 @@ export default defineComponent({
|
|||
* uma navegação confortável, mesmo quando o consumidor não informa o valor.
|
||||
*/
|
||||
const maximoBotoesVisiveis = computed(() => {
|
||||
const valor = props.maximoBotoes;
|
||||
const valor = props.maximoBotoes
|
||||
if (typeof valor === "number" && valor >= 5) {
|
||||
return Math.floor(valor);
|
||||
return Math.floor(valor)
|
||||
}
|
||||
return 7;
|
||||
});
|
||||
return 7
|
||||
})
|
||||
|
||||
/**
|
||||
* Constrói a lista de botões/reticências que serão exibidos na paginação.
|
||||
|
|
@ -92,62 +137,62 @@ export default defineComponent({
|
|||
* demais de forma dinâmica ao redor da página atual.
|
||||
*/
|
||||
const botoes = computed(() => {
|
||||
const total = props.totalPaginas;
|
||||
const atual = props.pagina;
|
||||
const limite = maximoBotoesVisiveis.value;
|
||||
const total = props.totalPaginas
|
||||
const atual = props.pagina
|
||||
const limite = maximoBotoesVisiveis.value
|
||||
const resultado: Array<{
|
||||
label: string;
|
||||
pagina?: number;
|
||||
ativo?: boolean;
|
||||
ehEllipsis?: boolean;
|
||||
}> = [];
|
||||
label: string
|
||||
pagina?: number
|
||||
ativo?: boolean
|
||||
ehEllipsis?: boolean
|
||||
}> = []
|
||||
|
||||
const adicionarPagina = (pagina: number) => {
|
||||
resultado.push({
|
||||
label: String(pagina),
|
||||
pagina,
|
||||
ativo: pagina === atual,
|
||||
});
|
||||
};
|
||||
})
|
||||
}
|
||||
|
||||
const adicionarReticencias = () => {
|
||||
resultado.push({ label: "…", ehEllipsis: true });
|
||||
};
|
||||
resultado.push({ label: "…", ehEllipsis: true })
|
||||
}
|
||||
|
||||
if (total <= limite) {
|
||||
for (let pagina = 1; pagina <= total; pagina += 1) {
|
||||
adicionarPagina(pagina);
|
||||
adicionarPagina(pagina)
|
||||
}
|
||||
return resultado;
|
||||
return resultado
|
||||
}
|
||||
|
||||
const visiveisCentrais = Math.max(3, limite - 2);
|
||||
let inicio = Math.max(2, atual - Math.floor(visiveisCentrais / 2));
|
||||
let fim = inicio + visiveisCentrais - 1;
|
||||
const visiveisCentrais = Math.max(3, limite - 2)
|
||||
let inicio = Math.max(2, atual - Math.floor(visiveisCentrais / 2))
|
||||
let fim = inicio + visiveisCentrais - 1
|
||||
|
||||
if (fim >= total) {
|
||||
fim = total - 1;
|
||||
inicio = fim - visiveisCentrais + 1;
|
||||
fim = total - 1
|
||||
inicio = fim - visiveisCentrais + 1
|
||||
}
|
||||
|
||||
adicionarPagina(1);
|
||||
adicionarPagina(1)
|
||||
|
||||
if (inicio > 2) {
|
||||
adicionarReticencias();
|
||||
adicionarReticencias()
|
||||
}
|
||||
|
||||
for (let pagina = inicio; pagina <= fim; pagina += 1) {
|
||||
adicionarPagina(pagina);
|
||||
adicionarPagina(pagina)
|
||||
}
|
||||
|
||||
if (fim < total - 1) {
|
||||
adicionarReticencias();
|
||||
adicionarReticencias()
|
||||
}
|
||||
|
||||
adicionarPagina(total);
|
||||
adicionarPagina(total)
|
||||
|
||||
return resultado;
|
||||
});
|
||||
return resultado
|
||||
})
|
||||
|
||||
/**
|
||||
* Emite a requisição de mudança de página garantindo que o valor esteja
|
||||
|
|
@ -155,18 +200,20 @@ export default defineComponent({
|
|||
*/
|
||||
function irParaPagina(pagina: number | undefined) {
|
||||
if (!pagina) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
const alvo = Math.min(Math.max(1, pagina), props.totalPaginas);
|
||||
const alvo = Math.min(Math.max(1, pagina), props.totalPaginas)
|
||||
if (alvo !== props.pagina) {
|
||||
emit("alterar", alvo);
|
||||
emit("alterar", alvo)
|
||||
}
|
||||
}
|
||||
|
||||
const anteriorDesabilitado = computed(() => props.pagina <= 1);
|
||||
const proximaDesabilitada = computed(() => props.pagina >= props.totalPaginas);
|
||||
const paginaAtual = computed(() => props.pagina);
|
||||
const totalPaginasExibidas = computed(() => props.totalPaginas);
|
||||
const anteriorDesabilitado = computed(() => props.pagina <= 1)
|
||||
const proximaDesabilitada = computed(
|
||||
() => props.pagina >= props.totalPaginas,
|
||||
)
|
||||
const paginaAtual = computed(() => props.pagina)
|
||||
const totalPaginasExibidas = computed(() => props.totalPaginas)
|
||||
|
||||
return {
|
||||
botoes,
|
||||
|
|
@ -175,9 +222,9 @@ export default defineComponent({
|
|||
proximaDesabilitada,
|
||||
paginaAtual,
|
||||
totalPaginasExibidas,
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
|
@ -241,4 +288,31 @@ export default defineComponent({
|
|||
color: rgba(107, 114, 128, 0.85);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.eli-tabela__acao-inferior {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
height: 34px;
|
||||
padding: 0 14px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #16a34a; /* default green */
|
||||
background: #ffffff;
|
||||
color: #16a34a;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease, color 0.2s ease;
|
||||
}
|
||||
|
||||
.eli-tabela__acao-inferior:hover,
|
||||
.eli-tabela__acao-inferior:focus-visible {
|
||||
background-color: #f0fdf4;
|
||||
}
|
||||
|
||||
.eli-tabela__acao-inferior:focus-visible {
|
||||
outline: 2px solid #16a34a;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -8,11 +8,15 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import type { Component } from "vue";
|
||||
import { computed, defineComponent, PropType } from "vue";
|
||||
import type { Component } from "vue"
|
||||
import { computed, defineComponent, type PropType } from "vue"
|
||||
|
||||
import type { tipoComponenteCelula, tipoTabelaCelula, tiposTabelaCelulas } from "../types-eli-tabela";
|
||||
import { registryTabelaCelulas } from "./registryTabelaCelulas";
|
||||
import type {
|
||||
tipoComponenteCelula,
|
||||
tiposTabelaCelulas,
|
||||
tipoTabelaCelula,
|
||||
} from "../types-eli-tabela"
|
||||
import { registryTabelaCelulas } from "./registryTabelaCelulas"
|
||||
|
||||
export default defineComponent({
|
||||
name: "EliTabelaCelula",
|
||||
|
|
@ -24,16 +28,20 @@ export default defineComponent({
|
|||
},
|
||||
},
|
||||
setup(props) {
|
||||
const tipo = computed(() => props.celula[0] as tipoTabelaCelula);
|
||||
const dados = computed(() => props.celula[1] as tiposTabelaCelulas[tipoTabelaCelula]);
|
||||
const tipo = computed(() => props.celula[0] as tipoTabelaCelula)
|
||||
const dados = computed(
|
||||
() => props.celula[1] as tiposTabelaCelulas[tipoTabelaCelula],
|
||||
)
|
||||
|
||||
// Observação: mantemos o registry tipado, mas o TS do template não consegue
|
||||
// fazer narrowing do componente com base em `tipo`, então tipamos como `Component`.
|
||||
const Componente = computed(() => registryTabelaCelulas[tipo.value] as unknown as Component);
|
||||
const Componente = computed(
|
||||
() => registryTabelaCelulas[tipo.value] as unknown as Component,
|
||||
)
|
||||
|
||||
const dadosParaComponente = computed(() => dados.value);
|
||||
const dadosParaComponente = computed(() => dados.value)
|
||||
|
||||
return { Componente, dadosParaComponente };
|
||||
return { Componente, dadosParaComponente }
|
||||
},
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -12,14 +12,9 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, PropType } from "vue";
|
||||
import dayjs from "dayjs";
|
||||
import relativeTime from "dayjs/plugin/relativeTime";
|
||||
|
||||
import type { tiposTabelaCelulas } from "./tiposTabelaCelulas";
|
||||
|
||||
// Necessário para `fromNow()`.
|
||||
dayjs.extend(relativeTime);
|
||||
import { dayjsbr } from "p-comuns"
|
||||
import { computed, defineComponent, type PropType } from "vue"
|
||||
import type { tiposTabelaCelulas } from "./tiposTabelaCelulas"
|
||||
|
||||
export default defineComponent({
|
||||
name: "EliTabelaCelulaData",
|
||||
|
|
@ -31,27 +26,27 @@ export default defineComponent({
|
|||
},
|
||||
setup({ dados }) {
|
||||
const textoData = computed(() => {
|
||||
const valorIso = dados?.valor;
|
||||
if (!valorIso) return "";
|
||||
const valorIso = dados?.valor
|
||||
if (!valorIso) return ""
|
||||
|
||||
const formato = dados?.formato ?? "data";
|
||||
const formato = dados?.formato ?? "data"
|
||||
|
||||
if (formato === "relativo") {
|
||||
return dayjs(valorIso).fromNow();
|
||||
return dayjsbr(valorIso).fromNow()
|
||||
}
|
||||
|
||||
if (formato === "data_hora") {
|
||||
// Padrão pt-BR simples (sem depender de locale do dayjs)
|
||||
return dayjs(valorIso).format("DD/MM/YYYY HH:mm");
|
||||
// Padrão pt-BR simples (sem depender de locale do dayjsbr)
|
||||
return dayjsbr(valorIso).format("DD/MM/YYYY HH:mm")
|
||||
}
|
||||
|
||||
// formato === "data"
|
||||
return dayjs(valorIso).format("DD/MM/YYYY");
|
||||
});
|
||||
return dayjsbr(valorIso).format("DD/MM/YYYY")
|
||||
})
|
||||
|
||||
return { dados, textoData };
|
||||
return { dados, textoData }
|
||||
},
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
|
|
|||
|
|
@ -10,9 +10,22 @@
|
|||
<span v-else>{{ textoNumero }}</span>
|
||||
</template>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, PropType } from "vue"
|
||||
import type { tiposTabelaCelulas } from "./tiposTabelaCelulas";
|
||||
import { computed, defineComponent, type PropType } from "vue"
|
||||
import type { tiposTabelaCelulas } from "./tiposTabelaCelulas"
|
||||
|
||||
export default defineComponent({
|
||||
name: "EliTabelaCelulaNumero",
|
||||
|
|
@ -25,15 +38,15 @@ export default defineComponent({
|
|||
setup({ dados }) {
|
||||
const textoNumero = computed(() => {
|
||||
// Mantemos o comportamento anterior (trocar "." por ","), mas agora suportamos prefixo/sufixo.
|
||||
const numero = String(dados?.numero).replace(".", ",");
|
||||
const prefixo = dados?.prefixo?.trim();
|
||||
const sufixo = dados?.sufixo?.trim();
|
||||
const numero = String(dados?.numero).replace(".", ",")
|
||||
const prefixo = dados?.prefixo?.trim()
|
||||
const sufixo = dados?.sufixo?.trim()
|
||||
|
||||
const inicio = prefixo ? `${prefixo} ` : "";
|
||||
const fim = sufixo ? ` ${sufixo}` : "";
|
||||
const inicio = prefixo ? `${prefixo} ` : ""
|
||||
const fim = sufixo ? ` ${sufixo}` : ""
|
||||
|
||||
return `${inicio}${numero}${fim}`;
|
||||
});
|
||||
return `${inicio}${numero}${fim}`
|
||||
})
|
||||
|
||||
return { dados, textoNumero }
|
||||
},
|
||||
|
|
|
|||
|
|
@ -23,10 +23,10 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from "vue";
|
||||
import { VChip } from "vuetify/components";
|
||||
import { defineComponent, type PropType } from "vue"
|
||||
import { VChip } from "vuetify/components"
|
||||
|
||||
import type { tiposTabelaCelulas } from "./tiposTabelaCelulas";
|
||||
import type { tiposTabelaCelulas } from "./tiposTabelaCelulas"
|
||||
|
||||
export default defineComponent({
|
||||
name: "EliTabelaCelulaTags",
|
||||
|
|
@ -38,9 +38,9 @@ export default defineComponent({
|
|||
},
|
||||
},
|
||||
setup({ dados }) {
|
||||
return { dados };
|
||||
return { dados }
|
||||
},
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from "vue"
|
||||
import type { tiposTabelaCelulas } from "./tiposTabelaCelulas";
|
||||
import { defineComponent, type PropType } from "vue"
|
||||
import type { tiposTabelaCelulas } from "./tiposTabelaCelulas"
|
||||
|
||||
export default defineComponent({
|
||||
name: "EliTabelaCelulaTextoSimples",
|
||||
|
|
@ -23,12 +23,9 @@ export default defineComponent({
|
|||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
return {}
|
||||
},
|
||||
methods: {},
|
||||
setup({ dados }) {
|
||||
return { dados }
|
||||
},
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from "vue";
|
||||
import type { tiposTabelaCelulas } from "./tiposTabelaCelulas";
|
||||
import { defineComponent, type PropType } from "vue"
|
||||
import type { tiposTabelaCelulas } from "./tiposTabelaCelulas"
|
||||
|
||||
export default defineComponent({
|
||||
name: "EliTabelaCelulaTextoTruncado",
|
||||
|
|
@ -24,9 +24,9 @@ export default defineComponent({
|
|||
},
|
||||
},
|
||||
setup({ dados }) {
|
||||
return { dados };
|
||||
return { dados }
|
||||
},
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
import type { Component } from "vue";
|
||||
|
||||
import EliTabelaCelulaTextoSimples from "./EliTabelaCelulaTextoSimples.vue";
|
||||
import EliTabelaCelulaTextoTruncado from "./EliTabelaCelulaTextoTruncado.vue";
|
||||
import EliTabelaCelulaNumero from "./EliTabelaCelulaNumero.vue";
|
||||
import EliTabelaCelulaTags from "./EliTabelaCelulaTags.vue";
|
||||
import EliTabelaCelulaData from "./EliTabelaCelulaData.vue";
|
||||
import type { tipoTabelaCelula } from "./tiposTabelaCelulas";
|
||||
import type { Component } from "vue"
|
||||
import EliTabelaCelulaData from "./EliTabelaCelulaData.vue"
|
||||
import EliTabelaCelulaNumero from "./EliTabelaCelulaNumero.vue"
|
||||
import EliTabelaCelulaTags from "./EliTabelaCelulaTags.vue"
|
||||
import EliTabelaCelulaTextoSimples from "./EliTabelaCelulaTextoSimples.vue"
|
||||
import EliTabelaCelulaTextoTruncado from "./EliTabelaCelulaTextoTruncado.vue"
|
||||
import type { tipoTabelaCelula } from "./tiposTabelaCelulas"
|
||||
|
||||
export const registryTabelaCelulas = {
|
||||
textoSimples: EliTabelaCelulaTextoSimples,
|
||||
|
|
@ -13,4 +12,4 @@ export const registryTabelaCelulas = {
|
|||
numero: EliTabelaCelulaNumero,
|
||||
tags: EliTabelaCelulaTags,
|
||||
data: EliTabelaCelulaData,
|
||||
} as const satisfies Record<tipoTabelaCelula, Component>;
|
||||
} as const satisfies Record<tipoTabelaCelula, Component>
|
||||
|
|
|
|||
|
|
@ -1,49 +1,48 @@
|
|||
/**
|
||||
* Tipagem dos dados de entrada dos componentes de celulas
|
||||
* Tipagem dos dados de entrada dos componentes de celulas
|
||||
*/
|
||||
|
||||
import type { LucideIcon } from "lucide-vue-next";
|
||||
import type { LucideIcon } from "lucide-vue-next"
|
||||
|
||||
export type tiposTabelaCelulas = {
|
||||
textoSimples: {
|
||||
texto: string;
|
||||
acao?: () => void;
|
||||
};
|
||||
texto: string
|
||||
acao?: () => void
|
||||
}
|
||||
textoTruncado: {
|
||||
texto: string;
|
||||
acao?: () => void;
|
||||
};
|
||||
texto: string
|
||||
acao?: () => void
|
||||
}
|
||||
numero: {
|
||||
numero: number;
|
||||
numero: number
|
||||
/** Texto opcional exibido antes do número (ex.: "R$", "≈"). */
|
||||
prefixo?: string;
|
||||
prefixo?: string
|
||||
/** Texto opcional exibido depois do número (ex.: "kg", "%"). */
|
||||
sufixo?: string;
|
||||
acao?: () => void;
|
||||
};
|
||||
sufixo?: string
|
||||
acao?: () => void
|
||||
}
|
||||
|
||||
tags: {
|
||||
opcoes: {
|
||||
/** Texto exibido dentro da tag. */
|
||||
rotulo: string;
|
||||
rotulo: string
|
||||
/** Cor do chip (segue as cores do Vuetify, ex.: "primary", "success", "error"). */
|
||||
cor?: string;
|
||||
cor?: string
|
||||
/** Ícone (Lucide) opcional exibido antes do rótulo. */
|
||||
icone?: LucideIcon;
|
||||
icone?: LucideIcon
|
||||
/** Ação opcional da tag. Quando existir, o chip vira clicável. */
|
||||
acao?: () => void;
|
||||
}[];
|
||||
};
|
||||
acao?: () => void
|
||||
}[]
|
||||
}
|
||||
|
||||
data: {
|
||||
/** Valor em ISO 8601 (ex.: "2026-01-09T16:15:00Z"). */
|
||||
valor: string;
|
||||
valor: string
|
||||
/** Define o formato de exibição. */
|
||||
formato: "data" | "data_hora" | "relativo";
|
||||
formato: "data" | "data_hora" | "relativo"
|
||||
/** Ação opcional ao clicar no valor. */
|
||||
acao?: () => void;
|
||||
};
|
||||
};
|
||||
|
||||
export type tipoTabelaCelula = keyof tiposTabelaCelulas;
|
||||
acao?: () => void
|
||||
}
|
||||
}
|
||||
|
||||
export type tipoTabelaCelula = keyof tiposTabelaCelulas
|
||||
|
|
|
|||
|
|
@ -1,40 +1,55 @@
|
|||
export type EliTabelaColunasConfig = {
|
||||
/** Rotulos das colunas visiveis (em ordem). */
|
||||
visiveis: string[];
|
||||
visiveis: string[]
|
||||
/** Rotulos das colunas invisiveis. */
|
||||
invisiveis: string[];
|
||||
};
|
||||
invisiveis: string[]
|
||||
}
|
||||
|
||||
const STORAGE_PREFIX = "eli:tabela";
|
||||
const STORAGE_PREFIX = "eli:tabela"
|
||||
|
||||
export function storageKeyColunas(nomeTabela: string) {
|
||||
return `${STORAGE_PREFIX}:${nomeTabela}:colunas`;
|
||||
return `${STORAGE_PREFIX}:${nomeTabela}:colunas`
|
||||
}
|
||||
|
||||
function normalizarConfig(valor: unknown): EliTabelaColunasConfig {
|
||||
if (!valor || typeof valor !== "object") {
|
||||
return { visiveis: [], invisiveis: [] };
|
||||
return { visiveis: [], invisiveis: [] }
|
||||
}
|
||||
|
||||
const v = valor as any;
|
||||
const visiveis = Array.isArray(v.visiveis) ? v.visiveis.filter((x: any) => typeof x === "string") : [];
|
||||
const invisiveis = Array.isArray(v.invisiveis) ? v.invisiveis.filter((x: any) => typeof x === "string") : [];
|
||||
return { visiveis, invisiveis };
|
||||
// biome-ignore lint/suspicious/noExplicitAny: dynamic config
|
||||
const v = valor as any
|
||||
const visiveis = Array.isArray(v.visiveis)
|
||||
? // biome-ignore lint/suspicious/noExplicitAny: dynamic array item
|
||||
v.visiveis.filter((x: any) => typeof x === "string")
|
||||
: []
|
||||
const invisiveis = Array.isArray(v.invisiveis)
|
||||
? // biome-ignore lint/suspicious/noExplicitAny: dynamic array item
|
||||
v.invisiveis.filter((x: any) => typeof x === "string")
|
||||
: []
|
||||
return { visiveis, invisiveis }
|
||||
}
|
||||
|
||||
export function carregarConfigColunas(nomeTabela: string): EliTabelaColunasConfig {
|
||||
export function carregarConfigColunas(
|
||||
nomeTabela: string,
|
||||
): EliTabelaColunasConfig {
|
||||
try {
|
||||
const raw = window.localStorage.getItem(storageKeyColunas(nomeTabela));
|
||||
if (!raw) return { visiveis: [], invisiveis: [] };
|
||||
return normalizarConfig(JSON.parse(raw));
|
||||
const raw = window.localStorage.getItem(storageKeyColunas(nomeTabela))
|
||||
if (!raw) return { visiveis: [], invisiveis: [] }
|
||||
return normalizarConfig(JSON.parse(raw))
|
||||
} catch {
|
||||
return { visiveis: [], invisiveis: [] };
|
||||
return { visiveis: [], invisiveis: [] }
|
||||
}
|
||||
}
|
||||
|
||||
export function salvarConfigColunas(nomeTabela: string, config: EliTabelaColunasConfig) {
|
||||
export function salvarConfigColunas(
|
||||
nomeTabela: string,
|
||||
config: EliTabelaColunasConfig,
|
||||
) {
|
||||
try {
|
||||
window.localStorage.setItem(storageKeyColunas(nomeTabela), JSON.stringify(normalizarConfig(config)));
|
||||
window.localStorage.setItem(
|
||||
storageKeyColunas(nomeTabela),
|
||||
JSON.stringify(normalizarConfig(config)),
|
||||
)
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
|
|
@ -42,7 +57,7 @@ export function salvarConfigColunas(nomeTabela: string, config: EliTabelaColunas
|
|||
|
||||
export function limparConfigColunas(nomeTabela: string) {
|
||||
try {
|
||||
window.localStorage.removeItem(storageKeyColunas(nomeTabela));
|
||||
window.localStorage.removeItem(storageKeyColunas(nomeTabela))
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,26 +1,35 @@
|
|||
export type EliTabelaFiltroAvancadoSalvo<T> = Array<{
|
||||
coluna: keyof T
|
||||
// biome-ignore lint/suspicious/noExplicitAny: dynamic value
|
||||
valor: any
|
||||
}>;
|
||||
}>
|
||||
|
||||
function key(nomeTabela: string) {
|
||||
return `eli_tabela:${nomeTabela}:filtro_avancado`;
|
||||
return `eli_tabela:${nomeTabela}:filtro_avancado`
|
||||
}
|
||||
|
||||
export function carregarFiltroAvancado<T>(nomeTabela: string): EliTabelaFiltroAvancadoSalvo<T> {
|
||||
export function carregarFiltroAvancado<T>(
|
||||
nomeTabela: string,
|
||||
): EliTabelaFiltroAvancadoSalvo<T> {
|
||||
try {
|
||||
const raw = localStorage.getItem(key(nomeTabela));
|
||||
if (!raw) return [] as unknown as EliTabelaFiltroAvancadoSalvo<T>;
|
||||
const parsed = JSON.parse(raw);
|
||||
return Array.isArray(parsed) ? (parsed as EliTabelaFiltroAvancadoSalvo<T>) : ([] as any);
|
||||
const raw = localStorage.getItem(key(nomeTabela))
|
||||
if (!raw) return [] as unknown as EliTabelaFiltroAvancadoSalvo<T>
|
||||
const parsed = JSON.parse(raw)
|
||||
return Array.isArray(parsed)
|
||||
? (parsed as EliTabelaFiltroAvancadoSalvo<T>)
|
||||
: // biome-ignore lint/suspicious/noExplicitAny: dynamic cast
|
||||
([] as any)
|
||||
} catch {
|
||||
return [] as unknown as EliTabelaFiltroAvancadoSalvo<T>;
|
||||
return [] as unknown as EliTabelaFiltroAvancadoSalvo<T>
|
||||
}
|
||||
}
|
||||
|
||||
export function salvarFiltroAvancado<T>(nomeTabela: string, filtros: EliTabelaFiltroAvancadoSalvo<T>) {
|
||||
export function salvarFiltroAvancado<T>(
|
||||
nomeTabela: string,
|
||||
filtros: EliTabelaFiltroAvancadoSalvo<T>,
|
||||
) {
|
||||
try {
|
||||
localStorage.setItem(key(nomeTabela), JSON.stringify(filtros ?? []));
|
||||
localStorage.setItem(key(nomeTabela), JSON.stringify(filtros ?? []))
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
|
|
@ -28,7 +37,7 @@ export function salvarFiltroAvancado<T>(nomeTabela: string, filtros: EliTabelaFi
|
|||
|
||||
export function limparFiltroAvancado(nomeTabela: string) {
|
||||
try {
|
||||
localStorage.removeItem(key(nomeTabela));
|
||||
localStorage.removeItem(key(nomeTabela))
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
export { default as EliTabela } from "./EliTabela.vue";
|
||||
|
||||
export * from "./types-eli-tabela";
|
||||
export * from "./celulas/tiposTabelaCelulas";
|
||||
export * from "./celulas/tiposTabelaCelulas"
|
||||
export { default as EliTabela } from "./EliTabela.vue"
|
||||
export * from "./types-eli-tabela"
|
||||
|
||||
// Helper para construção de células tipadas.
|
||||
export { celulaTabela } from "./types-eli-tabela";
|
||||
export { celulaTabela } from "./types-eli-tabela"
|
||||
|
|
|
|||
|
|
@ -1,18 +1,19 @@
|
|||
import type { tipoResposta } from "p-respostas";
|
||||
import type { LucideIcon } from "lucide-vue-next";
|
||||
import type { tipoTabelaCelula, tiposTabelaCelulas } from "./celulas/tiposTabelaCelulas";
|
||||
import { operadores, zFiltro } from "p-comuns";
|
||||
import { ComponenteEntrada } from "../EliEntrada/tiposEntradas";
|
||||
import type { LucideIcon } from "lucide-vue-next"
|
||||
import type { operadores, zFiltro } from "p-comuns"
|
||||
import type { tipoResposta } from "p-respostas"
|
||||
import type { ComponenteEntrada } from "../EliEntrada/tiposEntradas"
|
||||
import type {
|
||||
tiposTabelaCelulas,
|
||||
tipoTabelaCelula,
|
||||
} from "./celulas/tiposTabelaCelulas"
|
||||
|
||||
// `p-comuns` expõe `zFiltro` (schema). Inferimos o tipo a partir do `parse`.
|
||||
export type tipoFiltro = ReturnType<(typeof zFiltro)["parse"]>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export type tipoComponenteCelulaBase<T extends tipoTabelaCelula> =
|
||||
readonly [T, tiposTabelaCelulas[T]]
|
||||
export type tipoComponenteCelulaBase<T extends tipoTabelaCelula> = readonly [
|
||||
T,
|
||||
tiposTabelaCelulas[T],
|
||||
]
|
||||
|
||||
export type tipoComponenteCelula = {
|
||||
[K in tipoTabelaCelula]: tipoComponenteCelulaBase<K>
|
||||
|
|
@ -25,54 +26,60 @@ export const celulaTabela = <T extends tipoTabelaCelula>(
|
|||
return [tipo, dados] as const
|
||||
}
|
||||
|
||||
export type { tipoTabelaCelula, tiposTabelaCelulas };
|
||||
|
||||
|
||||
|
||||
export type { tipoTabelaCelula, tiposTabelaCelulas }
|
||||
|
||||
export type tipoEliColuna<T> = {
|
||||
/** Texto exibido no cabeçalho da coluna. */
|
||||
rotulo: string;
|
||||
rotulo: string
|
||||
/** Função responsável por renderizar o conteúdo da célula. */
|
||||
celula: (linha: T) => tipoComponenteCelula;
|
||||
celula: (linha: T) => tipoComponenteCelula
|
||||
/** Ação opcional disparada ao clicar na célula. */
|
||||
|
||||
/**
|
||||
* Campo de ordenação associado à coluna. Caso informado, a coluna passa a
|
||||
* exibir controles de ordenação e utiliza o valor como chave para o backend.
|
||||
*/
|
||||
coluna_ordem?: keyof T;
|
||||
coluna_ordem?: keyof T
|
||||
|
||||
/**
|
||||
* indica que a coluna será visivel, se false incia em detalhe
|
||||
* Caso já tenha salvo a propriedade de visibilidade será adotado a propriedade salva
|
||||
*/
|
||||
visivel: boolean
|
||||
};
|
||||
}
|
||||
|
||||
export type tipoEliConsultaPaginada<T> = {
|
||||
/** Registros retornados na consulta. */
|
||||
valores: T[];
|
||||
valores: T[]
|
||||
/** Total de registros disponíveis no backend. */
|
||||
quantidade: number;
|
||||
};
|
||||
quantidade: number
|
||||
}
|
||||
|
||||
export type tipoEliTabelaAcao<T> = {
|
||||
/** Ícone (Lucide) exibido para representar a ação. */
|
||||
icone: LucideIcon;
|
||||
icone: LucideIcon
|
||||
/** Cor aplicada ao ícone e rótulo. */
|
||||
cor: string;
|
||||
cor: string
|
||||
/** Texto descritivo da ação. */
|
||||
rotulo: string;
|
||||
rotulo: string
|
||||
/** Função executada quando o usuário ativa a ação. */
|
||||
acao: (linha: T) => void;
|
||||
acao: (linha: T) => void
|
||||
/**
|
||||
* Define se a ação deve ser exibida para a linha. Pode ser um booleano fixo
|
||||
* ou uma função (sincrona/assíncrona) que recebe a linha para decisão dinâmica.
|
||||
*/
|
||||
exibir?: boolean | ((linha: T) => Promise<boolean> | boolean);
|
||||
};
|
||||
exibir?: boolean | ((linha: T) => Promise<boolean> | boolean)
|
||||
}
|
||||
|
||||
export type parametrosConsulta<T> = {
|
||||
filtros?: tipoFiltro[]
|
||||
coluna_ordem?: keyof T
|
||||
direcao_ordem?: "asc" | "desc"
|
||||
offSet?: number
|
||||
limit?: number
|
||||
/** Texto digitado na caixa de busca, quando habilitada. */
|
||||
texto_busca?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Estrutura de dados para uma tabela alimentada por uma consulta.
|
||||
|
|
@ -85,45 +92,40 @@ export type tipoEliTabelaConsulta<T> = {
|
|||
/** nome da tabela, um identificador unico */
|
||||
nome: string
|
||||
/** Indica se a caixa de busca deve ser exibida acima da tabela. */
|
||||
mostrarCaixaDeBusca?: boolean;
|
||||
mostrarCaixaDeBusca?: boolean
|
||||
/** Lista de colunas da tabela. */
|
||||
colunas: tipoEliColuna<T>[];
|
||||
colunas: tipoEliColuna<T>[]
|
||||
/** Quantidade de registros solicitados por consulta (padrão `10`). */
|
||||
registros_por_consulta?: number;
|
||||
registros_por_consulta?: number
|
||||
/**
|
||||
* Função responsável por buscar os dados. Recebe parâmetros opcionais de
|
||||
* ordenação (`coluna_ordem`/`direcao_ordem`) e paginação (`offSet`/`limit`).
|
||||
*/
|
||||
consulta: (parametrosConsulta?: {
|
||||
|
||||
|
||||
filtros?: tipoFiltro[]
|
||||
coluna_ordem?: keyof T;
|
||||
direcao_ordem?: "asc" | "desc";
|
||||
offSet?: number;
|
||||
limit?: number;
|
||||
/** Texto digitado na caixa de busca, quando habilitada. */
|
||||
texto_busca?: string;
|
||||
}) => Promise<tipoResposta<tipoEliConsultaPaginada<T>>>;
|
||||
consulta: (
|
||||
parametrosConsulta?: parametrosConsulta<T>,
|
||||
) => Promise<tipoResposta<tipoEliConsultaPaginada<T>>>
|
||||
/** Quantidade máxima de botões exibidos na paginação (padrão `7`). */
|
||||
maximo_botoes_paginacao?: number;
|
||||
maximo_botoes_paginacao?: number
|
||||
/** Mensagem exibida quando a consulta retorna ok porém sem dados. */
|
||||
mensagemVazio?: string;
|
||||
mensagemVazio?: string
|
||||
/** Ações exibidas à direita de cada linha. */
|
||||
acoesLinha?: tipoEliTabelaAcao<T>[];
|
||||
acoesLinha?: tipoEliTabelaAcao<T>[]
|
||||
/**
|
||||
* Configurações dos botões que serão inseridos a direita da caixa de busca.
|
||||
* Seu uso mais comum será para criar novos registros, mas poderá ter outras utilidades.
|
||||
*/
|
||||
acoesTabela?: {
|
||||
/** superio será exibido a direita da caixa de busca, inferior a direita da paginação */
|
||||
posicao: "superior" | "inferior"
|
||||
|
||||
/** Ícone (Lucide) exibido no botão */
|
||||
icone?: LucideIcon;
|
||||
icone?: LucideIcon
|
||||
/** Cor aplicada ao botão. */
|
||||
cor?: string;
|
||||
cor?: string
|
||||
/** Texto descritivo da ação. */
|
||||
rotulo: string;
|
||||
rotulo: string
|
||||
/** Função executada ao clicar no botão. */
|
||||
acao: () => void;
|
||||
acao: (parametrosConsulta?: parametrosConsulta<T>) => void
|
||||
|
||||
/**
|
||||
* Callback opcional para forçar atualização da consulta.
|
||||
|
|
@ -135,14 +137,11 @@ export type tipoEliTabelaConsulta<T> = {
|
|||
* Observação: o componente `EliTabela` pode ignorar isso dependendo do modo de uso.
|
||||
*/
|
||||
editarLista?: (lista: T[]) => Promise<T[]>
|
||||
}[];
|
||||
|
||||
filtroAvancado?: {
|
||||
coluna: keyof T,
|
||||
operador: operadores | keyof typeof operadores,
|
||||
entrada: ComponenteEntrada
|
||||
}[]
|
||||
|
||||
|
||||
};
|
||||
|
||||
filtroAvancado?: {
|
||||
coluna: keyof T
|
||||
operador: operadores | keyof typeof operadores
|
||||
entrada: ComponenteEntrada
|
||||
}[]
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue