feat: implement VnTable

This commit is contained in:
Javier Segarra 2024-09-11 12:43:24 +02:00
parent b0a439c26c
commit d6bb39236d
5 changed files with 346 additions and 333 deletions

View File

@ -80,4 +80,8 @@ defineExpose({
.img_zoom { .img_zoom {
border-radius: 0%; border-radius: 0%;
} }
.image-wrapper {
height: 50px;
width: 50px;
}
</style> </style>

View File

@ -277,9 +277,7 @@ watch(
:user-filter="lineFilter" :user-filter="lineFilter"
> >
<template #column-image="{ row }"> <template #column-image="{ row }">
<div class="image-wrapper"> <VnImg :id="parseInt(row?.item?.image)" class="rounded image-wrapper" />
<VnImg :id="parseInt(row?.item?.image)" class="rounded" />
</div>
</template> </template>
<template #column-id="{ row }"> <template #column-id="{ row }">
<span class="link" @click.stop> <span class="link" @click.stop>
@ -339,7 +337,7 @@ watch(
} }
} }
.image-wrapper { .imafge-wrapper {
height: 50px; height: 50px;
width: 50px; width: 50px;
margin-left: 30%; margin-left: 30%;

View File

@ -6,7 +6,6 @@ import axios from 'axios';
import ChangeQuantityDialog from 'pages/Ticket/Negative/components/ChangeQuantityDialog.vue'; import ChangeQuantityDialog from 'pages/Ticket/Negative/components/ChangeQuantityDialog.vue';
import ChangeStateDialog from 'pages/Ticket/Negative/components/ChangeStateDialog.vue'; import ChangeStateDialog from 'pages/Ticket/Negative/components/ChangeStateDialog.vue';
import ItemProposal from 'pages/Item/components/ItemProposal.vue'; import ItemProposal from 'pages/Item/components/ItemProposal.vue';
import VnLv from 'src/components/ui/VnLv.vue';
import FetchedTags from 'components/ui/FetchedTags.vue'; import FetchedTags from 'components/ui/FetchedTags.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import TicketTransfer from '../Card/TicketTransfer.vue'; import TicketTransfer from '../Card/TicketTransfer.vue';
@ -21,8 +20,10 @@ import useNotify from 'src/composables/useNotify.js';
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import { useDialogPluginComponent } from 'quasar'; import { useDialogPluginComponent } from 'quasar';
import { useSession } from 'src/composables/useSession'; import { useSession } from 'src/composables/useSession';
import ZoneDescriptorProxy from 'pages/Zone/Card/ZoneDescriptorProxy.vue'; import { useRoute } from 'vue-router';
import { useArrayData } from 'src/composables/useArrayData';
import VnImg from 'src/components/ui/VnImg.vue';
import TicketLackTable from './TicketLackTable.vue';
const { t } = useI18n(); const { t } = useI18n();
const URL_KEY = 'Tickets/ItemLack'; const URL_KEY = 'Tickets/ItemLack';
const editableStates = ref([]); const editableStates = ref([]);
@ -33,21 +34,14 @@ const changeStateDialogRef = ref();
const changeQuantityDialogRef = ref(); const changeQuantityDialogRef = ref();
const showProposalDialog = ref(false); const showProposalDialog = ref(false);
const showChangeQuantityDialog = ref(false); const showChangeQuantityDialog = ref(false);
const componentIsRendered = ref(false);
const showFree = ref(true); const showFree = ref(true);
const selectedRows = ref([]); const selectedRows = ref([]);
const session = useSession();
import { useRoute } from 'vue-router';
import { useArrayData } from 'src/composables/useArrayData';
const route = useRoute(); const route = useRoute();
const token = session.getTokenMultimedia();
const itemLack = ref(null); const itemLack = ref(null);
const originalRowDataCopy = ref(null); const originalRowDataCopy = ref(null);
onMounted(() => { onMounted(() => {
stateStore.rightDrawer = false; stateStore.rightDrawer = false;
nextTick(() => {
componentIsRendered.value = true;
});
}); });
onUnmounted(() => { onUnmounted(() => {
stateStore.rightDrawer = true; stateStore.rightDrawer = true;
@ -56,173 +50,10 @@ onUnmounted(() => {
const copyOriginalRowsData = (rows) => { const copyOriginalRowsData = (rows) => {
originalRowDataCopy.value = JSON.parse(JSON.stringify(rows)); originalRowDataCopy.value = JSON.parse(JSON.stringify(rows));
}; };
const getInputEvents = (colField, props) => ({
'update:modelValue': () => saveChange(colField, props),
'keyup.enter': () => saveChange(colField, props),
});
const saveChange = async (field, { rowIndex, row }) => {
try {
switch (field) {
case 'code':
await axios.post(`Tickets/state`, {
ticketFk: row.ticketFk,
code: row[field],
});
break;
case 'quantity':
await axios.post(`Sales/${row.saleFk}/updateQuantity`, {
quantity: +row.quantity,
});
break;
default:
console.error(field, { rowIndex, row });
break;
}
notify('globals.dataSaved', 'positive');
} catch (err) {
console.error('Error saving changes', err);
}
};
const entityId = computed(() => route.params.id); const entityId = computed(() => route.params.id);
const item = ref({}); const item = ref({});
function isComponentVn(col) {
return tableColumnComponents?.value[col.name]?.component === 'span' ?? false;
}
const tableColumnComponents = computed(() => ({
status: {
component: 'span',
props: {},
event: () => ({}),
sortable: false,
},
ticketFk: {
component: QBtn,
props: { color: 'blue', sortable: true, flat: true },
event: () => ({}),
sortable: true,
},
shipped: {
component: 'span',
props: {},
event: () => ({}),
},
theoreticalhour: {
component: 'span',
props: {},
event: () => ({}),
},
state: {
style: 'width: 160px',
component: VnSelect,
type: 'select',
filterValue: null,
props: {
'option-value': 'code',
'option-label': 'name',
'emit-value': true,
'map-options': true,
'use-input': true,
'hide-selected': true,
dense: true,
options: editableStates.value,
},
event: getInputEvents,
},
zoneName: {
component: QBtn,
props: { color: 'blue', sortable: true, flat: true },
event: () => ({}),
},
nickname: {
component: 'span',
props: {},
event: () => ({}),
},
quantity: {
component: VnInput,
props: {
type: 'number',
min: 0,
class: 'input-number',
},
event: getInputEvents,
style: 'width: 100px',
},
alertLevelCode: {
component: 'span',
props: {},
event: () => ({}),
},
}));
const columns = computed(() => [
{
name: 'status',
align: 'left',
sortable: false,
},
{
name: 'ticketFk',
label: t('negative.detail.ticketFk'),
field: 'ticketFk',
align: 'left',
sortable: true,
},
{
name: 'shipped',
label: t('negative.detail.shipped'),
field: 'shipped',
align: 'left',
format: (val) => toDate(val),
sortable: true,
},
{
name: 'theoreticalhour',
label: t('negative.detail.theoreticalhour'),
field: 'theoreticalhour',
align: 'left',
sortable: true,
format: (val) => toHour(val),
},
{
name: 'state',
label: t('negative.detail.state'),
field: 'code',
align: 'left',
sortable: true,
},
{
name: 'zoneName',
label: t('negative.detail.zoneName'),
field: 'zoneName',
align: 'left',
sortable: true,
},
{
name: 'nickname',
label: t('negative.detail.nickname'),
field: 'nickname',
align: 'left',
sortable: true,
},
{
name: 'quantity',
label: t('negative.detail.quantity'),
field: 'quantity',
align: 'left',
sortable: true,
style: 'width: 100px',
},
]);
const emit = defineEmits([...useDialogPluginComponent.emits, 'selection', 'close']); const emit = defineEmits([...useDialogPluginComponent.emits, 'selection', 'close']);
function rowsHasSelected(selection) {
emit('selection', selection);
}
const itemLackForm = ref(); const itemLackForm = ref();
@ -257,7 +88,6 @@ const itemProposalEvt = ({ itemProposal }) => {
itemProposalSelected.value = itemProposal; itemProposalSelected.value = itemProposal;
replaceItem(); replaceItem();
}; };
const tableRef = ref(null);
const itemProposalSelected = ref(null); const itemProposalSelected = ref(null);
const replaceItem = () => { const replaceItem = () => {
const rows = handleRows(originalRowDataCopy.value).sort((row) => row.quantity); const rows = handleRows(originalRowDataCopy.value).sort((row) => row.quantity);
@ -396,10 +226,10 @@ const replaceItem = () => {
</VnSubToolbar> </VnSubToolbar>
<QPage> <QPage>
<div class="full-width q-pa-md"> <div class="full-width q-pa-md">
<p>item:{{ item }}</p> <!-- <p>item:{{ item }}</p>
<p>itemLack:{{ itemLack }}</p> <p>itemLack:{{ itemLack }}</p>
<p>selectedRows:{{ selectedRows }}</p> <p>selectedRows:{{ selectedRows }}</p>
<p>itemProposalSelected:{{ itemProposalSelected }}</p> <p>itemProposalSelected:{{ itemProposalSelected }}</p> -->
<VnPaginate <VnPaginate
:data-key="URL_KEY" :data-key="URL_KEY"
:url="`${URL_KEY}/${entityId}`" :url="`${URL_KEY}/${entityId}`"
@ -408,20 +238,10 @@ const replaceItem = () => {
auto-load auto-load
> >
<template #body="{ rows }"> <template #body="{ rows }">
<VnLv class="q-mb-lg image"> <!-- <VnLv >
<template #label> <template #label> -->
<QImg <div class="q-mb-lg image" style="display: flex; align-items: center">
:src="`/api/Images/catalog/50x50/${entityId}/download?access_token=${token}`" <VnImg :id="item.id" class="rounded image-wrapper"></VnImg>
spinner-color="primary"
:ratio="1"
height="50px"
width="50px"
class="image remove-bg"
:alt="'asdads'"
/>
</template>
<template #value>
<div style="display: flex; align-items: center">
<QBtn flat class="link text-blue"> <QBtn flat class="link text-blue">
{{ item.longName }} {{ item.longName }}
<ItemDescriptorProxy :id="entityId" /> <ItemDescriptorProxy :id="entityId" />
@ -432,135 +252,17 @@ const replaceItem = () => {
:color="itemLack.lack === 0 ? 'green' : 'red'" :color="itemLack.lack === 0 ? 'green' : 'red'"
:label="itemLack.lack" :label="itemLack.lack"
/> />
<FetchedTags <FetchedTags class="q-ml-md" :item="item" :max-length="5" />
class="q-ml-md"
:item="item"
:max-length="5"
/>
</div> </div>
{{ rows }}
<!-- </template>
<template #value> </template>
</VnLv> -->
</template> </template>
</VnLv> </VnPaginate>
<TransitionGroup name="list" tag="div"> </div>
<QTable <TicketLackTable></TicketLackTable>
ref="tableRef" </QPage>
:columns="columns"
:rows="handleRows(rows)"
row-key="ticketFk"
selection="multiple"
v-model:selected="selectedRows"
@update:selected="rowsHasSelected"
:grid="$q.screen.lt.md"
hide-bottom
>
<template #body="props">
<QTr>
<QTd>
<QCheckbox v-model="props.selected" />
<QIcon
name="do_not_disturb_on_total_silence"
size="sm"
color="red"
>
<QTooltip>{{
t('Disabled substitution')
}}</QTooltip>
</QIcon>
<QIcon
name="do_not_disturb_on_total_silence"
size="sm"
color="red"
>
<QTooltip>{{
t('Substitution Observation')
}}</QTooltip>
</QIcon>
</QTd>
<QTd v-for="col in props.cols" :key="col.name">
<template
v-if="
tableColumnComponents[col.name]?.component
"
>
<component
:is="
tableColumnComponents[col.name]
.component
"
v-bind="
tableColumnComponents[col.name].props
"
v-model="props.row[col.field]"
v-on="
tableColumnComponents[col.name].event(
col.field,
props
)
"
:style="
tableColumnComponents[col.name].style
"
>
<template v-if="isComponentVn(col)">{{
col.value
}}</template>
<template v-if="col.name === 'status'">
<QIcon
v-if="props.row.isRookie"
name="vn:person"
size="xs"
color="primary"
class="cursor-pointer"
>
<QTooltip>{{
t('negative.detail.isRookie')
}}</QTooltip>
</QIcon>
<QIcon
v-if="props.row.peticionCompra"
name="vn:buyrequest"
size="xs"
color="primary"
class="cursor-pointer"
>
<QTooltip>{{
t(
'negative.detail.peticionCompra'
)
}}</QTooltip>
</QIcon>
<QIcon
v-if="props.row.turno"
name="vn:calendar"
size="xs"
color="primary"
class="cursor-pointer"
>
<QTooltip>{{
t('negative.detail.turno')
}}</QTooltip>
</QIcon>
</template>
<template v-if="col.name === 'ticketFk'"
>{{ col.value }}
<ItemDescriptorProxy
:id="$props.entityId"
/></template>
<template v-if="col.name === 'zoneName'">
{{ col.value }}
<ZoneDescriptorProxy
:id="props.row.zoneFk"
/>
</template>
</component>
</template>
</QTd>
</QTr>
</template>
</QTable>
</TransitionGroup>
</template>
</VnPaginate></div
></QPage>
<!--<HandleSplited <!--<HandleSplited
ref="splitDialogRef" ref="splitDialogRef"

View File

@ -213,6 +213,7 @@ onBeforeMount(() => {
:use-model="true" :use-model="true"
:row-click="redirectToCreateView" :row-click="redirectToCreateView"
v-model:selected="selectedRows" v-model:selected="selectedRows"
:create="false"
:table="{ :table="{
'row-key': 'itemFk', 'row-key': 'itemFk',
selection: 'multiple', selection: 'multiple',

View File

@ -0,0 +1,308 @@
<script setup>
import { computed, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { QBtn, QCheckbox } from 'quasar';
import axios from 'axios';
import FetchData from 'src/components/FetchData.vue';
import VnSelect from 'components/common/VnSelect.vue';
import VnInput from 'src/components/common/VnInput.vue';
import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue';
import { toDate, toHour } from 'src/filters';
import useNotify from 'src/composables/useNotify.js';
import { useDialogPluginComponent } from 'quasar';
import ZoneDescriptorProxy from 'pages/Zone/Card/ZoneDescriptorProxy.vue';
import { useRoute } from 'vue-router';
import { useArrayData } from 'src/composables/useArrayData';
import VnTable from 'src/components/VnTable/VnTable.vue';
const { t } = useI18n();
const URL_KEY = 'Tickets/ItemLack';
const editableStates = ref([]);
const { notify } = useNotify();
const showFree = ref(true);
const selectedRows = ref([]);
const route = useRoute();
const itemLack = ref(null);
const originalRowDataCopy = ref(null);
// defineProps({
// rows: {
// type: [Object],
// required: true,
// default: () => {},
// },
// });
const getInputEvents = (colField, props) => ({
'update:modelValue': () => saveChange(colField, props),
'keyup.enter': () => saveChange(colField, props),
});
const saveChange = async (field, { rowIndex, row }) => {
try {
switch (field) {
case 'code':
await axios.post(`Tickets/state`, {
ticketFk: row.ticketFk,
code: row[field],
});
break;
case 'quantity':
await axios.post(`Sales/${row.saleFk}/updateQuantity`, {
quantity: +row.quantity,
});
break;
default:
console.error(field, { rowIndex, row });
break;
}
notify('globals.dataSaved', 'positive');
} catch (err) {
console.error('Error saving changes', err);
}
};
const entityId = computed(() => route.params.id);
const item = ref({});
function isComponentVn(col) {
return tableColumnComponents?.value[col.name]?.component === 'span' ?? false;
}
const tableColumnComponents = computed(() => ({
status: {
component: 'span',
props: {},
event: () => ({}),
sortable: false,
},
ticketFk: {
component: QBtn,
props: { color: 'blue', sortable: true, flat: true },
event: () => ({}),
sortable: true,
},
shipped: {
component: 'span',
props: {},
event: () => ({}),
},
theoreticalhour: {
component: 'span',
props: {},
event: () => ({}),
},
state: {
style: 'width: 160px',
component: VnSelect,
type: 'select',
filterValue: null,
props: {
'option-value': 'code',
'option-label': 'name',
'emit-value': true,
'map-options': true,
'use-input': true,
'hide-selected': true,
dense: true,
options: editableStates.value,
},
event: getInputEvents,
},
zoneName: {
component: QBtn,
props: { color: 'blue', sortable: true, flat: true },
event: () => ({}),
},
nickname: {
component: 'span',
props: {},
event: () => ({}),
},
quantity: {
component: VnInput,
props: {
type: 'number',
min: 0,
class: 'input-number',
},
event: getInputEvents,
style: 'width: 100px',
},
alertLevelCode: {
component: 'span',
props: {},
event: () => ({}),
},
}));
const columns = computed(() => [
{
name: 'status',
align: 'left',
sortable: false,
columnClass: 'expand',
},
{
name: 'ticketFk',
label: t('negative.detail.ticketFk'),
field: 'ticketFk',
align: 'left',
sortable: true,
},
{
name: 'shipped',
label: t('negative.detail.shipped'),
field: 'shipped',
align: 'left',
format: ({ shipped }) => toDate(shipped),
sortable: true,
},
{
name: 'theoreticalhour',
label: t('negative.detail.theoreticalhour'),
field: 'theoreticalhour',
align: 'left',
sortable: true,
format: ({ theoreticalhour }) => toHour(theoreticalhour),
},
{
name: 'state',
label: t('negative.detail.state'),
field: 'code',
align: 'left',
sortable: true,
},
{
name: 'zoneName',
label: t('negative.detail.zoneName'),
field: 'zoneName',
align: 'left',
sortable: true,
},
{
name: 'nickname',
label: t('negative.detail.nickname'),
field: 'nickname',
align: 'left',
sortable: true,
},
{
name: 'quantity',
label: t('negative.detail.quantity'),
field: 'quantity',
align: 'left',
sortable: true,
style: 'width: 100px',
},
]);
const emit = defineEmits([...useDialogPluginComponent.emits, 'selection', 'close']);
function rowsHasSelected(selection) {
emit('selection', selection);
}
const itemLackForm = ref();
const reload = async () => {
itemLackForm.value.fetch();
};
defineExpose({ reload });
// Función de comparación
const tableRef = ref(null);
</script>
<template>
<FetchData
url="States/editableStates"
@on-fetch="(data) => (editableStates = data)"
auto-load
/>
<FetchData
:url="`Items/${entityId}/getCard`"
:fields="['longName']"
@on-fetch="(data) => (item = data)"
auto-load
/>
<FetchData
:url="`Buys/latestBuysFilter`"
:fields="['longName']"
:filter="{ where: { 'i.id': '2' } }"
@on-fetch="(data) => Object.assign(item.value, data[0])"
auto-load
/>
<FetchData
:url="`Tickets/itemLack`"
:params="{ itemFk: entityId }"
@on-fetch="
(data) => {
itemLack = data[0];
// itemLackForm.value.fetch();
}
"
auto-load
/>
<VnTable
ref="tableRef"
:data-key="URL_KEY"
:url="`${URL_KEY}/${entityId}`"
:columns="columns"
:without-header="true"
:right-search="false"
auto-load
:create="false"
>
<!--
<template #body="props">
{{ props }}
</template> -->
<template #column-status="props">
<QIcon
v-if="props.row.isRookie"
name="vn:person"
size="xs"
color="primary"
class="cursor-pointer"
>
<QTooltip>{{ t('negative.detail.isRookie') }}</QTooltip>
</QIcon>
<QIcon
v-if="props.row.peticionCompra"
name="vn:buyrequest"
size="xs"
color="primary"
class="cursor-pointer"
>
<QTooltip>{{ t('negative.detail.peticionCompra') }}</QTooltip>
</QIcon>
<QIcon
v-if="props.row.turno"
name="vn:calendar"
size="xs"
color="primary"
class="cursor-pointer"
>
<QTooltip>{{ t('negative.detail.turno') }}</QTooltip>
</QIcon>
</template>
<template #column-ticketFk="{ col }"
>{{ col.value }} <ItemDescriptorProxy :id="$props.entityId"
/></template>
<template #column-zoneName="{ row, col }">
{{ col.value }}
<ZoneDescriptorProxy :id="row.zoneFk" />
</template>
</VnTable>
</template>
<style lang="scss" scoped>
.list-enter-active,
.list-leave-active {
transition: all 1s ease;
}
.list-enter-from,
.list-leave-to {
opacity: 0;
background-color: $primary;
}
</style>