feat(VnTable): add visibleColumns #536

Merged
alexm merged 5 commits from 6825-visibleColumns into dev 2024-07-08 12:07:43 +00:00
2 changed files with 205 additions and 17 deletions

View File

@ -13,6 +13,7 @@ import VnLv from 'components/ui/VnLv.vue';
import VnTableColumn from 'components/VnTable/VnColumn.vue'; import VnTableColumn from 'components/VnTable/VnColumn.vue';
import VnTableFilter from 'components/VnTable/VnFilter.vue'; import VnTableFilter from 'components/VnTable/VnFilter.vue';
import VnTableChip from 'components/VnTable/VnChip.vue'; import VnTableChip from 'components/VnTable/VnChip.vue';
import TableVisibleColumns from 'src/components/VnTable/VnVisibleColumn.vue';
const $props = defineProps({ const $props = defineProps({
columns: { columns: {
@ -67,6 +68,10 @@ const $props = defineProps({
type: Object, type: Object,
default: () => ({ card: false, table: false }), default: () => ({ card: false, table: false }),
}, },
tableCode: {
type: String,
default: null,
},
table: { table: {
type: Object, type: Object,
default: () => ({}), default: () => ({}),
@ -119,6 +124,14 @@ watch(
(val) => setUserParams(val) (val) => setUserParams(val)
); );
const rowClickFunction = computed(() => {
if ($props.rowClick) return $props.rowClick;
if ($props.redirect) return ({ id }) => redirectFn(id);
return () => {};
});
const isTableMode = computed(() => mode.value == TABLE_MODE);
function setUserParams(watchedParams) { function setUserParams(watchedParams) {
if (!watchedParams) return; if (!watchedParams) return;
@ -162,12 +175,6 @@ function splitColumns(columns) {
} }
} }
const rowClickFunction = computed(() => {
if ($props.rowClick) return $props.rowClick;
if ($props.redirect) return ({ id }) => redirectFn(id);
return () => {};
});
function redirectFn(id) { function redirectFn(id) {
router.push({ path: `/${$props.redirect}/${id}` }); router.push({ path: `/${$props.redirect}/${id}` });
} }
@ -240,7 +247,7 @@ defineExpose({
:limit="20" :limit="20"
ref="CrudModelRef" ref="CrudModelRef"
:search-url="searchUrl" :search-url="searchUrl"
:disable-infinite-scroll="mode == TABLE_MODE" :disable-infinite-scroll="isTableMode"
@save-changes="reload" @save-changes="reload"
:has-sub-toolbar="$attrs['hasSubToolbar'] ?? isEditable" :has-sub-toolbar="$attrs['hasSubToolbar'] ?? isEditable"
> >
@ -258,11 +265,11 @@ defineExpose({
:columns="splittedColumns.columns" :columns="splittedColumns.columns"
:rows="rows" :rows="rows"
v-model:selected="selected" v-model:selected="selected"
:grid="mode != TABLE_MODE" :grid="!isTableMode"
table-header-class="bg-header" table-header-class="bg-header"
card-container-class="grid-three" card-container-class="grid-three"
flat flat
:style="mode == TABLE_MODE && 'max-height: 90vh'" :style="isTableMode && 'max-height: 90vh'"
virtual-scroll virtual-scroll
@virtual-scroll=" @virtual-scroll="
(event) => (event) =>
@ -276,13 +283,12 @@ defineExpose({
<slot name="top-left"></slot> <slot name="top-left"></slot>
</template> </template>
<template #top-right> <template #top-right>
<!-- <QBtn <TableVisibleColumns
icon="visibility" v-if="isTableMode"
title="asd" v-model="splittedColumns.columns"
class="bg-vn-section-color q-mr-md" :table-code="tableCode ?? route.name"
dense class="q-mr-md"
v-if="mode == 'table'" />
/> -->
<QBtnToggle <QBtnToggle
v-model="mode" v-model="mode"
toggle-color="primary" toggle-color="primary"
@ -302,7 +308,7 @@ defineExpose({
<QTh <QTh
auto-width auto-width
style="min-width: 100px" style="min-width: 100px"
v-if="$props.columnSearch" v-if="$props.columnSearch && col.visible"
> >
<VnTableFilter <VnTableFilter
:column="col" :column="col"
@ -334,6 +340,7 @@ defineExpose({
auto-width auto-width
class="no-margin q-px-xs" class="no-margin q-px-xs"
:class="getColAlign(col)" :class="getColAlign(col)"
v-if="col.visible"
> >
<slot :name="`column-${col.name}`" :col="col" :row="row"> <slot :name="`column-${col.name}`" :col="col" :row="row">
<VnTableColumn <VnTableColumn

View File

@ -0,0 +1,181 @@
<script setup>
import { useI18n } from 'vue-i18n';
import { ref, computed, onMounted } from 'vue';
import { useState } from 'src/composables/useState';
import axios from 'axios';
import useNotify from 'src/composables/useNotify.js';
const columns = defineModel({ type: Object, default: [] });
const $props = defineProps({
tableCode: {
type: String,
default: '',
},
});
const { notify } = useNotify();
const { t } = useI18n();
const state = useState();
const user = state.getUser();
const popupProxyRef = ref();
const initialUserConfigViewData = ref();
const localColumns = ref([]);
const areAllChecksMarked = computed(() => {
return localColumns.value.every((col) => col.visible);
});
function setUserConfigViewData(data, isLocal) {
if (!data) return;
// Importante: El name de las columnas de la tabla debe conincidir con el name de las variables que devuelve la view config
if (!isLocal) localColumns.value = [];
for (let column of columns.value) {
const { label, name } = column;
column.visible = data[name] ?? true;
if (!isLocal) localColumns.value.push({ name, label, visible: column.visible });
}
}
function toggleMarkAll(val) {
localColumns.value.forEach((col) => (col.visible = val));
}
async function getConfig(url, filter) {
const response = await axios.get(url, {
params: { filter: filter },
});
return response.data && response.data.length > 0 ? response.data[0] : null;
}
async function fetchViewConfigData() {
try {
const defaultFilter = {
where: { tableCode: $props.tableCode },
};
const userConfig = await getConfig('UserConfigViews', {
where: {
...defaultFilter.where,
...{ userFk: user.id },
},
});
if (userConfig) {
initialUserConfigViewData.value = userConfig;
setUserConfigViewData(userConfig.configuration);
return;
}
const defaultConfig = await getConfig('DefaultViewConfigs', defaultFilter);
if (defaultConfig) {
setUserConfigViewData(defaultConfig.columns);
return;
}
} catch (err) {
console.err('Error fetching config view data', err);
}
}
async function saveConfig() {
const configuration = {};
for (const { name, visible } of localColumns.value)
configuration[name] = visible ?? true;
setUserConfigViewData(configuration, true);
if (!$props.tableCode) return popupProxyRef.value.hide();
try {
const params = {};
// Si existe una view config del usuario hacemos un update si no la creamos
if (initialUserConfigViewData.value) {
params.updates = [
{
data: {
configuration,
},
where: {
id: initialUserConfigViewData.value.id,
},
},
];
} else {
params.creates = [
{
userFk: user.value.id,
tableCode: $props.tableCode,
tableConfig: $props.tableCode,
configuration,
},
];
}
const response = await axios.post('UserConfigViews/crud', params);
if (response.data && response.data[0]) {
initialUserConfigViewData.value = response.data[0];
}
notify('globals.dataSaved', 'positive');
popupProxyRef.value.hide();
} catch (err) {
console.error('Error saving user view config', err);
notify('errors.writeRequest', 'negative');
}
}
onMounted(async () => {
setUserConfigViewData({});
await fetchViewConfigData();
});
</script>
<template>
<QBtn icon="vn:visible_columns" class="bg-vn-section-color q-mr-md" dense>
<QPopupProxy ref="popupProxyRef">
<QCard class="column q-pa-md">
<QIcon name="info" size="sm" class="info-icon">
<QTooltip>{{ t('Check the columns you want to see') }}</QTooltip>
</QIcon>
<span class="text-body1 q-mb-sm">{{ t('Visible columns') }}</span>
<QCheckbox
:label="t('Tick all')"
:model-value="areAllChecksMarked"
@update:model-value="toggleMarkAll($event)"
class="q-mb-sm"
/>
<div v-if="columns.length > 0" class="checks-layout">
<QCheckbox
v-for="col in localColumns"
:key="col.name"
:label="col.label"
v-model="col.visible"
/>
</div>
<QBtn
class="full-width q-mt-md"
color="primary"
@click="saveConfig()"
:label="t('globals.save')"
/>
</QCard>
</QPopupProxy>
<QTooltip>{{ t('Visible columns') }}</QTooltip>
</QBtn>
</template>
<style lang="scss" scoped>
.info-icon {
position: absolute;
top: 20px;
right: 20px;
}
.checks-layout {
display: grid;
grid-template-columns: repeat(3, 200px);
}
</style>
<i18n>
es:
Check the columns you want to see: Marca las columnas que quieres ver
Visible columns: Columnas visibles
Tick all: Marcar todas
</i18n>