adicionado detalhes

This commit is contained in:
Luiz Silva 2026-01-28 19:28:34 -03:00
parent 133f32e4f7
commit 5c587c9232
9 changed files with 496 additions and 50 deletions

View file

@ -4,6 +4,7 @@
*/ */
/** Dependências do Vue (Composition API) */ /** Dependências do Vue (Composition API) */
import { PropType } from "vue"; import { PropType } from "vue";
import type { EliColuna } from "./types-eli-tabela";
/** Tipos da configuração/contrato da tabela */ /** Tipos da configuração/contrato da tabela */
import type { EliTabelaConsulta } from "./types-eli-tabela"; import type { EliTabelaConsulta } from "./types-eli-tabela";
import { type EliTabelaColunasConfig } from "./colunasStorage"; import { type EliTabelaColunasConfig } from "./colunasStorage";
@ -45,9 +46,13 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
visiveis: string[]; visiveis: string[];
invisiveis: string[]; invisiveis: string[];
}>; }>;
temColunasInvisiveis: import("vue").ComputedRef<boolean>;
colunasInvisiveisEfetivas: import("vue").ComputedRef<EliColuna<any>[]>;
linhasExpandidas: import("vue").Ref<Record<number, boolean>, Record<number, boolean>>;
abrirModalColunas: () => void; abrirModalColunas: () => void;
fecharModalColunas: () => void; fecharModalColunas: () => void;
salvarModalColunas: (cfg: EliTabelaColunasConfig) => void; salvarModalColunas: (cfg: EliTabelaColunasConfig) => void;
alternarLinhaExpandida: (indice: number) => void;
alternarOrdenacao: (chave?: string) => void; alternarOrdenacao: (chave?: string) => void;
atualizarBusca: (texto: string) => void; atualizarBusca: (texto: string) => void;
irParaPagina: (pagina: number) => void; irParaPagina: (pagina: number) => void;
@ -262,13 +267,17 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
}>> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>; }>> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
EliTabelaHead: import("vue").DefineComponent<import("vue").ExtractPropTypes<{ EliTabelaHead: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
colunas: { colunas: {
type: PropType<Array<import("./types-eli-tabela").EliColuna<any>>>; type: PropType<Array<EliColuna<any>>>;
required: true; required: true;
}; };
temAcoes: { temAcoes: {
type: BooleanConstructor; type: BooleanConstructor;
required: true; required: true;
}; };
temColunasInvisiveis: {
type: BooleanConstructor;
required: true;
};
colunaOrdenacao: { colunaOrdenacao: {
type: PropType<string | null>; type: PropType<string | null>;
required: true; required: true;
@ -287,13 +296,17 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
alternarOrdenacao(chave: string): boolean; alternarOrdenacao(chave: string): boolean;
}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{ }, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
colunas: { colunas: {
type: PropType<Array<import("./types-eli-tabela").EliColuna<any>>>; type: PropType<Array<EliColuna<any>>>;
required: true; required: true;
}; };
temAcoes: { temAcoes: {
type: BooleanConstructor; type: BooleanConstructor;
required: true; required: true;
}; };
temColunasInvisiveis: {
type: BooleanConstructor;
required: true;
};
colunaOrdenacao: { colunaOrdenacao: {
type: PropType<string | null>; type: PropType<string | null>;
required: true; required: true;
@ -310,7 +323,19 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>; }, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
EliTabelaBody: import("vue").DefineComponent<import("vue").ExtractPropTypes<{ EliTabelaBody: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
colunas: { colunas: {
type: PropType<Array<import("./types-eli-tabela").EliColuna<any>>>; type: PropType<Array<EliColuna<any>>>;
required: true;
};
colunasInvisiveis: {
type: PropType<Array<EliColuna<any>>>;
required: true;
};
temColunasInvisiveis: {
type: BooleanConstructor;
required: true;
};
linhasExpandidas: {
type: PropType<Record<number, boolean>>;
required: true; required: true;
}; };
linhas: { linhas: {
@ -333,13 +358,31 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
type: PropType<(indice: number, evento: MouseEvent) => void>; type: PropType<(indice: number, evento: MouseEvent) => void>;
required: true; required: true;
}; };
alternarLinhaExpandida: {
type: PropType<(indice: number) => void>;
required: true;
};
}>, { }>, {
ChevronRight: import("vue").FunctionalComponent<import("lucide-vue-next").LucideProps, {}, any, {}>;
ChevronDown: import("vue").FunctionalComponent<import("lucide-vue-next").LucideProps, {}, any, {}>;
obterClasseAlinhamento: (alinhamento?: string) => "eli-tabela__celula--direita" | "eli-tabela__celula--centro" | "eli-tabela__celula--esquerda"; obterClasseAlinhamento: (alinhamento?: string) => "eli-tabela__celula--direita" | "eli-tabela__celula--centro" | "eli-tabela__celula--esquerda";
obterMaxWidth: (largura?: number | string) => string | undefined; obterMaxWidth: (largura?: number | string) => string | undefined;
obterTooltipCelula: (celula: unknown) => any; obterTooltipCelula: (celula: unknown) => any;
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{ }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
colunas: { colunas: {
type: PropType<Array<import("./types-eli-tabela").EliColuna<any>>>; type: PropType<Array<EliColuna<any>>>;
required: true;
};
colunasInvisiveis: {
type: PropType<Array<EliColuna<any>>>;
required: true;
};
temColunasInvisiveis: {
type: BooleanConstructor;
required: true;
};
linhasExpandidas: {
type: PropType<Record<number, boolean>>;
required: true; required: true;
}; };
linhas: { linhas: {
@ -362,6 +405,10 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
type: PropType<(indice: number, evento: MouseEvent) => void>; type: PropType<(indice: number, evento: MouseEvent) => void>;
required: true; required: true;
}; };
alternarLinhaExpandida: {
type: PropType<(indice: number) => void>;
required: true;
};
}>> & Readonly<{}>, {}, {}, { }>> & Readonly<{}>, {}, {}, {
EliTabelaCelula: import("vue").DefineComponent<import("vue").ExtractPropTypes<{ EliTabelaCelula: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
celula: { celula: {
@ -383,7 +430,49 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
required: true; required: true;
}; };
}>> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>; }>> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
EliTabelaDetalhesLinha: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
linha: {
type: PropType<unknown>;
required: true;
};
colunasInvisiveis: {
type: PropType<Array<EliColuna<any>>>;
required: true;
};
}>, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
linha: {
type: PropType<unknown>;
required: true;
};
colunasInvisiveis: {
type: PropType<Array<EliColuna<any>>>;
required: true;
};
}>> & Readonly<{}>, {}, {}, {
EliTabelaCelula: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
celula: {
type: PropType<import("./types-eli-tabela").ComponenteCelula>;
required: true;
};
}>, {
Componente: import("vue").ComputedRef<import("vue").Component>;
dadosParaComponente: import("vue").ComputedRef<{
texto: string;
acao?: () => void;
} | {
numero: number;
acao?: () => void;
}>;
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
celula: {
type: PropType<import("./types-eli-tabela").ComponenteCelula>;
required: true;
};
}>> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
MoreVertical: import("vue").FunctionalComponent<import("lucide-vue-next").LucideProps, {}, any, {}>; MoreVertical: import("vue").FunctionalComponent<import("lucide-vue-next").LucideProps, {}, any, {}>;
ChevronRight: import("vue").FunctionalComponent<import("lucide-vue-next").LucideProps, {}, any, {}>;
ChevronDown: import("vue").FunctionalComponent<import("lucide-vue-next").LucideProps, {}, any, {}>;
}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>; }, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
EliTabelaMenuAcoes: import("vue").DefineComponent<import("vue").ExtractPropTypes<{ EliTabelaMenuAcoes: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
menuAberto: { menuAberto: {

View file

@ -5,6 +5,18 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
type: PropType<Array<EliColuna<any>>>; type: PropType<Array<EliColuna<any>>>;
required: true; required: true;
}; };
colunasInvisiveis: {
type: PropType<Array<EliColuna<any>>>;
required: true;
};
temColunasInvisiveis: {
type: BooleanConstructor;
required: true;
};
linhasExpandidas: {
type: PropType<Record<number, boolean>>;
required: true;
};
linhas: { linhas: {
type: PropType<Array<unknown>>; type: PropType<Array<unknown>>;
required: true; required: true;
@ -25,7 +37,13 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
type: PropType<(indice: number, evento: MouseEvent) => void>; type: PropType<(indice: number, evento: MouseEvent) => void>;
required: true; required: true;
}; };
alternarLinhaExpandida: {
type: PropType<(indice: number) => void>;
required: true;
};
}>, { }>, {
ChevronRight: import("vue").FunctionalComponent<import("lucide-vue-next").LucideProps, {}, any, {}>;
ChevronDown: import("vue").FunctionalComponent<import("lucide-vue-next").LucideProps, {}, any, {}>;
obterClasseAlinhamento: (alinhamento?: string) => "eli-tabela__celula--direita" | "eli-tabela__celula--centro" | "eli-tabela__celula--esquerda"; obterClasseAlinhamento: (alinhamento?: string) => "eli-tabela__celula--direita" | "eli-tabela__celula--centro" | "eli-tabela__celula--esquerda";
obterMaxWidth: (largura?: number | string) => string | undefined; obterMaxWidth: (largura?: number | string) => string | undefined;
obterTooltipCelula: (celula: unknown) => any; obterTooltipCelula: (celula: unknown) => any;
@ -34,6 +52,18 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
type: PropType<Array<EliColuna<any>>>; type: PropType<Array<EliColuna<any>>>;
required: true; required: true;
}; };
colunasInvisiveis: {
type: PropType<Array<EliColuna<any>>>;
required: true;
};
temColunasInvisiveis: {
type: BooleanConstructor;
required: true;
};
linhasExpandidas: {
type: PropType<Record<number, boolean>>;
required: true;
};
linhas: { linhas: {
type: PropType<Array<unknown>>; type: PropType<Array<unknown>>;
required: true; required: true;
@ -54,6 +84,10 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
type: PropType<(indice: number, evento: MouseEvent) => void>; type: PropType<(indice: number, evento: MouseEvent) => void>;
required: true; required: true;
}; };
alternarLinhaExpandida: {
type: PropType<(indice: number) => void>;
required: true;
};
}>> & Readonly<{}>, {}, {}, { }>> & Readonly<{}>, {}, {}, {
EliTabelaCelula: import("vue").DefineComponent<import("vue").ExtractPropTypes<{ EliTabelaCelula: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
celula: { celula: {
@ -75,7 +109,49 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
required: true; required: true;
}; };
}>> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>; }>> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
EliTabelaDetalhesLinha: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
linha: {
type: PropType<unknown>;
required: true;
};
colunasInvisiveis: {
type: PropType<Array<EliColuna<any>>>;
required: true;
};
}>, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
linha: {
type: PropType<unknown>;
required: true;
};
colunasInvisiveis: {
type: PropType<Array<EliColuna<any>>>;
required: true;
};
}>> & Readonly<{}>, {}, {}, {
EliTabelaCelula: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
celula: {
type: PropType<import("./types-eli-tabela").ComponenteCelula>;
required: true;
};
}>, {
Componente: import("vue").ComputedRef<import("vue").Component>;
dadosParaComponente: import("vue").ComputedRef<{
texto: string;
acao?: () => void;
} | {
numero: number;
acao?: () => void;
}>;
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
celula: {
type: PropType<import("./types-eli-tabela").ComponenteCelula>;
required: true;
};
}>> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
MoreVertical: import("vue").FunctionalComponent<import("lucide-vue-next").LucideProps, {}, any, {}>; MoreVertical: import("vue").FunctionalComponent<import("lucide-vue-next").LucideProps, {}, any, {}>;
ChevronRight: import("vue").FunctionalComponent<import("lucide-vue-next").LucideProps, {}, any, {}>;
ChevronDown: import("vue").FunctionalComponent<import("lucide-vue-next").LucideProps, {}, any, {}>;
}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>; }, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
declare const _default: typeof __VLS_export; declare const _default: typeof __VLS_export;
export default _default; export default _default;

View file

@ -0,0 +1,44 @@
import { PropType } from "vue";
import type { EliColuna } from "./types-eli-tabela";
declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
linha: {
type: PropType<unknown>;
required: true;
};
colunasInvisiveis: {
type: PropType<Array<EliColuna<any>>>;
required: true;
};
}>, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
linha: {
type: PropType<unknown>;
required: true;
};
colunasInvisiveis: {
type: PropType<Array<EliColuna<any>>>;
required: true;
};
}>> & Readonly<{}>, {}, {}, {
EliTabelaCelula: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
celula: {
type: PropType<import("./types-eli-tabela").ComponenteCelula>;
required: true;
};
}>, {
Componente: import("vue").ComputedRef<import("vue").Component>;
dadosParaComponente: import("vue").ComputedRef<{
texto: string;
acao?: () => void;
} | {
numero: number;
acao?: () => void;
}>;
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
celula: {
type: PropType<import("./types-eli-tabela").ComponenteCelula>;
required: true;
};
}>> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
declare const _default: typeof __VLS_export;
export default _default;

View file

@ -9,6 +9,10 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
type: BooleanConstructor; type: BooleanConstructor;
required: true; required: true;
}; };
temColunasInvisiveis: {
type: BooleanConstructor;
required: true;
};
colunaOrdenacao: { colunaOrdenacao: {
type: PropType<string | null>; type: PropType<string | null>;
required: true; required: true;
@ -34,6 +38,10 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
type: BooleanConstructor; type: BooleanConstructor;
required: true; required: true;
}; };
temColunasInvisiveis: {
type: BooleanConstructor;
required: true;
};
colunaOrdenacao: { colunaOrdenacao: {
type: PropType<string | null>; type: PropType<string | null>;
required: true; required: true;

View file

@ -311,3 +311,81 @@
flex: 1; flex: 1;
text-align: left; text-align: left;
} }
/* =========================
* Expander (colunas invisíveis)
* ========================= */
.eli-tabela__th--expander,
.eli-tabela__td--expander {
width: 42px;
padding: 6px 8px;
text-align: center;
vertical-align: middle;
}
.eli-tabela__expander-botao {
display: inline-flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
border-radius: 9999px;
border: none;
background: transparent;
color: rgba(15, 23, 42, 0.72);
cursor: pointer;
transition: background-color 0.2s ease, color 0.2s ease;
}
.eli-tabela__expander-botao:hover,
.eli-tabela__expander-botao:focus-visible {
background-color: rgba(15, 23, 42, 0.08);
color: rgba(15, 23, 42, 0.95);
}
.eli-tabela__expander-botao:focus-visible {
outline: 2px solid rgba(37, 99, 235, 0.45);
outline-offset: 2px;
}
.eli-tabela__expander-botao--ativo {
background-color: rgba(15, 23, 42, 0.06);
color: rgba(15, 23, 42, 0.95);
}
.eli-tabela__td--detalhes {
padding: 12px;
}
.eli-tabela__tr--detalhes .eli-tabela__td {
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
}
.eli-tabela__detalhes {
display: grid;
gap: 10px;
padding-left: 4px;
}
.eli-tabela__detalhe {
display: grid;
grid-template-columns: 180px 1fr;
gap: 10px;
align-items: start;
}
.eli-tabela__detalhe-rotulo {
font-weight: 600;
color: rgba(15, 23, 42, 0.85);
}
.eli-tabela__detalhe-valor {
min-width: 0;
}
@media (max-width: 720px) {
.eli-tabela__detalhe {
grid-template-columns: 1fr;
}
}

View file

@ -31,6 +31,7 @@
<EliTabelaHead <EliTabelaHead
:colunas="colunasEfetivas" :colunas="colunasEfetivas"
:temAcoes="temAcoes" :temAcoes="temAcoes"
:temColunasInvisiveis="temColunasInvisiveis"
:colunaOrdenacao="colunaOrdenacao" :colunaOrdenacao="colunaOrdenacao"
:direcaoOrdenacao="direcaoOrdenacao" :direcaoOrdenacao="direcaoOrdenacao"
@alternar-ordenacao="alternarOrdenacao" @alternar-ordenacao="alternarOrdenacao"
@ -38,11 +39,15 @@
<EliTabelaBody <EliTabelaBody
:colunas="colunasEfetivas" :colunas="colunasEfetivas"
:colunasInvisiveis="colunasInvisiveisEfetivas"
:temColunasInvisiveis="temColunasInvisiveis"
:linhasExpandidas="linhasExpandidas"
:linhas="linhas" :linhas="linhas"
:temAcoes="temAcoes" :temAcoes="temAcoes"
:menuAberto="menuAberto" :menuAberto="menuAberto"
:possuiAcoes="possuiAcoes" :possuiAcoes="possuiAcoes"
:toggleMenu="toggleMenu" :toggleMenu="toggleMenu"
:alternarLinhaExpandida="alternarLinhaExpandida"
/> />
</table> </table>
@ -93,6 +98,7 @@ import EliTabelaBody from "./EliTabelaBody.vue";
import EliTabelaMenuAcoes from "./EliTabelaMenuAcoes.vue"; import EliTabelaMenuAcoes from "./EliTabelaMenuAcoes.vue";
import EliTabelaPaginacao from "./EliTabelaPaginacao.vue"; import EliTabelaPaginacao from "./EliTabelaPaginacao.vue";
import EliTabelaModalColunas from "./EliTabelaModalColunas.vue"; import EliTabelaModalColunas from "./EliTabelaModalColunas.vue";
import type { EliColuna } from "./types-eli-tabela";
/** Tipos da configuração/contrato da tabela */ /** Tipos da configuração/contrato da tabela */
import type { EliTabelaConsulta } from "./types-eli-tabela"; import type { EliTabelaConsulta } from "./types-eli-tabela";
import { import {
@ -158,8 +164,37 @@ export default defineComponent({
carregarConfigColunas(props.tabela.nome) carregarConfigColunas(props.tabela.nome)
); );
/** Linhas expandidas (para exibir colunas invisíveis) */
const linhasExpandidas = ref<Record<number, boolean>>({});
const rotulosColunas = computed(() => props.tabela.colunas.map((c) => c.rotulo)); const rotulosColunas = computed(() => props.tabela.colunas.map((c) => c.rotulo));
const colunasInvisiveisEfetivas = computed(() => {
const colunas = props.tabela.colunas as Array<EliColuna<any>>;
const invisiveisSet = new Set(configColunas.value.invisiveis ?? []);
const base = colunas.filter((c) => invisiveisSet.has(c.rotulo));
// ordenação: usa a lista salva de invisíveis (se existir), senão segue ordem original
const ordemSalva = configColunas.value.invisiveis ?? [];
const mapa = new Map<string, EliColuna<any>>();
for (const c of base) {
if (!mapa.has(c.rotulo)) mapa.set(c.rotulo, c);
}
const ordenadas: Array<EliColuna<any>> = [];
for (const r of ordemSalva) {
const c = mapa.get(r);
if (c) ordenadas.push(c);
}
for (const c of base) {
if (!ordenadas.includes(c)) ordenadas.push(c);
}
return ordenadas;
});
const temColunasInvisiveis = computed(() => colunasInvisiveisEfetivas.value.length > 0);
const colunasEfetivas = computed(() => { const colunasEfetivas = computed(() => {
const colunas = props.tabela.colunas; const colunas = props.tabela.colunas;
const todosRotulos = rotulosColunas.value; const todosRotulos = rotulosColunas.value;
@ -202,6 +237,17 @@ export default defineComponent({
configColunas.value = cfg; configColunas.value = cfg;
salvarConfigColunas(props.tabela.nome, cfg); salvarConfigColunas(props.tabela.nome, cfg);
modalColunasAberto.value = false; modalColunasAberto.value = false;
// ao mudar colunas, fecha detalhes expandidos
linhasExpandidas.value = {};
}
function alternarLinhaExpandida(indice: number) {
const atual = Boolean(linhasExpandidas.value[indice]);
linhasExpandidas.value = {
...linhasExpandidas.value,
[indice]: !atual,
};
} }
/** Registros por consulta (normaliza para inteiro positivo) */ /** Registros por consulta (normaliza para inteiro positivo) */
@ -383,6 +429,7 @@ export default defineComponent({
erro.value = null; erro.value = null;
acoesVisiveis.value = []; acoesVisiveis.value = [];
menuAberto.value = null; menuAberto.value = null;
linhasExpandidas.value = {};
const limite = Math.max(1, registrosPorConsulta.value); const limite = Math.max(1, registrosPorConsulta.value);
const offset = (paginaAtual.value - 1) * limite; const offset = (paginaAtual.value - 1) * limite;
@ -523,6 +570,7 @@ export default defineComponent({
valorBusca.value = ""; valorBusca.value = "";
modalColunasAberto.value = false; modalColunasAberto.value = false;
configColunas.value = carregarConfigColunas(props.tabela.nome); configColunas.value = carregarConfigColunas(props.tabela.nome);
linhasExpandidas.value = {};
if (paginaAtual.value !== 1) { if (paginaAtual.value !== 1) {
paginaAtual.value = 1; paginaAtual.value = 1;
} else { } else {
@ -546,6 +594,7 @@ export default defineComponent({
/** Watch: mudança nas linhas fecha o menu aberto */ /** Watch: mudança nas linhas fecha o menu aberto */
watch(linhas, () => { watch(linhas, () => {
menuAberto.value = null; menuAberto.value = null;
linhasExpandidas.value = {};
}); });
/** Exposição para o template (state, computed, helpers e actions) */ /** Exposição para o template (state, computed, helpers e actions) */
@ -574,10 +623,16 @@ export default defineComponent({
modalColunasAberto, modalColunasAberto,
configColunas, configColunas,
temColunasInvisiveis,
colunasInvisiveisEfetivas,
linhasExpandidas,
abrirModalColunas, abrirModalColunas,
fecharModalColunas, fecharModalColunas,
salvarModalColunas, salvarModalColunas,
alternarLinhaExpandida,
// actions // actions
alternarOrdenacao, alternarOrdenacao,
atualizarBusca, atualizarBusca,

View file

@ -1,11 +1,30 @@
<template> <template>
<tbody class="eli-tabela__tbody"> <tbody class="eli-tabela__tbody">
<template v-for="(linha, i) in linhas" :key="`grp-${i}`">
<tr <tr
v-for="(linha, i) in linhas"
:key="`tr-${i}`"
class="eli-tabela__tr" class="eli-tabela__tr"
:class="[i % 2 === 1 ? 'eli-tabela__tr--zebra' : undefined]" :class="[i % 2 === 1 ? 'eli-tabela__tr--zebra' : undefined]"
> >
<td v-if="temColunasInvisiveis" class="eli-tabela__td eli-tabela__td--expander" :key="`td-${i}-exp`">
<button
type="button"
class="eli-tabela__expander-botao"
:class="[linhasExpandidas?.[i] ? 'eli-tabela__expander-botao--ativo' : undefined]"
:aria-expanded="linhasExpandidas?.[i] ? 'true' : 'false'"
:aria-label="linhasExpandidas?.[i] ? 'Ocultar colunas ocultas' : 'Mostrar colunas ocultas'"
:title="linhasExpandidas?.[i] ? 'Ocultar detalhes' : 'Mostrar detalhes'"
@click.stop="alternarLinhaExpandida(i)"
>
<component
:is="linhasExpandidas?.[i] ? ChevronDown : ChevronRight"
class="eli-tabela__expander-icone"
:size="16"
:stroke-width="2"
aria-hidden="true"
/>
</button>
</td>
<td <td
v-for="(coluna, j) in colunas" v-for="(coluna, j) in colunas"
:key="`td-${i}-${j}`" :key="`td-${i}-${j}`"
@ -50,26 +69,56 @@
</div> </div>
</td> </td>
</tr> </tr>
<tr
v-if="temColunasInvisiveis && Boolean(linhasExpandidas?.[i])"
class="eli-tabela__tr eli-tabela__tr--detalhes"
:class="[i % 2 === 1 ? 'eli-tabela__tr--zebra' : undefined]"
>
<td
class="eli-tabela__td eli-tabela__td--detalhes"
:colspan="(temColunasInvisiveis ? 1 : 0) + colunas.length + (temAcoes ? 1 : 0)"
>
<EliTabelaDetalhesLinha :linha="linha" :colunasInvisiveis="colunasInvisiveis" />
</td>
</tr>
</template>
</tbody> </tbody>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, PropType } from "vue"; import { defineComponent, PropType } from "vue";
import { MoreVertical } from "lucide-vue-next"; import { ChevronDown, ChevronRight, MoreVertical } from "lucide-vue-next";
import EliTabelaCelula from "./celulas/EliTabelaCelula.vue"; import EliTabelaCelula from "./celulas/EliTabelaCelula.vue";
import EliTabelaDetalhesLinha from "./EliTabelaDetalhesLinha.vue";
import type { EliColuna } from "./types-eli-tabela"; import type { EliColuna } from "./types-eli-tabela";
export default defineComponent({ export default defineComponent({
name: "EliTabelaBody", name: "EliTabelaBody",
components: { components: {
EliTabelaCelula, EliTabelaCelula,
EliTabelaDetalhesLinha,
MoreVertical, MoreVertical,
ChevronRight,
ChevronDown,
}, },
props: { props: {
colunas: { colunas: {
type: Array as PropType<Array<EliColuna<any>>>, type: Array as PropType<Array<EliColuna<any>>>,
required: true, required: true,
}, },
colunasInvisiveis: {
type: Array as PropType<Array<EliColuna<any>>>,
required: true,
},
temColunasInvisiveis: {
type: Boolean,
required: true,
},
linhasExpandidas: {
type: Object as PropType<Record<number, boolean>>,
required: true,
},
linhas: { linhas: {
type: Array as PropType<Array<unknown>>, type: Array as PropType<Array<unknown>>,
required: true, required: true,
@ -90,6 +139,10 @@ export default defineComponent({
type: Function as PropType<(indice: number, evento: MouseEvent) => void>, type: Function as PropType<(indice: number, evento: MouseEvent) => void>,
required: true, required: true,
}, },
alternarLinhaExpandida: {
type: Function as PropType<(indice: number) => void>,
required: true,
},
}, },
setup() { setup() {
function obterClasseAlinhamento(alinhamento?: string) { function obterClasseAlinhamento(alinhamento?: string) {
@ -121,6 +174,8 @@ export default defineComponent({
} }
return { return {
ChevronRight,
ChevronDown,
obterClasseAlinhamento, obterClasseAlinhamento,
obterMaxWidth, obterMaxWidth,
obterTooltipCelula, obterTooltipCelula,

View file

@ -0,0 +1,35 @@
<template>
<div class="eli-tabela__detalhes">
<div v-for="(coluna, idx) in colunasInvisiveis" :key="`det-${idx}-${coluna.rotulo}`" class="eli-tabela__detalhe">
<div class="eli-tabela__detalhe-rotulo">{{ coluna.rotulo }}</div>
<div class="eli-tabela__detalhe-valor">
<EliTabelaCelula :celula="(coluna.celula(linha as never) as never)" />
</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, PropType } from "vue";
import EliTabelaCelula from "./celulas/EliTabelaCelula.vue";
import type { EliColuna } from "./types-eli-tabela";
export default defineComponent({
name: "EliTabelaDetalhesLinha",
components: { EliTabelaCelula },
props: {
linha: {
type: null as unknown as PropType<unknown>,
required: true,
},
colunasInvisiveis: {
type: Array as PropType<Array<EliColuna<any>>>,
required: true,
},
},
});
</script>
<style scoped>
/* estilos base ficam no EliTabela.css (global do componente) */
</style>

View file

@ -1,6 +1,8 @@
<template> <template>
<thead class="eli-tabela__thead"> <thead class="eli-tabela__thead">
<tr class="eli-tabela__tr eli-tabela__tr--header"> <tr class="eli-tabela__tr eli-tabela__tr--header">
<th v-if="temColunasInvisiveis" class="eli-tabela__th eli-tabela__th--expander" scope="col"></th>
<th <th
v-for="(coluna, idx) in colunas" v-for="(coluna, idx) in colunas"
:key="`th-${idx}`" :key="`th-${idx}`"
@ -66,6 +68,10 @@ export default defineComponent({
type: Boolean, type: Boolean,
required: true, required: true,
}, },
temColunasInvisiveis: {
type: Boolean,
required: true,
},
colunaOrdenacao: { colunaOrdenacao: {
type: String as PropType<string | null>, type: String as PropType<string | null>,
required: true, required: true,