From 579786d12184f9cfac37726960b67216d5ca561e Mon Sep 17 00:00:00 2001 From: pablone <pablone@verdnatura.es> Date: Thu, 27 Feb 2025 06:17:31 +0100 Subject: [PATCH 1/2] refactor: refs #6897 update component props and attributes for consistency and improved functionality --- src/components/FormModel.vue | 4 ++ src/components/VnTable/VnTable.vue | 62 ++++++++++++++++--- src/components/common/RightMenu.vue | 14 ++++- src/components/common/VnCheckbox.vue | 2 +- src/components/common/VnSelect.vue | 2 - src/composables/checkEntryLock.js | 1 - src/pages/Entry/Card/EntryBuys.vue | 31 ++++++---- src/pages/Entry/EntryList.vue | 28 ++++++--- src/pages/Entry/EntryStockBought.vue | 2 +- src/pages/Supplier/SupplierList.vue | 16 +++++ src/pages/Ticket/TicketList.vue | 1 + .../integration/entry/entryList.spec.js | 2 +- 12 files changed, 125 insertions(+), 40 deletions(-) diff --git a/src/components/FormModel.vue b/src/components/FormModel.vue index 04ef13d45..2cf20a28c 100644 --- a/src/components/FormModel.vue +++ b/src/components/FormModel.vue @@ -95,6 +95,10 @@ const $props = defineProps({ type: [String, Boolean], default: '800px', }, + onDataSaved: { + type: Function, + default: () => {}, + }, }); const emit = defineEmits(['onFetch', 'onDataSaved']); const modelValue = computed( diff --git a/src/components/VnTable/VnTable.vue b/src/components/VnTable/VnTable.vue index de06d4e74..f19045785 100644 --- a/src/components/VnTable/VnTable.vue +++ b/src/components/VnTable/VnTable.vue @@ -32,7 +32,6 @@ import VnTableOrder from 'src/components/VnTable/VnOrder.vue'; import VnTableFilter from './VnTableFilter.vue'; import { getColAlign } from 'src/composables/getColAlign'; import RightMenu from '../common/RightMenu.vue'; -import { QItemSection } from 'quasar'; const arrayData = useArrayData(useAttrs()['data-key']); const $props = defineProps({ @@ -139,6 +138,10 @@ const $props = defineProps({ createComplement: { type: Object, }, + dataCy: { + type: String, + default: 'vn-table', + }, }); const { t } = useI18n(); @@ -167,7 +170,6 @@ const app = inject('app'); const editingRow = ref(null); const editingField = ref(null); const isTableMode = computed(() => mode.value == TABLE_MODE); -const showRightIcon = computed(() => $props.rightSearch || $props.rightSearchIcon); const selectRegex = /select/; const emit = defineEmits(['onFetch', 'update:selected', 'saveChanges']); const tableModes = [ @@ -255,7 +257,9 @@ function splitColumns(columns) { col.columnFilter = { inWhere: true, ...col.columnFilter }; splittedColumns.value.columns.push(col); } - // Status column + + splittedColumns.value.create = createOrderSort(splittedColumns.value.create); + if (splittedColumns.value.chips.length) { splittedColumns.value.columnChips = splittedColumns.value.chips.filter( (c) => !c.isId, @@ -271,6 +275,24 @@ function splitColumns(columns) { } } +function createOrderSort(columns) { + const orderedColumn = columns + .map((column, index) => + column.createOrder !== undefined ? { ...column, originalIndex: index } : null, + ) + .filter((item) => item !== null); + + orderedColumn.sort((a, b) => a.createOrder - b.createOrder); + + const filteredColumns = columns.filter((col) => col.createOrder === undefined); + + orderedColumn.forEach((col) => { + filteredColumns.splice(col.createOrder, 0, col); + }); + + return filteredColumns; +} + const rowClickFunction = computed(() => { if ($props.rowClick != undefined) return $props.rowClick; if ($props.redirect) return ({ id }) => redirectFn(id); @@ -340,12 +362,11 @@ function hasEditableFormat(column) { const clickHandler = async (event) => { const clickedElement = event.target.closest('td'); - const isDateElement = event.target.closest('.q-date'); const isTimeElement = event.target.closest('.q-time'); - const isQselectDropDown = event.target.closest('.q-select__dropdown-icon'); + const isQSelectDropDown = event.target.closest('.q-select__dropdown-icon'); - if (isDateElement || isTimeElement || isQselectDropDown) return; + if (isDateElement || isTimeElement || isQSelectDropDown) return; if (clickedElement === null) { await destroyInput(editingRow.value, editingField.value); @@ -584,9 +605,24 @@ function removeTextValue(data, getChanges) { return data; } + +function handleRowClick(event, row) { + if (event.ctrlKey) return rowCtrlClickFunction.value(event, row); + if (rowClickFunction.value) rowClickFunction.value(row); +} + +const rowCtrlClickFunction = computed(() => { + if ($props.rowCtrlClick != undefined) return $props.rowCtrlClick; + if ($props.redirect) + return (evt, { id }) => { + stopEventPropagation(evt); + window.open(`/#/${$props.redirect}/${id}`, '_blank'); + }; + return () => {}; +}); </script> <template> - <RightMenu v-if="$props.rightSearch"> + <RightMenu v-if="$props.rightSearch" :overlay="overlay"> <template #right-panel> <VnTableFilter :data-key="$attrs['data-key']" @@ -639,7 +675,7 @@ function removeTextValue(data, getChanges) { :style="isTableMode && `max-height: ${tableHeight}`" :virtual-scroll="isTableMode" @virtual-scroll="handleScroll" - @row-click="(_, row) => rowClickFunction && rowClickFunction(row)" + @row-click="(event, row) => handleRowClick(event, row)" @update:selected="emit('update:selected', $event)" @selection="(details) => handleSelection(details, rows)" :hide-selected-banner="true" @@ -985,7 +1021,10 @@ function removeTextValue(data, getChanges) { > <template #form-inputs="{ data }"> <div :style="createComplement?.containerStyle"> - <div> + <div + :style="createComplement?.previousStyle" + v-if="!quasar.screen.xs" + > <slot name="previous-create-dialog" :data="data" /> </div> <div class="grid-create" :style="createComplement?.columnGridStyle"> @@ -998,7 +1037,10 @@ function removeTextValue(data, getChanges) { :label="column.label" > <VnColumn - :column="column" + :column="{ + ...column, + ...{ disable: column?.createDisable ?? false }, + }" :row="{}" default="input" v-model="data[column.name]" diff --git a/src/components/common/RightMenu.vue b/src/components/common/RightMenu.vue index 196815df1..e2bc2d3e4 100644 --- a/src/components/common/RightMenu.vue +++ b/src/components/common/RightMenu.vue @@ -11,6 +11,13 @@ const stateStore = useStateStore(); const slots = useSlots(); const hasContent = useHasContent('#right-panel'); +defineProps({ + overlay: { + type: Boolean, + default: false, + }, +}); + onMounted(() => { if ((!slots['right-panel'] && !hasContent.value) || quasar.platform.is.mobile) stateStore.rightDrawer = false; @@ -34,7 +41,12 @@ onMounted(() => { </QBtn> </div> </Teleport> - <QDrawer v-model="stateStore.rightDrawer" side="right" :width="256"> + <QDrawer + v-model="stateStore.rightDrawer" + side="right" + :width="256" + :overlay="overlay" + > <QScrollArea class="fit"> <div id="right-panel"></div> <slot v-if="!hasContent" name="right-panel" /> diff --git a/src/components/common/VnCheckbox.vue b/src/components/common/VnCheckbox.vue index 27131d45e..94e91328b 100644 --- a/src/components/common/VnCheckbox.vue +++ b/src/components/common/VnCheckbox.vue @@ -27,7 +27,7 @@ const checkboxModel = computed({ </script> <template> <div> - <QCheckbox v-bind="$attrs" v-on="$attrs" v-model="checkboxModel" /> + <QCheckbox v-bind="$attrs" v-model="checkboxModel" /> <QIcon v-if="info" v-bind="$attrs" diff --git a/src/components/common/VnSelect.vue b/src/components/common/VnSelect.vue index 339f90e0e..d111780bd 100644 --- a/src/components/common/VnSelect.vue +++ b/src/components/common/VnSelect.vue @@ -302,8 +302,6 @@ defineExpose({ opts: myOptions, vnSelectRef }); function handleKeyDown(event) { if (event.key === 'Tab' && !event.shiftKey) { - event.preventDefault(); - const inputValue = vnSelectRef.value?.inputValue; if (inputValue) { diff --git a/src/composables/checkEntryLock.js b/src/composables/checkEntryLock.js index f964dea27..cb9fc4cd6 100644 --- a/src/composables/checkEntryLock.js +++ b/src/composables/checkEntryLock.js @@ -29,7 +29,6 @@ export async function checkEntryLock(entryFk, userFk) { .dialog({ component: VnConfirm, componentProps: { - 'data-cy': 'entry-lock-confirm', title: t('entry.lock.title'), message: t('entry.lock.message', { userName: data?.user?.nickname, diff --git a/src/pages/Entry/Card/EntryBuys.vue b/src/pages/Entry/Card/EntryBuys.vue index 67333b5bd..15f8cc20c 100644 --- a/src/pages/Entry/Card/EntryBuys.vue +++ b/src/pages/Entry/Card/EntryBuys.vue @@ -54,6 +54,7 @@ const columns = [ toggleIndeterminate: false, }, create: true, + createOrder: 12, width: '25px', }, { @@ -87,15 +88,6 @@ const columns = [ isEditable: false, columnFilter: false, }, - { - name: 'entryFk', - isId: true, - visible: false, - isEditable: false, - disable: true, - create: true, - columnFilter: false, - }, { align: 'center', label: 'Id', @@ -137,6 +129,7 @@ const columns = [ name: 'itemFk', visible: false, create: true, + createOrder: 0, columnFilter: false, }, { @@ -160,6 +153,8 @@ const columns = [ name: 'stickers', component: 'input', create: true, + + createOrder: 1, attrs: { positive: false, }, @@ -271,6 +266,7 @@ const columns = [ }, width: '45px', create: true, + createOrder: 3, style: getQuantityStyle, }, { @@ -280,6 +276,7 @@ const columns = [ toolTip: t('Buying value'), name: 'buyingValue', create: true, + createOrder: 2, component: 'number', attrs: { positive: false, @@ -312,6 +309,7 @@ const columns = [ toolTip: t('Package'), name: 'price2', component: 'number', + createDisable: true, width: '35px', create: true, format: (row) => parseFloat(row['price2']).toFixed(2), @@ -321,6 +319,7 @@ const columns = [ label: t('Box'), name: 'price3', component: 'number', + createDisable: true, cellEvent: { 'update:modelValue': async (value, oldValue, row) => { row['price2'] = row['price2'] * (value / oldValue); @@ -508,13 +507,14 @@ async function setBuyUltimate(itemFk, data) { }, }); const buyUltimateData = buyUltimate.data[0]; + if (!buyUltimateData) return; const allowedKeys = columns .filter((col) => col.create === true) .map((col) => col.name); allowedKeys.forEach((key) => { - if (buyUltimateData.hasOwnProperty(key) && key !== 'entryFk') { + if (buyUltimateData?.hasOwnProperty(key) && key !== 'entryFk') { if (!['stickers', 'quantity'].includes(key)) data[key] = buyUltimateData[key]; } }); @@ -607,6 +607,7 @@ onMounted(() => { ref="entryBuysRef" data-key="EntryBuys" :url="`Entries/${entityId}/getBuyList`" + search-url="EntryBuys" save-url="Buys/crud" :disable-option="{ card: true }" v-model:selected="selectedRows" @@ -636,16 +637,19 @@ onMounted(() => { isFullWidth: true, containerStyle: { display: 'flex', - 'flex-wrap': 'wrap', gap: '16px', position: 'relative', - height: '500px', }, columnGridStyle: { 'max-width': '50%', - flex: 1, 'margin-right': '30px', + flex: 1, }, + previousStyle: { + 'max-width': '30%', + height: '500px', + }, + displayPrevious: true, }" :is-editable="editableMode" :without-header="!editableMode" @@ -660,6 +664,7 @@ onMounted(() => { auto-load footer data-cy="entry-buys" + overlay > <template #column-hex="{ row }"> <VnColor :colors="row?.hexJson" style="height: 100%; min-width: 2000px" /> diff --git a/src/pages/Entry/EntryList.vue b/src/pages/Entry/EntryList.vue index a9cf2a5e2..f66151cc9 100644 --- a/src/pages/Entry/EntryList.vue +++ b/src/pages/Entry/EntryList.vue @@ -11,6 +11,8 @@ import VnTable from 'components/VnTable/VnTable.vue'; import SupplierDescriptorProxy from 'src/pages/Supplier/Card/SupplierDescriptorProxy.vue'; import VnSelectTravelExtended from 'src/components/common/VnSelectTravelExtended.vue'; import { toDate } from 'src/filters'; +import { useSummaryDialog } from 'src/composables/useSummaryDialog'; +import EntrySummary from './Card/EntrySummary.vue'; const { t } = useI18n(); const tableRef = ref(); @@ -18,6 +20,7 @@ const defaultEntry = ref({}); const state = useState(); const user = state.getUser(); const dataKey = 'EntryList'; +const { viewSummary } = useSummaryDialog(); const entryQueryFilter = { include: [ @@ -222,6 +225,19 @@ const columns = computed(() => [ visible: false, create: true, }, + { + align: 'right', + label: '', + name: 'tableActions', + actions: [ + { + title: t('components.smartCard.viewSummary'), + icon: 'preview', + isPrimary: true, + action: (row) => viewSummary(row.id, EntrySummary, 'xlg-width'), + }, + ], + }, ]); function getBadgeAttrs(row) { const date = row.landed; @@ -267,16 +283,7 @@ onBeforeMount(async () => { </script> <template> - <VnSection - :data-key="dataKey" - prefix="entry" - url="Entries/filter" - :array-data-props="{ - url: 'Entries/filter', - order: 'landed DESC', - userFilter: entryQueryFilter, - }" - > + <VnSection :data-key="dataKey" prefix="entry"> <template #advanced-menu> <EntryFilter :data-key="dataKey" /> </template> @@ -285,6 +292,7 @@ onBeforeMount(async () => { v-if="defaultEntry.defaultSupplierFk" ref="tableRef" :data-key="dataKey" + search-url="EntryList" url="Entries/filter" :filter="entryQueryFilter" order="landed DESC" diff --git a/src/pages/Entry/EntryStockBought.vue b/src/pages/Entry/EntryStockBought.vue index 4bd0fe640..888dd205c 100644 --- a/src/pages/Entry/EntryStockBought.vue +++ b/src/pages/Entry/EntryStockBought.vue @@ -95,7 +95,7 @@ const columns = computed(() => [ }, }, ], - 'data-cy': 'table-actions', + dataCy: 'table-actions', }, ]); diff --git a/src/pages/Supplier/SupplierList.vue b/src/pages/Supplier/SupplierList.vue index 600790745..c9625518f 100644 --- a/src/pages/Supplier/SupplierList.vue +++ b/src/pages/Supplier/SupplierList.vue @@ -6,7 +6,10 @@ import VnSection from 'src/components/common/VnSection.vue'; import VnInput from 'src/components/common/VnInput.vue'; import VnSelect from 'src/components/common/VnSelect.vue'; import FetchData from 'src/components/FetchData.vue'; +import { useSummaryDialog } from 'src/composables/useSummaryDialog'; +import SupplierSummary from './Card/SupplierSummary.vue'; +const { viewSummary } = useSummaryDialog(); const { t } = useI18n(); const tableRef = ref(); const dataKey = 'SupplierList'; @@ -103,6 +106,19 @@ const columns = computed(() => [ }, }, }, + { + align: 'right', + label: '', + name: 'tableActions', + actions: [ + { + title: t('components.smartCard.viewSummary'), + icon: 'preview', + isPrimary: true, + action: (row) => viewSummary(row.id, SupplierSummary, 'md-width'), + }, + ], + }, ]); </script> <template> diff --git a/src/pages/Ticket/TicketList.vue b/src/pages/Ticket/TicketList.vue index 78bebc297..f51547144 100644 --- a/src/pages/Ticket/TicketList.vue +++ b/src/pages/Ticket/TicketList.vue @@ -214,6 +214,7 @@ const columns = computed(() => [ { title: t('components.smartCard.viewSummary'), icon: 'preview', + isPrimary: true, action: (row, evt) => { if (evt && evt.ctrlKey) { const url = router.resolve({ diff --git a/test/cypress/integration/entry/entryList.spec.js b/test/cypress/integration/entry/entryList.spec.js index 4f99f0cb6..1ce99115a 100644 --- a/test/cypress/integration/entry/entryList.spec.js +++ b/test/cypress/integration/entry/entryList.spec.js @@ -67,7 +67,7 @@ describe('Entry', () => { it('Should notify when entry is lock by another user', () => { const checkLockMessage = () => { - cy.get('[data-cy="entry-lock-confirm"]').should('be.visible'); + cy.get('[role="dialog"]').should('be.visible'); cy.get('[data-cy="VnConfirm_message"] > span').should( 'contain.text', 'This entry has been locked by buyerNick', From a95b87999f6675de496f57ee2b4a392c681fc84f Mon Sep 17 00:00:00 2001 From: pablone <pablone@verdnatura.es> Date: Thu, 27 Feb 2025 10:35:15 +0100 Subject: [PATCH 2/2] fix: refs #6897 prevent default event behavior in autocompleteExpense function --- src/pages/InvoiceIn/Card/InvoiceInVat.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/InvoiceIn/Card/InvoiceInVat.vue b/src/pages/InvoiceIn/Card/InvoiceInVat.vue index e77453bc0..eae255120 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInVat.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInVat.vue @@ -202,7 +202,7 @@ function setCursor(ref) { :option-label="col.optionLabel" :filter-options="['id', 'name']" :tooltip="t('Create a new expense')" - @keydown.tab=" + @keydown.tab.prevent=" autocompleteExpense( $event, row,