diff --git a/src/boot/mainShortcutMixin.js b/src/boot/mainShortcutMixin.js
new file mode 100644
index 000000000..481077e37
--- /dev/null
+++ b/src/boot/mainShortcutMixin.js
@@ -0,0 +1,36 @@
+import routes from 'src/router/modules';
+import { useRouter } from 'vue-router';
+
+let isNotified = false;
+
+export default {
+ created: function () {
+ const router = useRouter();
+ const keyBindingMap = routes
+ .filter((route) => route.meta.keyBinding)
+ .reduce((map, route) => {
+ map['Key' + route.meta.keyBinding.toUpperCase()] = route.path;
+ return map;
+ }, {});
+
+ const handleKeyDown = (event) => {
+ const { ctrlKey, altKey, code } = event;
+
+ if (ctrlKey && altKey && keyBindingMap[code] && !isNotified) {
+ event.preventDefault();
+ router.push(keyBindingMap[code]);
+ isNotified = true;
+ }
+ };
+
+ const handleKeyUp = (event) => {
+ const { ctrlKey, altKey } = event;
+ if (!ctrlKey || !altKey) {
+ isNotified = false;
+ }
+ };
+
+ window.addEventListener('keydown', handleKeyDown);
+ window.addEventListener('keyup', handleKeyUp);
+ },
+};
diff --git a/src/boot/qformMixin.js b/src/boot/qformMixin.js
index fc7852369..8a75e1af7 100644
--- a/src/boot/qformMixin.js
+++ b/src/boot/qformMixin.js
@@ -1,30 +1,52 @@
-import { getCurrentInstance } from 'vue';
-
+function focusFirstInput(input) {
+ input.focus();
+ return;
+}
export default {
mounted: function () {
- const vm = getCurrentInstance();
- if (vm.type.name === 'QForm') {
- if (!['searchbarForm', 'filterPanelForm'].includes(this.$el?.id)) {
- // TODO: AUTOFOCUS IS NOT FOCUSING
- const that = this;
- this.$el.addEventListener('keyup', function (evt) {
- if (evt.key === 'Enter') {
- const input = evt.target;
- if (input.type == 'textarea' && evt.shiftKey) {
- evt.preventDefault();
- let { selectionStart, selectionEnd } = input;
- input.value =
- input.value.substring(0, selectionStart) +
- '\n' +
- input.value.substring(selectionEnd);
- selectionStart = selectionEnd = selectionStart + 1;
- return;
- }
- evt.preventDefault();
- that.onSubmit();
- }
- });
+ const that = this;
+
+ const form = document.querySelector('.q-form#formModel');
+ if (!form) return;
+ try {
+ const inputsFormCard = form.querySelectorAll(
+ `input:not([disabled]):not([type="checkbox"])`
+ );
+ if (inputsFormCard.length) {
+ focusFirstInput(inputsFormCard[0]);
}
+ const textareas = document.querySelectorAll(
+ 'textarea:not([disabled]), [contenteditable]:not([disabled])'
+ );
+ if (textareas.length) {
+ focusFirstInput(textareas[textareas.length - 1]);
+ }
+ const inputs = document.querySelectorAll(
+ 'form#formModel input:not([disabled]):not([type="checkbox"])'
+ );
+ const input = inputs[0];
+ if (!input) return;
+
+ focusFirstInput(input);
+ } catch (error) {
+ console.error(error);
}
+ form.addEventListener('keyup', function (evt) {
+ if (evt.key === 'Enter') {
+ const input = evt.target;
+ if (input.type == 'textarea' && evt.shiftKey) {
+ evt.preventDefault();
+ let { selectionStart, selectionEnd } = input;
+ input.value =
+ input.value.substring(0, selectionStart) +
+ '\n' +
+ input.value.substring(selectionEnd);
+ selectionStart = selectionEnd = selectionStart + 1;
+ return;
+ }
+ evt.preventDefault();
+ that.onSubmit();
+ }
+ });
},
};
diff --git a/src/boot/quasar.js b/src/boot/quasar.js
index 01fe68d8b..d375c2f69 100644
--- a/src/boot/quasar.js
+++ b/src/boot/quasar.js
@@ -3,11 +3,16 @@ import qFormMixin from './qformMixin';
import keyShortcut from './keyShortcut';
import useNotify from 'src/composables/useNotify.js';
import { CanceledError } from 'axios';
+import { QForm } from 'quasar';
+import { QLayout } from 'quasar';
+import mainShortcutMixin from './mainShortcutMixin';
const { notify } = useNotify();
export default boot(({ app }) => {
- app.mixin(qFormMixin);
+ QForm.mixins = [qFormMixin];
+ QLayout.mixins = [mainShortcutMixin];
+
app.directive('shortcut', keyShortcut);
app.config.errorHandler = (error) => {
let message;
diff --git a/src/components/LeftMenu.vue b/src/components/LeftMenu.vue
index ab2931dfd..31ad9ebed 100644
--- a/src/components/LeftMenu.vue
+++ b/src/components/LeftMenu.vue
@@ -177,6 +177,7 @@ function normalize(text) {
class="full-width"
filled
dense
+ autofocus
/>
diff --git a/src/components/common/VnSelectWorker.vue b/src/components/common/VnSelectWorker.vue
new file mode 100644
index 000000000..b0fef4443
--- /dev/null
+++ b/src/components/common/VnSelectWorker.vue
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+
+
+
+ {{ $t($props.hasInfo) }}
+
+
+
+
+
+
+ {{ scope.opt.name }}
+
+
+ {{ scope.opt.nickname }}
+
+
+ {{ scope.opt.nickname }}, {{ scope.opt.code }}
+
+
+
+
+
+
+
+
+es:
+ Responsible for approving invoices: Responsable de aprobar las facturas
+
diff --git a/src/layouts/MainLayout.vue b/src/layouts/MainLayout.vue
index 754b084fc..2a84e5aa1 100644
--- a/src/layouts/MainLayout.vue
+++ b/src/layouts/MainLayout.vue
@@ -1,50 +1,10 @@
-
-
+
diff --git a/src/pages/Customer/Card/CustomerBasicData.vue b/src/pages/Customer/Card/CustomerBasicData.vue
index 1a86d9f31..768c66f32 100644
--- a/src/pages/Customer/Card/CustomerBasicData.vue
+++ b/src/pages/Customer/Card/CustomerBasicData.vue
@@ -8,7 +8,7 @@ import FormModel from 'components/FormModel.vue';
import VnRow from 'components/ui/VnRow.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
-import VnAvatar from 'src/components/ui/VnAvatar.vue';
+import VnSelectWorker from 'src/components/common/VnSelectWorker.vue';
import { getDifferences, getUpdatedValues } from 'src/filters';
const route = useRoute();
@@ -16,7 +16,6 @@ const { t } = useI18n();
const businessTypes = ref([]);
const contactChannels = ref([]);
-const title = ref();
const handleSalesModelValue = (val) => ({
or: [
{ id: val },
@@ -117,41 +116,17 @@ function onBeforeSave(formData, originalData) {
/>
-
-
-
-
-
-
-
- {{ scope.opt?.name }}
- {{ scope.opt?.nickname }},
- {{ scope.opt?.code }}
-
-
-
-
+ />
{
- {
outlined
rounded
:input-debounce="0"
- >
-
-
-
- {{ opt.name }}
-
- {{ opt.nickname }},{{ opt.code }}
-
-
-
-
+ />
diff --git a/src/pages/Customer/CustomerList.vue b/src/pages/Customer/CustomerList.vue
index 865287aeb..e86e35966 100644
--- a/src/pages/Customer/CustomerList.vue
+++ b/src/pages/Customer/CustomerList.vue
@@ -2,7 +2,6 @@
import { ref, computed, markRaw } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
-import VnSelect from 'src/components/common/VnSelect.vue';
import VnTable from 'components/VnTable/VnTable.vue';
import VnLocation from 'src/components/common/VnLocation.vue';
import VnSearchbar from 'components/ui/VnSearchbar.vue';
@@ -12,7 +11,7 @@ import RightMenu from 'src/components/common/RightMenu.vue';
import VnLinkPhone from 'src/components/ui/VnLinkPhone.vue';
import { toDate } from 'src/filters';
import CustomerFilter from './CustomerFilter.vue';
-import VnAvatar from 'src/components/ui/VnAvatar.vue';
+import VnSelectWorker from 'src/components/common/VnSelectWorker.vue';
const { t } = useI18n();
const router = useRouter();
@@ -422,40 +421,17 @@ function handleLocation(data, location) {
auto-load
>
-
-
-
-
-
-
-
- {{ scope.opt?.name }}
- {{ scope.opt?.nickname }},
- {{ scope.opt?.code }}
-
-
-
-
-
+ />
-
{
-
-
-
-
- {{ scope.opt?.name }}
- {{ scope.opt?.nickname }},
- {{ scope.opt?.code }}
-
-
-
-
+ />
diff --git a/src/pages/Monitor/Ticket/MonitorTicketFilter.vue b/src/pages/Monitor/Ticket/MonitorTicketFilter.vue
index 2205666ec..8377d73ef 100644
--- a/src/pages/Monitor/Ticket/MonitorTicketFilter.vue
+++ b/src/pages/Monitor/Ticket/MonitorTicketFilter.vue
@@ -9,6 +9,7 @@ import VnInput from 'src/components/common/VnInput.vue';
import VnInputNumber from 'src/components/common/VnInputNumber.vue';
import FetchData from 'src/components/FetchData.vue';
import { dateRange } from 'src/filters';
+import VnSelectWorker from 'src/components/common/VnSelectWorker.vue';
defineProps({ dataKey: { type: String, required: true } });
const { t, te } = useI18n();
@@ -112,33 +113,16 @@ const getLocale = (label) => {
-
-
-
-
- {{ opt.name }}
-
- {{ `${opt.nickname}, ${opt.code}` }}
-
-
-
-
-
+
diff --git a/src/pages/Order/Card/OrderFilter.vue b/src/pages/Order/Card/OrderFilter.vue
index 917369919..dc86600ac 100644
--- a/src/pages/Order/Card/OrderFilter.vue
+++ b/src/pages/Order/Card/OrderFilter.vue
@@ -6,6 +6,7 @@ import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
import VnSelect from 'components/common/VnSelect.vue';
import VnInputDate from 'components/common/VnInputDate.vue';
import VnInput from 'components/common/VnInput.vue';
+import VnSelectWorker from 'src/components/common/VnSelectWorker.vue';
const { t } = useI18n();
const props = defineProps({
@@ -61,28 +62,16 @@ const sourceList = ref([]);
outlined
rounded
/>
-
-
-
-
- {{ opt.name }}
-
- {{ opt.nickname }},{{ opt.code }}
-
-
-
-
-
+ />
-
-
-
-
- {{ opt.name }}
-
- {{ opt.nickname }},{{ opt.code }}
-
-
-
-
-
+ />
diff --git a/src/pages/Route/Card/RouteForm.vue b/src/pages/Route/Card/RouteForm.vue
index 8c89718fa..aa5caf1ef 100644
--- a/src/pages/Route/Card/RouteForm.vue
+++ b/src/pages/Route/Card/RouteForm.vue
@@ -11,6 +11,7 @@ import VnInputDate from 'components/common/VnInputDate.vue';
import VnInput from 'components/common/VnInput.vue';
import axios from 'axios';
import VnInputTime from 'components/common/VnInputTime.vue';
+import VnSelectWorker from 'src/components/common/VnSelectWorker.vue';
const { t } = useI18n();
const route = useRoute();
@@ -94,26 +95,7 @@ const onSave = (data, response) => {
>
-
-
-
-
- {{ opt.name }}
-
- {{ opt.nickname }}, {{ opt.code }}
-
-
-
-
-
+
-
-
-
- {{
- t('Responsible for approving invoices')
- }}
-
-
-
-
-
- {{ scope.opt?.name }}
-
- {{ scope.opt?.nickname }}, {{ scope.opt?.id }}
-
-
-
-
-
+ />
es:
- Responsible for approving invoices: Responsable de aprobar las facturas
Small(1-5), Medium(6-50), Big(> 50): Pequeño(1-5), Mediano(6-50), Grande(> 50)
diff --git a/src/pages/Ticket/Card/TicketCreateTracking.vue b/src/pages/Ticket/Card/TicketCreateTracking.vue
index 3ea762c6c..5c1e916f2 100644
--- a/src/pages/Ticket/Card/TicketCreateTracking.vue
+++ b/src/pages/Ticket/Card/TicketCreateTracking.vue
@@ -9,6 +9,7 @@ import VnSelect from 'src/components/common/VnSelect.vue';
import FetchData from 'components/FetchData.vue';
import { useState } from 'src/composables/useState';
+import VnSelectWorker from 'src/components/common/VnSelectWorker.vue';
const emit = defineEmits(['onRequestCreated']);
@@ -46,29 +47,7 @@ const onStateFkChange = (formData) => (formData.userFk = user.value.id);
option-label="name"
option-value="id"
/>
-
-
-
-
-
- {{ opt.name }}
-
-
- {{ opt.nickname }}, {{ opt.code }}
-
-
-
-
+
diff --git a/src/pages/Worker/Card/WorkerPda.vue b/src/pages/Worker/Card/WorkerPda.vue
index 94f4e0d95..3ceee2493 100644
--- a/src/pages/Worker/Card/WorkerPda.vue
+++ b/src/pages/Worker/Card/WorkerPda.vue
@@ -133,6 +133,7 @@ function reloadData() {
option-value="id"
id="deviceProductionFk"
hide-selected
+ data-cy="pda-dialog-select"
>
diff --git a/src/pages/Worker/WorkerCreate.vue b/src/pages/Worker/WorkerCreate.vue
index 5676837dd..a4c6c2a06 100644
--- a/src/pages/Worker/WorkerCreate.vue
+++ b/src/pages/Worker/WorkerCreate.vue
@@ -14,6 +14,7 @@ import FormModel from 'components/FormModel.vue';
import CreateBankEntityForm from 'src/components/CreateBankEntityForm.vue';
import VnRadio from 'src/components/common/VnRadio.vue';
import { useState } from 'src/composables/useState';
+import VnSelectWorker from 'src/components/common/VnSelectWorker.vue';
const { t } = useI18n();
const user = useState().getUser();
@@ -149,27 +150,11 @@ async function autofillBic(worker) {
hide-selected
:rules="validate('Worker.company')"
/>
-
-
-
-
- {{ scope.opt.name }}
- {{ scope.opt.nickname }},
- {{ scope.opt.code }}
-
-
-
-
-
+ />
-
-
-
-
- {{ scope.opt.name }}
- {{ scope.opt.nickname }},
- {{ scope.opt.code }}
-
-
-
-
-
+ />
@@ -376,6 +361,7 @@ async function autofillBic(worker) {
es:
+ Create worker: Crear trabajador
Search worker: Buscar trabajador
You can search by worker id or name: Puedes buscar por id o nombre del trabajador
diff --git a/src/pages/Zone/Card/ZoneBasicData.vue b/src/pages/Zone/Card/ZoneBasicData.vue
index 535f2393d..731e03ba7 100644
--- a/src/pages/Zone/Card/ZoneBasicData.vue
+++ b/src/pages/Zone/Card/ZoneBasicData.vue
@@ -32,7 +32,12 @@ const agencyOptions = ref([]);
-
+
diff --git a/test/cypress/integration/worker/workerPda.spec.js b/test/cypress/integration/worker/workerPda.spec.js
index fe8efa834..dc1ca6224 100644
--- a/test/cypress/integration/worker/workerPda.spec.js
+++ b/test/cypress/integration/worker/workerPda.spec.js
@@ -1,6 +1,5 @@
describe('WorkerPda', () => {
- const deviceProductionField =
- '.vn-row > .q-field > .q-field__inner > .q-field__control > .q-field__control-container';
+ const select = '[data-cy="pda-dialog-select"]';
beforeEach(() => {
cy.viewport(1920, 1080);
cy.login('developer');
@@ -9,7 +8,8 @@ describe('WorkerPda', () => {
it('assign pda', () => {
cy.get('.q-page-sticky > div > .q-btn > .q-btn__content > .q-icon').click();
- cy.get(deviceProductionField).type('{downArrow}{enter}');
+ cy.get(select).click();
+ cy.get(select).type('{downArrow}{enter}');
cy.get('.q-notification__message').should('have.text', 'Data created');
});
diff --git a/test/cypress/integration/zone/zoneBasicData.spec.js b/test/cypress/integration/zone/zoneBasicData.spec.js
index c6151a49b..de85dac94 100644
--- a/test/cypress/integration/zone/zoneBasicData.spec.js
+++ b/test/cypress/integration/zone/zoneBasicData.spec.js
@@ -8,7 +8,7 @@ describe('ZoneBasicData', () => {
});
it('should throw an error if the name is empty', () => {
- cy.get('.q-card > :nth-child(1)').clear();
+ cy.get('[data-cy="zone-basic-data-name"] input').type('{selectall}{backspace}');
cy.get('.q-btn-group > .q-btn--standard').click();
cy.get(notification).should('contains.text', "can't be blank");
});