refactor: refs #8604 changed TicketFuture to Vntable and modified filter
gitea/salix-front/pipeline/pr-dev This commit looks good Details

This commit is contained in:
Jon Elias 2025-02-14 10:53:46 +01:00
parent 6f2790f6f8
commit 012da1edfb
3 changed files with 182 additions and 305 deletions

View File

@ -1045,7 +1045,7 @@ es:
.grid-three { .grid-three {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, max-content)); grid-template-columns: repeat(auto-fit, minmax(300px, max-content));
max-width: 100%; width: 100%;
grid-gap: 20px; grid-gap: 20px;
margin: 0 auto; margin: 0 auto;
} }

View File

@ -1,23 +1,21 @@
<script setup> <script setup>
import { onMounted, ref, computed, reactive } from 'vue'; import { ref, computed, reactive, watch } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import FetchData from 'components/FetchData.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
import TicketDescriptorProxy from 'src/pages/Ticket/Card/TicketDescriptorProxy.vue'; import TicketDescriptorProxy from 'src/pages/Ticket/Card/TicketDescriptorProxy.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import VnSearchbar from 'src/components/ui/VnSearchbar.vue'; import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
import RightMenu from 'src/components/common/RightMenu.vue'; import RightMenu from 'src/components/common/RightMenu.vue';
import VnTable from 'src/components/VnTable/VnTable.vue';
import TicketFutureFilter from './TicketFutureFilter.vue'; import TicketFutureFilter from './TicketFutureFilter.vue';
import { dashIfEmpty, toCurrency } from 'src/filters'; import { dashIfEmpty, toCurrency } from 'src/filters';
import { useVnConfirm } from 'composables/useVnConfirm'; import { useVnConfirm } from 'composables/useVnConfirm';
import { useArrayData } from 'composables/useArrayData';
import { getDateQBadgeColor } from 'src/composables/getDateQBadgeColor.js'; import { getDateQBadgeColor } from 'src/composables/getDateQBadgeColor.js';
import useNotify from 'src/composables/useNotify.js'; import useNotify from 'src/composables/useNotify.js';
import { useState } from 'src/composables/useState'; import { useState } from 'src/composables/useState';
import { toDateTimeFormat } from 'src/filters/date.js'; import { toDateTimeFormat } from 'src/filters/date.js';
import { toDateFormat } from 'src/filters/date.js';
import axios from 'axios'; import axios from 'axios';
const state = useState(); const state = useState();
@ -26,214 +24,123 @@ const { openConfirmationModal } = useVnConfirm();
const { notify } = useNotify(); const { notify } = useNotify();
const user = state.getUser(); const user = state.getUser();
const itemPackingTypesOptions = ref([]);
const selectedTickets = ref([]); const selectedTickets = ref([]);
const vnTableRef = ref({});
const exprBuilder = (param, value) => { const originElRef = ref(null);
switch (param) { const destinationElRef = ref(null);
case 'id':
return { id: value };
case 'futureId':
return { futureId: value };
case 'liters':
return { liters: value };
case 'lines':
return { lines: value };
case 'iptColFilter':
return { ipt: { like: `%${value}%` } };
case 'futureIptColFilter':
return { futureIpt: { like: `%${value}%` } };
case 'totalWithVat':
return { totalWithVat: value };
}
};
const userParams = reactive({ const userParams = reactive({
futureScopeDays: Date.vnNew().toISOString(), futureScopeDays: Date.vnNew().toISOString(),
originScopeDays: Date.vnNew().toISOString(), originScopeDays: Date.vnNew().toISOString(),
warehouseFk: user.value.warehouseFk, warehouseFk: user.value.warehouseFk,
}); });
const arrayData = useArrayData('FutureTickets', {
url: 'Tickets/getTicketsFuture',
userParams: userParams,
exprBuilder: exprBuilder,
});
const { store } = arrayData;
const params = reactive({
futureScopeDays: Date.vnNew(),
originScopeDays: Date.vnNew(),
warehouseFk: user.value.warehouseFk,
});
const applyColumnFilter = async (col) => {
const paramKey = col.columnFilter?.filterParamKey || col.field;
params[paramKey] = col.columnFilter.filterValue;
await arrayData.addFilter({ params });
};
const getInputEvents = (col) => {
return col.columnFilter.type === 'select'
? { 'update:modelValue': () => applyColumnFilter(col) }
: {
'keyup.enter': () => applyColumnFilter(col),
};
};
const tickets = computed(() => store.data);
const ticketColumns = computed(() => [ const ticketColumns = computed(() => [
{ {
label: t('futureTickets.problems'), label: '',
name: 'problems', name: 'problems',
headerClass: 'horizontal-separator',
align: 'left', align: 'left',
columnFilter: null, columnFilter: false,
}, },
{ {
label: t('advanceTickets.ticketId'), label: t('advanceTickets.ticketId'),
name: 'ticketId', name: 'id',
align: 'center', align: 'center',
sortable: true, headerClass: 'horizontal-separator',
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
filterParamKey: 'id',
event: getInputEvents,
attrs: {
dense: true,
},
},
}, },
{ {
label: t('futureTickets.shipped'), label: t('futureTickets.shipped'),
name: 'shipped', name: 'shipped',
align: 'left', align: 'left',
sortable: true, columnFilter: false,
columnFilter: null, headerClass: 'horizontal-separator',
}, },
{ {
align: 'left',
label: t('advanceTickets.ipt'), label: t('advanceTickets.ipt'),
name: 'ipt', name: 'ipt',
field: 'ipt',
align: 'left',
sortable: true,
columnFilter: { columnFilter: {
component: VnSelect, component: 'select',
filterParamKey: 'iptColFilter',
type: 'select',
filterValue: null,
event: getInputEvents,
attrs: { attrs: {
options: itemPackingTypesOptions.value, url: 'itemPackingTypes',
'option-value': 'code', fields: ['code', 'description'],
'option-label': 'description', where: { isActive: true },
dense: true, optionValue: 'code',
optionLabel: 'description',
inWhere: false,
}, },
}, },
format: (val) => dashIfEmpty(val), format: (row, dashIfEmpty) => dashIfEmpty(row.ipt),
headerClass: 'horizontal-separator',
}, },
{ {
label: t('ticketList.state'), label: t('ticketList.state'),
name: 'state', name: 'state',
align: 'left', align: 'left',
sortable: true, columnFilter: false,
columnFilter: null, headerClass: 'horizontal-separator',
}, },
{ {
label: t('advanceTickets.liters'), label: t('advanceTickets.liters'),
name: 'liters', name: 'liters',
field: 'liters',
align: 'left', align: 'left',
sortable: true, headerClass: 'horizontal-separator',
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
}, },
{ {
label: t('advanceTickets.import'), label: t('advanceTickets.import'),
field: 'import',
name: 'import', name: 'import',
align: 'left', align: 'left',
sortable: true, headerClass: 'horizontal-separator',
columnFilter: false,
format: (row) => toCurrency(row.totalWithVat),
}, },
{ {
label: t('futureTickets.availableLines'), label: t('futureTickets.availableLines'),
name: 'lines', name: 'lines',
field: 'lines', field: 'lines',
align: 'center', align: 'center',
sortable: true, headerClass: 'horizontal-separator',
columnFilter: { format: (row, dashIfEmpty) => dashIfEmpty(row.lines),
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
format: (val) => dashIfEmpty(val),
}, },
{ {
label: t('advanceTickets.futureId'), label: t('advanceTickets.futureId'),
name: 'futureId', name: 'futureId',
align: 'left', align: 'center',
sortable: true, headerClass: 'vertical-separator horizontal-separator',
columnFilter: { columnClass: 'vertical-separator',
component: VnInput,
type: 'text',
filterValue: null,
filterParamKey: 'futureId',
event: getInputEvents,
attrs: {
dense: true,
},
},
}, },
{ {
label: t('futureTickets.futureShipped'), label: t('futureTickets.futureShipped'),
name: 'futureShipped', name: 'futureShipped',
align: 'left', align: 'left',
sortable: true, headerClass: 'horizontal-separator',
columnFilter: null, columnFilter: false,
format: (val) => dashIfEmpty(val), format: (row) => toDateTimeFormat(row.futureShipped),
}, },
{ {
align: 'left',
label: t('advanceTickets.futureIpt'), label: t('advanceTickets.futureIpt'),
name: 'futureIpt', name: 'futureIpt',
field: 'futureIpt',
align: 'left',
sortable: true,
columnFilter: { columnFilter: {
component: VnSelect, component: 'select',
filterParamKey: 'futureIptColFilter',
type: 'select',
filterValue: null,
event: getInputEvents,
attrs: { attrs: {
options: itemPackingTypesOptions.value, url: 'itemPackingTypes',
'option-value': 'code', fields: ['code', 'description'],
'option-label': 'description', where: { isActive: true },
dense: true, optionValue: 'code',
optionLabel: 'description',
}, },
}, },
format: (val) => dashIfEmpty(val), headerClass: 'horizontal-separator',
format: (row, dashIfEmpty) => dashIfEmpty(row.futureIpt),
}, },
{ {
label: t('advanceTickets.futureState'), label: t('advanceTickets.futureState'),
name: 'futureState', name: 'futureState',
align: 'right', align: 'right',
sortable: true, headerClass: 'horizontal-separator',
columnFilter: null, columnFilter: false,
format: (val) => dashIfEmpty(val), format: (row, dashIfEmpty) => dashIfEmpty(row.futureState),
}, },
]); ]);
@ -258,26 +165,59 @@ const moveTicketsFuture = async () => {
await axios.post('Tickets/merge', params); await axios.post('Tickets/merge', params);
notify(t('advanceTickets.moveTicketSuccess'), 'positive'); notify(t('advanceTickets.moveTicketSuccess'), 'positive');
selectedTickets.value = []; selectedTickets.value = [];
arrayData.fetch({ append: false }); vnTableRef.value.reload();
}; };
onMounted(async () => {
await arrayData.fetch({ append: false }); watch(
}); () => vnTableRef.value.tableRef?.$el,
($el) => {
if (!$el) return;
const head = $el.querySelector('thead');
const firstRow = $el.querySelector('thead > tr');
const newRow = document.createElement('tr');
destinationElRef.value = document.createElement('th');
originElRef.value = document.createElement('th');
newRow.classList.add('bg-header');
destinationElRef.value.classList.add('text-uppercase', 'color-vn-label');
originElRef.value.classList.add('text-uppercase', 'color-vn-label');
destinationElRef.value.setAttribute('colspan', '7');
originElRef.value.setAttribute('colspan', '9');
destinationElRef.value.textContent = `${t(
'advanceTickets.destination',
)} ${toDateFormat(vnTableRef.value.params.dateToAdvance)}`;
originElRef.value.textContent = `${t('advanceTickets.origin')} ${toDateFormat(
vnTableRef.value.params.dateFuture,
)}`;
newRow.append(destinationElRef.value, originElRef.value);
head.insertBefore(newRow, firstRow);
},
{ once: true, inmmediate: true },
);
watch(
() => vnTableRef.value.params,
() => {
if (originElRef.value && destinationElRef.value) {
destinationElRef.value.textContent = `${t(
'advanceTickets.destination',
)} ${toDateFormat(vnTableRef.value.params.dateToAdvance)}`;
originElRef.value.textContent = `${t('advanceTickets.origin')} ${toDateFormat(
vnTableRef.value.params.dateFuture,
)}`;
}
},
{ deep: true },
);
</script> </script>
<template> <template>
<FetchData
url="itemPackingTypes"
:filter="{
fields: ['code', 'description'],
order: 'description ASC',
where: { isActive: true },
}"
auto-load
@on-fetch="(data) => (itemPackingTypesOptions = data)"
/>
<VnSearchbar <VnSearchbar
data-key="FutureTickets" data-key="futureTicket"
:label="t('Search ticket')" :label="t('Search ticket')"
:info="t('futureTickets.searchInfo')" :info="t('futureTickets.searchInfo')"
/> />
@ -293,7 +233,7 @@ onMounted(async () => {
t(`futureTickets.moveTicketDialogSubtitle`, { t(`futureTickets.moveTicketDialogSubtitle`, {
selectedTickets: selectedTickets.length, selectedTickets: selectedTickets.length,
}), }),
moveTicketsFuture moveTicketsFuture,
) )
" "
> >
@ -305,77 +245,29 @@ onMounted(async () => {
</VnSubToolbar> </VnSubToolbar>
<RightMenu> <RightMenu>
<template #right-panel> <template #right-panel>
<TicketFutureFilter data-key="FutureTickets" /> <TicketFutureFilter data-key="futureTickets" />
</template> </template>
</RightMenu> </RightMenu>
<QPage class="column items-center q-pa-md"> <QPage class="column items-center q-pa-md">
<QTable <VnTable
:rows="tickets" data-key="futureTickets"
ref="vnTableRef"
url="Tickets/getTicketsFuture"
search-url="futureTickets"
:user-params="userParams"
:limit="0"
:columns="ticketColumns" :columns="ticketColumns"
row-key="id" :table="{
selection="multiple" 'row-key': '$index',
selection: 'multiple',
}"
v-model:selected="selectedTickets" v-model:selected="selectedTickets"
:pagination="{ rowsPerPage: 0 }" :right-search="false"
:no-data-label="t('globals.noResults')" auto-load
style="max-width: 99%" :disable-option="{ card: true }"
> >
<template #header="props"> <template #column-problems="{ row }">
<QTr> <span class="q-gutter-x-xs">
<QTh class="horizontal-separator" />
<QTh
class="horizontal-separator text-uppercase color-vn-label"
colspan="8"
translate
>
{{ t('advanceTickets.origin') }}
</QTh>
<QTh
class="horizontal-separator text-uppercase color-vn-label"
colspan="4"
translate
>
{{ t('advanceTickets.destination') }}
</QTh>
</QTr>
<QTr>
<QTh>
<QCheckbox v-model="props.selected" />
</QTh>
<QTh
v-for="(col, index) in ticketColumns"
:key="index"
:class="{ 'vertical-separator': col.name === 'futureId' }"
>
{{ col.label }}
</QTh>
</QTr>
</template>
<template #top-row="{ cols }">
<QTr>
<QTd />
<QTd
v-for="(col, index) in cols"
:key="index"
style="max-width: 100px"
>
<component
:is="col.columnFilter.component"
v-if="col.columnFilter"
v-model="col.columnFilter.filterValue"
v-bind="col.columnFilter.attrs"
v-on="col.columnFilter.event(col)"
dense
/>
</QTd>
</QTr>
</template>
<template #header-cell-availableLines="{ col }">
<QTh class="vertical-separator">
{{ col.label }}
</QTh>
</template>
<template #body-cell-problems="{ row }">
<QTd class="q-gutter-x-xs">
<QIcon <QIcon
v-if="row.futureAgencyFk !== row.agencyFk && row.agencyFk" v-if="row.futureAgencyFk !== row.agencyFk && row.agencyFk"
color="primary" color="primary"
@ -465,99 +357,84 @@ onMounted(async () => {
{{ t('futureTickets.rounding') }} {{ t('futureTickets.rounding') }}
</QTooltip> </QTooltip>
</QIcon> </QIcon>
</QTd> </span>
</template> </template>
<template #body-cell-ticketId="{ row }"> <template #column-id="{ row }">
<QTd> <QBtn flat class="link">
<QBtn flat class="link"> {{ row.id }}
{{ row.id }} <TicketDescriptorProxy :id="row.id" />
<TicketDescriptorProxy :id="row.id" /> </QBtn>
</QBtn>
</QTd>
</template> </template>
<template #body-cell-shipped="{ row }"> <template #column-shipped="{ row }">
<QTd class="shipped"> <QBadge
<QBadge text-color="black"
text-color="black" :color="getDateQBadgeColor(row.shipped)"
:color="getDateQBadgeColor(row.shipped)" class="q-ma-none"
class="q-ma-none" >
> {{ toDateTimeFormat(row.shipped) }}
{{ toDateTimeFormat(row.shipped) }} </QBadge>
</QBadge>
</QTd>
</template> </template>
<template #body-cell-state="{ row }"> <template #column-state="{ row }">
<QTd> <QBadge
<QBadge v-if="row.state"
text-color="black" text-color="black"
:color="row.classColor" :color="row.classColor"
class="q-ma-none" class="q-ma-none"
dense dense
> >
{{ row.state }} {{ row.state }}
</QBadge> </QBadge>
</QTd> <span v-else> {{ dashIfEmpty(row.state) }}</span>
</template> </template>
<template #body-cell-import="{ row }"> <template #column-import="{ row }">
<QTd> <QBadge
<QBadge :text-color="
:text-color=" totalPriceColor(row.totalWithVat) === 'warning'
totalPriceColor(row.totalWithVat) === 'warning' ? 'black'
? 'black' : 'white'
: 'white' "
" :color="totalPriceColor(row.totalWithVat)"
:color="totalPriceColor(row.totalWithVat)" class="q-ma-none"
class="q-ma-none" dense
dense >
> {{ toCurrency(row.totalWithVat || 0) }}
{{ toCurrency(row.totalWithVat || 0) }} </QBadge>
</QBadge>
</QTd>
</template> </template>
<template #body-cell-futureId="{ row }"> <template #column-futureId="{ row }">
<QTd class="vertical-separator"> <QBtn flat class="link" dense>
<QBtn flat class="link" dense> {{ row.futureId }}
{{ row.futureId }} <TicketDescriptorProxy :id="row.futureId" />
<TicketDescriptorProxy :id="row.futureId" /> </QBtn>
</QBtn>
</QTd>
</template> </template>
<template #body-cell-futureShipped="{ row }"> <template #column-futureShipped="{ row }">
<QTd class="shipped"> <QBadge
<QBadge text-color="black"
text-color="black" :color="getDateQBadgeColor(row.futureShipped)"
:color="getDateQBadgeColor(row.futureShipped)" class="q-ma-none"
class="q-ma-none" >
> {{ toDateTimeFormat(row.futureShipped) }}
{{ toDateTimeFormat(row.futureShipped) }} </QBadge>
</QBadge>
</QTd>
</template> </template>
<template #body-cell-futureState="{ row }"> <template #column-futureState="{ row }">
<QTd> <QBadge
<QBadge text-color="black"
text-color="black" :color="row.futureClassColor"
:color="row.futureClassColor" class="q-ma-none"
class="q-ma-none" dense
dense >
> {{ row.futureState }}
{{ row.futureState }} </QBadge>
</QBadge>
</QTd>
</template> </template>
</QTable> </VnTable>
</QPage> </QPage>
</template> </template>
<style scoped lang="scss"> <style scoped lang="scss">
.shipped { :deep(.vertical-separator) {
min-width: 132px;
}
.vertical-separator {
border-left: 4px solid white !important; border-left: 4px solid white !important;
} }
.horizontal-separator { :deep(.horizontal-separator) {
border-bottom: 4px solid white !important; border-top: 4px solid white !important;
} }
</style> </style>

View File

@ -12,7 +12,7 @@ import axios from 'axios';
import { onMounted } from 'vue'; import { onMounted } from 'vue';
const { t } = useI18n(); const { t } = useI18n();
const props = defineProps({ defineProps({
dataKey: { dataKey: {
type: String, type: String,
required: true, required: true,
@ -58,7 +58,7 @@ onMounted(async () => {
auto-load auto-load
/> />
<VnFilterPanel <VnFilterPanel
:data-key="props.dataKey" :data-key
:un-removable-params="['warehouseFk', 'originScopeDays ', 'futureScopeDays']" :un-removable-params="['warehouseFk', 'originScopeDays ', 'futureScopeDays']"
> >
<template #tags="{ tag, formatFn }"> <template #tags="{ tag, formatFn }">