From 5803b8392b6821bb1fa1a9168aff7fea5fe505be Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Fri, 15 Mar 2024 16:30:12 +0100 Subject: [PATCH 1/7] feat: refs #6951 clone ticket --- .../Ticket/Card/TicketDescriptorMenu.vue | 90 ++++++++++++------- .../ticket/ticketDescriptor.spec.js | 27 ++++++ 2 files changed, 87 insertions(+), 30 deletions(-) create mode 100644 test/cypress/integration/ticket/ticketDescriptor.spec.js diff --git a/src/pages/Ticket/Card/TicketDescriptorMenu.vue b/src/pages/Ticket/Card/TicketDescriptorMenu.vue index 95f6a94d9..61167d722 100644 --- a/src/pages/Ticket/Card/TicketDescriptorMenu.vue +++ b/src/pages/Ticket/Card/TicketDescriptorMenu.vue @@ -3,7 +3,7 @@ import axios from 'axios'; import { ref } from 'vue'; import { useQuasar } from 'quasar'; import { useI18n } from 'vue-i18n'; -import { useRouter, useRoute } from 'vue-router'; +import { useRouter } from 'vue-router'; import { usePrintService } from 'composables/usePrintService'; import SendEmailDialog from 'components/common/SendEmailDialog.vue'; import VnConfirm from 'components/ui/VnConfirm.vue'; @@ -17,13 +17,14 @@ const props = defineProps({ }, }); -const router = useRouter(); -const route = useRoute(); -const quasar = useQuasar(); +const { push, currentRoute } = useRouter(); +const { dialog, notify } = useQuasar(); const { t } = useI18n(); const { openReport, sendEmail } = usePrintService(); const ticket = ref(props.ticket); +const ticketId = currentRoute.value.params.id; +const actions = { remove: remove, clone: clone }; function openDeliveryNote(type = 'deliveryNote', documentType = 'pdf') { const path = `Tickets/${ticket.value.id}/delivery-note-${documentType}`; @@ -35,7 +36,7 @@ function openDeliveryNote(type = 'deliveryNote', documentType = 'pdf') { function sendDeliveryNoteConfirmation(type = 'deliveryNote', documentType = 'pdf') { const customer = ticket.value.client; - quasar.dialog({ + dialog({ component: SendEmailDialog, componentProps: { data: { @@ -67,7 +68,7 @@ function showSmsDialog(template, customData) { const address = ticket.value.address; const client = ticket.value.client; const phone = - route.params.phone || + currentRoute.value.params.phone || address.mobile || address.phone || client.mobile || @@ -82,7 +83,7 @@ function showSmsDialog(template, customData) { Object.assign(data, customData); } - quasar.dialog({ + dialog({ component: VnSmsDialog, componentProps: { phone: phone, @@ -95,43 +96,63 @@ function showSmsDialog(template, customData) { } async function showSmsDialogWithChanges() { - const query = `TicketLogs/${route.params.id}/getChanges`; + const query = `TicketLogs/${ticketId}/getChanges`; const response = await axios.get(query); showSmsDialog('orderChanges', { changes: response.data }); } async function sendSms(body) { - await axios.post(`Tickets/${route.params.id}/sendSms`, body); - quasar.notify({ + await axios.post(`Tickets/${ticketId}/sendSms`, body); + notify({ message: 'Notification sent', type: 'positive', }); } -function confirmDelete() { - quasar - .dialog({ - component: VnConfirm, - componentProps: { - promise: remove, - }, - }) - .onOk(async () => await router.push({ name: 'TicketList' })); +function openConfirmDialog(callback) { + dialog({ + component: VnConfirm, + componentProps: { + promise: actions[callback], + }, + }); +} + +async function clone() { + const opts = { message: t('Ticket cloned'), type: 'positive' }; + let clonedTicketId; + + try { + const { data } = await axios.post(`Tickets/${ticketId}/clone`, { + shipped: ticket.value.shipped, + }); + clonedTicketId = data; + } catch (e) { + opts.message = t('It was not able to clone the ticket'); + opts.type = 'negative'; + } finally { + notify(opts); + + if (clonedTicketId) + push({ name: 'TicketSummary', params: { id: clonedTicketId } }); + } } async function remove() { - const id = route.params.id; - await axios.post(`Tickets/${id}/setDeleted`); + try { + await axios.post(`Tickets/${ticketId}/setDeleted`); - quasar.notify({ - message: t('Ticket deleted'), - type: 'positive', - }); - quasar.notify({ - message: t('You can undo this action within the first hour'), - icon: 'info', - }); + notify({ message: t('Ticket deleted'), type: 'positive' }); + notify({ + message: t('You can undo this action within the first hour'), + icon: 'info', + }); + + push({ name: 'TicketList' }); + } catch (e) { + notify({ message: e.message, type: 'negative' }); + } } </script> <template> @@ -227,9 +248,15 @@ async function remove() { </QList> </QMenu> </QItem> + <QItem @click="openConfirmDialog('clone')" v-ripple clickable> + <QItemSection avatar> + <QIcon name="content_copy" /> + </QItemSection> + <QItemSection>{{ t('To clone ticket') }}</QItemSection> + </QItem> <template v-if="!ticket.isDeleted"> <QSeparator /> - <QItem @click="confirmDelete()" v-ripple clickable> + <QItem @click="openConfirmDialog('remove')" v-ripple clickable> <QItemSection avatar> <QIcon name="delete" /> </QItemSection> @@ -253,4 +280,7 @@ es: Order changes: Cambios del pedido Ticket deleted: Ticket eliminado You can undo this action within the first hour: Puedes deshacer esta acción dentro de la primera hora + To clone ticket: Clonar ticket + Ticket cloned: Ticked clonado + It was not able to clone the ticket: No se pudo clonar el ticket </i18n> diff --git a/test/cypress/integration/ticket/ticketDescriptor.spec.js b/test/cypress/integration/ticket/ticketDescriptor.spec.js new file mode 100644 index 000000000..c212d3b4a --- /dev/null +++ b/test/cypress/integration/ticket/ticketDescriptor.spec.js @@ -0,0 +1,27 @@ +/// <reference types="cypress" /> +describe('Ticket descriptor', () => { + const toCloneOpt = '.q-list > :nth-child(5)'; + const warehouseValue = '.summaryBody > :nth-child(2) > :nth-child(6) > .value > span'; + const summaryHeader = '.summaryHeader > div'; + + beforeEach(() => { + const ticketId = 1; + + cy.login('developer'); + cy.visit(`/#/ticket/${ticketId}/summary`); + }); + + it('should clone the ticket without warehouse', () => { + cy.openLeftMenu(); + cy.openActionsDescriptor(); + cy.get(toCloneOpt).click(); + cy.clickConfirm(); + cy.get(warehouseValue).contains('-'); + cy.get(summaryHeader) + .invoke('text') + .then((text) => { + const [, owner] = text.split('-'); + cy.wrap(owner.trim()).should('eq', 'Bruce Wayne (1101)'); + }); + }); +}); From d7d83fd886fedb8eb09f03fd49ae7bbd6a99b3ca Mon Sep 17 00:00:00 2001 From: Javier Segarra <jsegarra@verdnatura.es> Date: Thu, 21 Mar 2024 14:56:39 +0100 Subject: [PATCH 2/7] refs #7124 feat: autofocus without property --- quasar.config.js | 2 +- src/boot/qformMixin.js | 32 +++++++++++++++++++++++++++++++ src/boot/quasar.js | 6 ++++++ src/components/ui/VnSearchbar.vue | 2 +- 4 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 src/boot/qformMixin.js create mode 100644 src/boot/quasar.js diff --git a/quasar.config.js b/quasar.config.js index 2d8289508..80ddc3759 100644 --- a/quasar.config.js +++ b/quasar.config.js @@ -29,7 +29,7 @@ module.exports = configure(function (/* ctx */) { // app boot file (/src/boot) // --> boot files are part of "main.js" // https://v2.quasar.dev/quasar-cli/boot-files - boot: ['i18n', 'axios', 'vnDate', 'validations'], + boot: ['i18n', 'axios', 'vnDate', 'validations', 'quasar'], // https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#css css: ['app.scss'], diff --git a/src/boot/qformMixin.js b/src/boot/qformMixin.js new file mode 100644 index 000000000..37beda4f0 --- /dev/null +++ b/src/boot/qformMixin.js @@ -0,0 +1,32 @@ +import { QForm } from 'quasar'; +import { getCurrentInstance } from 'vue'; + +export default { + inject: { QForm }, + component: { QForm }, + components: { QForm }, + extends: { QForm }, + mounted: function () { + const vm = getCurrentInstance(); + if (vm.type.name === 'QForm') + if (![ 'searchbarForm'].includes(this.$el?.id)) { + let that = this; + + // AUTOFOCUS + const elementsArray = Array.from(this.$el.elements); + const index = elementsArray.findIndex(element => element.classList.contains('q-field__native')); + + if (index !== -1) { + const firstInputElement = elementsArray[index]; + firstInputElement.focus(); + } + + // KEYUP Event + document.addEventListener('keyup', function (evt) { + if (evt.keyCode === 13) { + that.onSubmit(); + } + }); + } + }, +}; diff --git a/src/boot/quasar.js b/src/boot/quasar.js new file mode 100644 index 000000000..a8d9b7ad9 --- /dev/null +++ b/src/boot/quasar.js @@ -0,0 +1,6 @@ +import { boot } from 'quasar/wrappers'; +import qFormMixin from './qformMixin'; + +export default boot(({ app }) => { + app.mixin(qFormMixin); +}); diff --git a/src/components/ui/VnSearchbar.vue b/src/components/ui/VnSearchbar.vue index 143efcd0f..d86b02166 100644 --- a/src/components/ui/VnSearchbar.vue +++ b/src/components/ui/VnSearchbar.vue @@ -108,7 +108,7 @@ async function search() { </script> <template> - <QForm @submit="search"> + <QForm @submit="search" id="searchbarForm"> <VnInput id="searchbar" v-model="searchText" From 561a7a52869ffb8cdd86603d82051047aea7c5bb Mon Sep 17 00:00:00 2001 From: Javier Segarra <jsegarra@verdnatura.es> Date: Thu, 21 Mar 2024 15:03:48 +0100 Subject: [PATCH 3/7] refs #7124 perf: whitespace --- src/boot/qformMixin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/boot/qformMixin.js b/src/boot/qformMixin.js index 37beda4f0..a3e2543c4 100644 --- a/src/boot/qformMixin.js +++ b/src/boot/qformMixin.js @@ -9,7 +9,7 @@ export default { mounted: function () { const vm = getCurrentInstance(); if (vm.type.name === 'QForm') - if (![ 'searchbarForm'].includes(this.$el?.id)) { + if (!['searchbarForm'].includes(this.$el?.id)) { let that = this; // AUTOFOCUS From c4f7b5e7c0659468487c86d5b9221193f8dfaae0 Mon Sep 17 00:00:00 2001 From: Javier Segarra <jsegarra@verdnatura.es> Date: Fri, 22 Mar 2024 07:32:21 +0100 Subject: [PATCH 4/7] refs #7124 perf: use find instead findIndex --- src/boot/qformMixin.js | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/src/boot/qformMixin.js b/src/boot/qformMixin.js index a3e2543c4..71f5e9f6d 100644 --- a/src/boot/qformMixin.js +++ b/src/boot/qformMixin.js @@ -1,11 +1,6 @@ -import { QForm } from 'quasar'; import { getCurrentInstance } from 'vue'; export default { - inject: { QForm }, - component: { QForm }, - components: { QForm }, - extends: { QForm }, mounted: function () { const vm = getCurrentInstance(); if (vm.type.name === 'QForm') @@ -14,19 +9,11 @@ export default { // AUTOFOCUS const elementsArray = Array.from(this.$el.elements); - const index = elementsArray.findIndex(element => element.classList.contains('q-field__native')); + const firstInputElement = elementsArray.find(element => element.classList.contains('q-field__native')); - if (index !== -1) { - const firstInputElement = elementsArray[index]; + if (firstInputElement) { firstInputElement.focus(); } - - // KEYUP Event - document.addEventListener('keyup', function (evt) { - if (evt.keyCode === 13) { - that.onSubmit(); - } - }); } }, }; From b11462e244d61b7857345233155284044845857b Mon Sep 17 00:00:00 2001 From: Javier Segarra <jsegarra@verdnatura.es> Date: Fri, 22 Mar 2024 07:32:33 +0100 Subject: [PATCH 5/7] refs #7124 perf: avoid focus in VnFilterPanel --- src/boot/qformMixin.js | 4 +--- src/components/ui/VnFilterPanel.vue | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/boot/qformMixin.js b/src/boot/qformMixin.js index 71f5e9f6d..66e3de4f5 100644 --- a/src/boot/qformMixin.js +++ b/src/boot/qformMixin.js @@ -4,9 +4,7 @@ export default { mounted: function () { const vm = getCurrentInstance(); if (vm.type.name === 'QForm') - if (!['searchbarForm'].includes(this.$el?.id)) { - let that = this; - + if (!['searchbarForm','filterPanelForm'].includes(this.$el?.id)) { // AUTOFOCUS const elementsArray = Array.from(this.$el.elements); const firstInputElement = elementsArray.find(element => element.classList.contains('q-field__native')); diff --git a/src/components/ui/VnFilterPanel.vue b/src/components/ui/VnFilterPanel.vue index 722462d4a..96d097191 100644 --- a/src/components/ui/VnFilterPanel.vue +++ b/src/components/ui/VnFilterPanel.vue @@ -164,7 +164,7 @@ function formatValue(value) { </script> <template> - <QForm @submit="search"> + <QForm @submit="search" id="filterPanelForm"> <QList dense> <QItem class="q-mt-xs"> <QItemSection top> From 8a82b7ab38a27925842c69dd771beb7bf40a0007 Mon Sep 17 00:00:00 2001 From: Javier Segarra <jsegarra@verdnatura.es> Date: Fri, 22 Mar 2024 09:55:16 +0100 Subject: [PATCH 6/7] refs #7124 perf: avoid focus in disabled fields --- src/boot/qformMixin.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/boot/qformMixin.js b/src/boot/qformMixin.js index 66e3de4f5..8c89c9202 100644 --- a/src/boot/qformMixin.js +++ b/src/boot/qformMixin.js @@ -1,5 +1,9 @@ import { getCurrentInstance } from 'vue'; +const filterAvailableInput = element => element.classList.contains('q-field__native') && !element.disabled +const filterAvailableText = element => element.__vueParentComponent.type.name === 'QInput' && element.__vueParentComponent?.attrs?.class !== 'vn-input-date'; + + export default { mounted: function () { const vm = getCurrentInstance(); @@ -7,7 +11,7 @@ export default { if (!['searchbarForm','filterPanelForm'].includes(this.$el?.id)) { // AUTOFOCUS const elementsArray = Array.from(this.$el.elements); - const firstInputElement = elementsArray.find(element => element.classList.contains('q-field__native')); + const firstInputElement = elementsArray.filter(filterAvailableInput).find(filterAvailableText); if (firstInputElement) { firstInputElement.focus(); From 2bb95fa56816ac4dd603908ab73ee0dd3d983806 Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Fri, 22 Mar 2024 16:31:10 +0100 Subject: [PATCH 7/7] rafactor: refs #6951 actions descriptor & update changelog --- CHANGELOG.md | 2 + .../Ticket/Card/TicketDescriptorMenu.vue | 73 +++++++++---------- 2 files changed, 38 insertions(+), 37 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dbf6bdcc3..51dd2010c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- (Tickets) => Se añade la opción de clonar ticket. #6951 + ### Changed ### Fixed diff --git a/src/pages/Ticket/Card/TicketDescriptorMenu.vue b/src/pages/Ticket/Card/TicketDescriptorMenu.vue index 61167d722..c7784bc2a 100644 --- a/src/pages/Ticket/Card/TicketDescriptorMenu.vue +++ b/src/pages/Ticket/Card/TicketDescriptorMenu.vue @@ -24,7 +24,42 @@ const { openReport, sendEmail } = usePrintService(); const ticket = ref(props.ticket); const ticketId = currentRoute.value.params.id; -const actions = { remove: remove, clone: clone }; +const actions = { + clone: async () => { + const opts = { message: t('Ticket cloned'), type: 'positive' }; + let clonedTicketId; + + try { + const { data } = await axios.post(`Tickets/${ticketId}/clone`, { + shipped: ticket.value.shipped, + }); + clonedTicketId = data; + } catch (e) { + opts.message = t('It was not able to clone the ticket'); + opts.type = 'negative'; + } finally { + notify(opts); + + if (clonedTicketId) + push({ name: 'TicketSummary', params: { id: clonedTicketId } }); + } + }, + remove: async () => { + try { + await axios.post(`Tickets/${ticketId}/setDeleted`); + + notify({ message: t('Ticket deleted'), type: 'positive' }); + notify({ + message: t('You can undo this action within the first hour'), + icon: 'info', + }); + + push({ name: 'TicketList' }); + } catch (e) { + notify({ message: e.message, type: 'negative' }); + } + }, +}; function openDeliveryNote(type = 'deliveryNote', documentType = 'pdf') { const path = `Tickets/${ticket.value.id}/delivery-note-${documentType}`; @@ -118,42 +153,6 @@ function openConfirmDialog(callback) { }, }); } - -async function clone() { - const opts = { message: t('Ticket cloned'), type: 'positive' }; - let clonedTicketId; - - try { - const { data } = await axios.post(`Tickets/${ticketId}/clone`, { - shipped: ticket.value.shipped, - }); - clonedTicketId = data; - } catch (e) { - opts.message = t('It was not able to clone the ticket'); - opts.type = 'negative'; - } finally { - notify(opts); - - if (clonedTicketId) - push({ name: 'TicketSummary', params: { id: clonedTicketId } }); - } -} - -async function remove() { - try { - await axios.post(`Tickets/${ticketId}/setDeleted`); - - notify({ message: t('Ticket deleted'), type: 'positive' }); - notify({ - message: t('You can undo this action within the first hour'), - icon: 'info', - }); - - push({ name: 'TicketList' }); - } catch (e) { - notify({ message: e.message, type: 'negative' }); - } -} </script> <template> <QItem v-ripple clickable>