From c621ccb5a6e35552a25bc203575554c852d9f8d2 Mon Sep 17 00:00:00 2001 From: jcasado <jcasado.verdnatura@gmail.com> Date: Tue, 23 Apr 2024 13:19:21 +0200 Subject: [PATCH 01/27] refs #7113 fix test --- test/cypress/support/commands.js | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/test/cypress/support/commands.js b/test/cypress/support/commands.js index f075d500ff..13103950cc 100755 --- a/test/cypress/support/commands.js +++ b/test/cypress/support/commands.js @@ -38,6 +38,15 @@ Cypress.Commands.add('login', (user) => { }, }).then((response) => { window.localStorage.setItem('token', response.body.token); + cy.request({ + method: 'GET', + url: '/api/VnUsers/ShareToken', + headers: { + Authorization: window.localStorage.getItem('token'), + }, + }).then(({ body }) => { + window.localStorage.setItem('tokenMultimedia', body.multimediaToken.id); + }); }); }); @@ -52,16 +61,18 @@ Cypress.Commands.add('getValue', (selector) => { } // Si es un QSelect if ($el.find('.q-select__dropdown-icon').length) { - return cy.get( - selector + - '> .q-field > .q-field__inner > .q-field__control > .q-field__control-container > .q-field__native > input' - ).invoke('val') + return cy + .get( + selector + + '> .q-field > .q-field__inner > .q-field__control > .q-field__control-container > .q-field__native > input' + ) + .invoke('val'); } // Si es un QSelect if ($el.find('span').length) { - return cy.get( - selector + ' span' - ).then(($span) => { return $span[0].innerText }) + return cy.get(selector + ' span').then(($span) => { + return $span[0].innerText; + }); } // Puedes añadir un log o lanzar un error si el elemento no es reconocido cy.log('Elemento no soportado'); @@ -132,13 +143,13 @@ Cypress.Commands.add('validateRow', (rowSelector, expectedValues) => { cy.get(rowSelector).within(() => { for (const [index, value] of expectedValues.entries()) { cy.log('CHECKING ', index, value); - if(value === undefined) continue + if (value === undefined) continue; if (typeof value == 'boolean') { const prefix = value ? '' : 'not.'; cy.getValue(`:nth-child(${index + 1})`).should(`${prefix}be.checked`); continue; } - cy.getValue(`:nth-child(${index + 1})`).should('equal', value) + cy.getValue(`:nth-child(${index + 1})`).should('equal', value); } }); }); From 934db329733c81741074a22cdb39ea37446350d6 Mon Sep 17 00:00:00 2001 From: wbuezas <wbuezas@verdnatura.es> Date: Wed, 24 Apr 2024 10:31:33 -0300 Subject: [PATCH 02/27] WIP --- src/components/CrudModel.vue | 1 + src/css/app.scss | 5 + src/pages/Item/Card/ItemTags.vue | 176 ++++++++++++++++++++++++++++++- src/router/modules/item.js | 4 +- 4 files changed, 183 insertions(+), 3 deletions(-) diff --git a/src/components/CrudModel.vue b/src/components/CrudModel.vue index fb3ac10c38..deec0e4feb 100644 --- a/src/components/CrudModel.vue +++ b/src/components/CrudModel.vue @@ -81,6 +81,7 @@ defineExpose({ hasChanges, saveChanges, getChanges, + formData, }); async function fetch(data) { diff --git a/src/css/app.scss b/src/css/app.scss index 25b4846497..9037802ada 100644 --- a/src/css/app.scss +++ b/src/css/app.scss @@ -119,6 +119,11 @@ select:-webkit-autofill { font-variation-settings: 'FILL' 1; } +.fill-icon-on-hover:hover { + font-variation-settings: 'FILL' 1; + cursor: pointer; +} + .vn-table-separation-row { height: 16px !important; background-color: var(--vn-section-color) !important; diff --git a/src/pages/Item/Card/ItemTags.vue b/src/pages/Item/Card/ItemTags.vue index 95f4380e44..bf18c682c0 100644 --- a/src/pages/Item/Card/ItemTags.vue +++ b/src/pages/Item/Card/ItemTags.vue @@ -1 +1,175 @@ -<template>Item tags (CREAR CUANDO SE DESARROLLE EL MODULO DE ITEMS)</template> +<script setup> +import { ref, onMounted, computed } from 'vue'; +import { useRoute } from 'vue-router'; +import { useI18n } from 'vue-i18n'; + +import CrudModel from 'components/CrudModel.vue'; +import VnRow from 'components/ui/VnRow.vue'; +import VnInput from 'src/components/common/VnInput.vue'; +import FetchData from 'components/FetchData.vue'; +import VnSelectFilter from 'src/components/common/VnSelectFilter.vue'; + +import axios from 'axios'; + +const route = useRoute(); +const { t } = useI18n(); + +const itemTagsRef = ref(null); +const tagOptions = ref([]); + +// const getHighestPriority = () => { +// let max = 0; +// console.log('formData:: ', itemTagsRef.value.formData); +// itemTagsRef.value.formData.forEach((tag) => { +// if (tag.priority > max) max = tag.priority; +// }); +// return max + 1; +// }; + +const getHighestPriority = computed(() => { + let max = 0; + if (!itemTagsRef.value || !itemTagsRef.value.length) return max; + console.log('formData:: ', itemTagsRef.value.formData); + itemTagsRef.value.formData.forEach((tag) => { + if (tag.priority > max) max = tag.priority; + }); + return max + 1; +}); + +const getSelectedTagValues = async (tag) => { + try { + tag.value = null; + const filter = { + fields: ['value'], + order: 'value ASC', + }; + + const params = { filter: JSON.stringify(filter) }; + const { data } = await axios.get(`Tags/${tag.selectedTag.id}/filterValue`, { + params, + }); + tag.valueOptions = data; + } catch (err) { + console.error('Error getting selected tag values'); + } +}; + +onMounted(() => { + if (itemTagsRef.value) itemTagsRef.value.reload(); +}); +</script> + +<template> + <FetchData + url="Tags" + :filter="{ fields: ['id', 'name', 'isFree', 'sourceTable'] }" + @on-fetch="(data) => (tagOptions = data)" + auto-load + /> + <div class="full-width flex justify-center"> + <QPage class="card-width q-pa-lg"> + <CrudModel + ref="itemTagsRef" + :data-required="{ + itemFk: route.params.id, + priority: getHighestPriority, + tag: { + isFree: undefined, + }, + }" + :default-remove="false" + :filter="{ + fields: ['id', 'itemFk', 'tagFk', 'value', 'priority'], + where: { itemFk: route.params.id }, + order: 'priority ASC', + include: { + relation: 'tag', + scope: { + fields: ['id', 'name', 'isFree', 'sourceTable'], + }, + }, + }" + data-key="ItemTags" + model="ItemTags" + url="ItemTags" + save-url="Tags/onSubmit" + > + <template #body="{ rows }"> + <QCard class="q-pl-lg q-py-md"> + <VnRow + v-for="(row, index) in rows" + :key="index" + class="row q-gutter-md q-mb-md" + > + <VnSelectFilter + :label="t('Tag')" + v-model="row.tagFk" + :options="tagOptions" + option-label="name" + option-value="id" + hide-selected + @update:model-value="getSelectedTagValues(value)" + /> + <VnSelectFilter + v-if="row.tag?.isFree === false" + :label="t('Value')" + v-model="row.value" + option-value="value" + option-label="value" + emit-value + use-input + :is-clearable="false" + /> + <VnInput + v-if="row.tag?.isFree || row.tag?.isFree == undefined" + v-model="row.value" + :label="t('Value')" + :is-clearable="false" + /> + <VnInput + :label="t('Relevancy')" + type="number" + v-model="row.priority" + /> + <div class="col-1 row justify-center items-center"> + <QIcon + @click="itemTagsRef.remove([row])" + class="fill-icon-on-hover" + color="primary" + name="delete" + size="sm" + > + <QTooltip> + {{ t('Remove tag') }} + </QTooltip> + </QIcon> + </div> + </VnRow> + <VnRow> + <QIcon + @click="itemTagsRef.insert()" + class="cursor-pointer" + color="primary" + name="add" + size="sm" + > + <QTooltip> + {{ t('Add tag') }} + </QTooltip> + </QIcon> + </VnRow> + </QCard> + </template> + </CrudModel> + </QPage> + </div> +</template> + +<i18n> +es: + Remove tag: Quitar etiqueta + Add tag: Añadir etiqueta + Tag: Etiqueta + Value: Valor + Relevancy: Relevancia +</i18n> diff --git a/src/router/modules/item.js b/src/router/modules/item.js index 70d49c56cb..1b582d8e40 100644 --- a/src/router/modules/item.js +++ b/src/router/modules/item.js @@ -12,7 +12,7 @@ export default { redirect: { name: 'ItemMain' }, menus: { main: ['ItemList', 'WasteBreakdown', 'ItemTypeList'], - card: ['ItemBasicData'], + card: ['ItemBasicData', 'ItemTags'], }, children: [ { @@ -98,7 +98,7 @@ export default { path: 'tags', name: 'ItemTags', meta: { - title: 'Tags', + title: 'tags', icon: 'vn:tags', }, component: () => import('src/pages/Item/Card/ItemTags.vue'), From 42591d078119e6a1051669a104c977f7c6b4abc2 Mon Sep 17 00:00:00 2001 From: Javier Segarra <jsegarra@verdnatura.es> Date: Thu, 25 Apr 2024 14:27:56 +0200 Subject: [PATCH 03/27] feat #7271 router --- src/router/modules/index.js | 2 + src/router/modules/zone.js | 139 +++++++++++++++++++++++++++++++ src/router/routes.js | 2 + src/stores/useNavigationStore.js | 1 + 4 files changed, 144 insertions(+) create mode 100644 src/router/modules/zone.js diff --git a/src/router/modules/index.js b/src/router/modules/index.js index 302ba7fe0b..2fe40038f3 100644 --- a/src/router/modules/index.js +++ b/src/router/modules/index.js @@ -15,6 +15,7 @@ import Department from './department'; import Entry from './entry'; import roadmap from './roadmap'; import Parking from './parking'; +import Zone from './zone'; export default [ Item, @@ -34,4 +35,5 @@ export default [ Entry, roadmap, Parking, + Zone, ]; diff --git a/src/router/modules/zone.js b/src/router/modules/zone.js new file mode 100644 index 0000000000..be11ced11a --- /dev/null +++ b/src/router/modules/zone.js @@ -0,0 +1,139 @@ +import { RouterView } from 'vue-router'; + +export default { + path: '/zone', + name: 'Zone', + meta: { + title: 'zones', + icon: 'vn:zone', + moduleName: 'Zone', + }, + component: RouterView, + redirect: { name: 'ZoneMain' }, + menus: { + main: ['ZoneList', 'ZoneDeliveryList', 'ZoneUpcomingList'], + card: [], + }, + children: [ + { + path: '/zone', + name: 'ZoneMain', + component: () => import('src/pages/Zone/ZoneMain.vue'), + redirect: { name: 'ZoneList' }, + children: [ + { + path: 'list', + name: 'ZoneList', + meta: { + title: 'zonesList', + icon: 'vn:zone', + }, + component: () => import('src/pages/Zone/ZoneList.vue'), + }, + { + path: 'create', + name: 'ZoneCreate', + meta: { + title: 'zoneCreate', + icon: 'create', + }, + component: () => import('src/pages/Zone/ZoneCreate.vue'), + }, + { + path: ':id/edit', + name: 'ZoneEdit', + meta: { + title: 'zoneEdit', + icon: 'edit', + }, + component: () => import('src/pages/Zone/ZoneCreate.vue'), + }, + { + path: 'counter', + name: 'ZoneCounter', + meta: { + title: 'zoneCounter', + icon: 'add_circle', + }, + component: () => import('src/pages/Zone/ZoneCounter.vue'), + }, + ], + }, + { + path: '/zone/delivery', + name: 'ZoneDeliveryMain', + component: () => import('src/pages/Zone/ZoneMain.vue'), + redirect: { name: 'ZoneDeliveryList' }, + children: [ + { + path: 'list', + name: 'ZoneDeliveryList', + meta: { + title: 'deliveryList', + icon: 'today', + }, + component: () => + import('src/pages/Zone/Delivery/ZoneDeliveryList.vue'), + }, + { + path: 'create', + name: 'ZoneDeliveryCreate', + meta: { + title: 'deliveryCreate', + icon: 'create', + }, + component: () => + import('src/pages/Zone/Delivery/ZoneDeliveryCreate.vue'), + }, + { + path: ':id/edit', + name: 'ZoneDeliveryEdit', + meta: { + title: 'deliveryEdit', + icon: 'edit', + }, + component: () => + import('src/pages/Zone/Delivery/ZoneDeliveryCreate.vue'), + }, + ], + }, + { + path: '/zone/upcoming', + name: 'ZoneUpcomingMain', + component: () => import('src/pages/Zone/ZoneMain.vue'), + redirect: { name: 'ZoneUpcomingList' }, + children: [ + { + path: 'list', + name: 'ZoneUpcomingList', + meta: { + title: 'upcomingList', + icon: 'today', + }, + component: () => + import('src/pages/Zone/Upcoming/ZoneUpcomingList.vue'), + }, + { + path: 'create', + name: 'ZoneUpcomingCreate', + meta: { + title: 'upcomingCreate', + icon: 'create', + }, + component: () => + import('src/pages/Zone/Upcoming/ZoneUpcomingCreate.vue'), + }, + { + path: ':id/edit', + name: 'ZoneUpcomingEdit', + meta: { + title: 'upcomingEdit', + icon: 'edit', + }, + component: () => + import('src/pages/Zone/Upcoming/ZoneUpcomingCreate.vue'), + }, + ], + }, + ], +}; diff --git a/src/router/routes.js b/src/router/routes.js index 51e726a62f..14bf6665f8 100644 --- a/src/router/routes.js +++ b/src/router/routes.js @@ -15,6 +15,7 @@ import order from 'src/router/modules/order'; import entry from 'src/router/modules/entry'; import roadmap from 'src/router/modules/roadmap'; import parking from 'src/router/modules/parking'; +import zone from 'src/router/modules/zone'; const routes = [ { @@ -71,6 +72,7 @@ const routes = [ roadmap, entry, parking, + zone, { path: '/:catchAll(.*)*', name: 'NotFound', diff --git a/src/stores/useNavigationStore.js b/src/stores/useNavigationStore.js index f075301f66..ee1e04e9b6 100644 --- a/src/stores/useNavigationStore.js +++ b/src/stores/useNavigationStore.js @@ -21,6 +21,7 @@ export const useNavigationStore = defineStore('navigationStore', () => { 'ticket', 'worker', 'wagon', + 'zone', ]; const pinnedModules = ref([]); const role = useRole(); From b4e3157887e6ace9072ba5f686b9d26046bf2360 Mon Sep 17 00:00:00 2001 From: Javier Segarra <jsegarra@verdnatura.es> Date: Thu, 25 Apr 2024 14:28:06 +0200 Subject: [PATCH 04/27] feat #7271 i18n --- src/i18n/locale/en.yml | 6 ++++++ src/i18n/locale/es.yml | 8 +++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/i18n/locale/en.yml b/src/i18n/locale/en.yml index ff57bf9680..aa65ce08c7 100644 --- a/src/i18n/locale/en.yml +++ b/src/i18n/locale/en.yml @@ -1165,6 +1165,12 @@ item: type: Type intrastat: Intrastat origin: Origin +zone: + pageTitles: + zones: Zone + zonesList: Zones + deliveryList: Delivery days + upcomingList: Upcoming deliveries components: topbar: {} itemsFilterPanel: diff --git a/src/i18n/locale/es.yml b/src/i18n/locale/es.yml index f9278a9b0b..da421432de 100644 --- a/src/i18n/locale/es.yml +++ b/src/i18n/locale/es.yml @@ -287,7 +287,7 @@ customer: hasSepaVnl: Recibido B2B VNL entry: pageTitles: - entries: Entradas + entries: Entrasdadas list: Listado summary: Resumen basicData: Datos básicos @@ -1164,6 +1164,12 @@ item: type: Tipo intrastat: Intrastat origin: Origen +zone: + pageTitles: + zones: Zona + zonesList: Zonas + deliveryList: Días de entrega + upcomingList: Próximos repartos components: topbar: {} itemsFilterPanel: From b1871c33fd0ad1eb86f7ca6985af6ec41a770709 Mon Sep 17 00:00:00 2001 From: Javier Segarra <jsegarra@verdnatura.es> Date: Thu, 25 Apr 2024 14:30:16 +0200 Subject: [PATCH 05/27] feat #7271 Zone Components boilerplate --- src/pages/Zone/Card/ZoneCard.vue | 6 + .../Zone/Delivery/ZoneDeliveryCreate.vue | 432 ++++++++++++++++++ src/pages/Zone/Delivery/ZoneDeliveryList.vue | 81 ++++ .../Zone/Upcoming/ZoneUpcomingCreate.vue | 432 ++++++++++++++++++ src/pages/Zone/Upcoming/ZoneUpcomingList.vue | 81 ++++ src/pages/Zone/ZoneCreate.vue | 184 ++++++++ src/pages/Zone/ZoneList.vue | 97 ++++ src/pages/Zone/ZoneMain.vue | 17 + 8 files changed, 1330 insertions(+) create mode 100644 src/pages/Zone/Card/ZoneCard.vue create mode 100644 src/pages/Zone/Delivery/ZoneDeliveryCreate.vue create mode 100644 src/pages/Zone/Delivery/ZoneDeliveryList.vue create mode 100644 src/pages/Zone/Upcoming/ZoneUpcomingCreate.vue create mode 100644 src/pages/Zone/Upcoming/ZoneUpcomingList.vue create mode 100644 src/pages/Zone/ZoneCreate.vue create mode 100644 src/pages/Zone/ZoneList.vue create mode 100644 src/pages/Zone/ZoneMain.vue diff --git a/src/pages/Zone/Card/ZoneCard.vue b/src/pages/Zone/Card/ZoneCard.vue new file mode 100644 index 0000000000..948636c55a --- /dev/null +++ b/src/pages/Zone/Card/ZoneCard.vue @@ -0,0 +1,6 @@ +<script setup> +import VnCard from 'components/common/VnCard.vue'; +</script> +<template> + <VnCard data-key="Zone" base-url="Zones" /> +</template> diff --git a/src/pages/Zone/Delivery/ZoneDeliveryCreate.vue b/src/pages/Zone/Delivery/ZoneDeliveryCreate.vue new file mode 100644 index 0000000000..a48eaf2787 --- /dev/null +++ b/src/pages/Zone/Delivery/ZoneDeliveryCreate.vue @@ -0,0 +1,432 @@ +<script setup> +import { computed, ref, onMounted, onUpdated } from 'vue'; +import { useRoute, useRouter } from 'vue-router'; +import { useQuasar } from 'quasar'; + +import VnInput from 'src/components/common/VnInput.vue'; + +import { useI18n } from 'vue-i18n'; +import axios from 'axios'; + +onMounted(() => fetch()); +onUpdated(() => fetch()); + +const { t } = useI18n(); +const route = useRoute(); +const quasar = useQuasar(); +const router = useRouter(); +const $props = defineProps({ + id: { + type: Number, + required: false, + default: null, + }, +}); +const entityId = computed(() => $props.id || route.params.id); + +const zone = ref([]); +const divisible = ref(false); +const name = ref(''); +const colorPickerActive = ref(false); +let originalData = { trays: [] }; +let zoneConfig; +let zoneDeliveryColors; +let currentTrayColorPicked; + +async function fetch() { + try { + await axios.get('ZoneConfigs').then(async (res) => { + if (res.data) { + zoneConfig = res.data[0]; + } + }); + + await axios.get(`ZoneDeliveryColors`).then(async (res) => { + if (res.data) { + zoneDeliveryColors = res.data; + if (!entityId.value) + zone.value.push({ + id: 0, + position: 0, + color: { ...zoneDeliveryColors[0] }, + action: 'add', + }); + else { + await axios + .get(`ZoneDeliveryTrays`, { + params: { filter: { where: { typeFk: entityId.value } } }, + }) + .then(async (res) => { + if (res.data) { + for (let i = 0; i < res.data.length; i++) { + const tray = res.data[i]; + zone.value.push({ + id: res.data.length - i - 1, + position: tray.height, + color: { + ...zoneDeliveryColors.find((color) => { + return color.id === tray.colorFk; + }), + }, + action: tray.height == 0 ? 'add' : 'delete', + }); + } + zone.value.forEach((value) => { + originalData.trays.push({ ...value }); + }); + } + }); + } + } + }); + + if (entityId.value) { + await axios.get(`ZoneDeliverys/${entityId.value}`).then((res) => { + if (res.data) { + originalData.name = name.value = res.data.name; + originalData.divisible = divisible.value = res.data.divisible; + } + }); + } + } catch (e) { + // + } +} + +function addTray() { + if ( + zone.value.find((tray) => { + return tray.position == null; + }) + ) { + quasar.notify({ + message: t('zone.warnings.uncompleteTrays'), + type: 'warning', + }); + return; + } + + if (zone.value.length < zoneConfig.maxTrays) { + zone.value.unshift({ + id: zone.value.length, + position: null, + color: { ...zoneDeliveryColors[0] }, + action: 'delete', + }); + } else { + quasar.notify({ + message: t('zone.warnings.maxTrays'), + type: 'warning', + }); + } +} + +function deleteTray(trayToDelete) { + zone.value = zone.value.filter((tray) => tray.id !== trayToDelete.id); + reorderIds(); +} + +function reorderIds() { + for (let index = zone.value.length - 1; index >= 0; index--) { + zone.value[index].id = index; + } +} + +async function onSubmit() { + try { + const path = entityId.value + ? 'ZoneDeliverys/editZoneDelivery' + : 'ZoneDeliverys/createZoneDelivery'; + + const params = { + id: entityId.value, + name: name.value, + divisible: divisible.value, + trays: zone.value, + }; + + await axios.patch(path, params).then((res) => { + if (res.status == 204) router.push({ path: `/zone/type/list` }); + }); + } catch (error) { + // + } +} + +function onReset() { + name.value = entityId.value ? originalData.name : null; + divisible.value = entityId.value ? originalData.divisible : false; + zone.value = entityId.value + ? [...originalData.trays] + : [ + { + id: 0, + position: 0, + color: { ...zoneDeliveryColors[0] }, + action: 'add', + }, + ]; +} + +function doAction(tray) { + if (tray.action == 'add') { + addTray(); + } else { + deleteTray(tray); + } +} + +function showColorPicker(tray) { + colorPickerActive.value = true; + currentTrayColorPicked = zone.value.findIndex((val) => { + return val.id === tray.id; + }); +} + +function updateColor(newColor) { + zone.value[currentTrayColorPicked].color = { + ...zoneDeliveryColors.find((color) => { + return color.rgb === newColor; + }), + }; +} + +function onPositionBlur(tray) { + if (tray.position) { + if (tray.position == '' || tray.position < 0) { + tray.position = null; + return; + } + tray.position = parseInt(tray.position); + zone.value.sort((a, b) => b.position - a.position); + reorderIds(); + for (let index = zone.value.length - 1; index > 0; index--) { + if (exceedMaxHeight(index - 1)) continue; + if ( + zone.value[index - 1].position - zone.value[index].position >= + zoneConfig.minHeightBetweenTrays + ) { + continue; + } else { + zone.value[index - 1].position += + zoneConfig.minHeightBetweenTrays - + (zone.value[index - 1].position - zone.value[index].position); + + quasar.notify({ + message: + t('zone.warnings.minHeightBetweenTrays') + + zoneConfig.minHeightBetweenTrays + + ' cm', + type: 'warning', + }); + + exceedMaxHeight(index - 1); + } + } + } +} + +function exceedMaxHeight(pos) { + if (zone.value[pos].position > zoneConfig.maxZoneHeight) { + zone.value.splice(pos, 1); + quasar.notify({ + message: t('zone.warnings.maxZoneHeight') + zoneConfig.maxZoneHeight + ' cm', + type: 'warning', + }); + return true; + } + return false; +} +</script> + +<template> + <QPage class="q-pa-sm q-mx-xl"> + <QForm @submit="onSubmit()" @reset="onReset()" class="q-pa-sm"> + <QCard class="q-pa-md"> + <VnInput + filled + v-model="name" + :label="t('zone.delivery.name')" + :rules="[(val) => !!val || t('zone.warnings.nameNotEmpty')]" + /> + <QCheckbox class="q-mb-sm" v-model="divisible" label="Divisible" /> + <div class="zone-tray q-mx-lg" v-for="tray in zone" :key="tray.id"> + <div class="position"> + <QInput + autofocus + filled + type="number" + :class="{ isVisible: tray.action == 'add' }" + v-model="tray.position" + @blur="onPositionBlur(tray)" + > + <QTooltip :delay="2000"> + {{ + t('zone.warnings.minHeightBetweenTrays') + + zoneConfig.minHeightBetweenTrays + + ' cm' + }} + <QSpace /> + {{ + t('zone.warnings.maxZoneHeight') + + zoneConfig.maxZoneHeight + + ' cm' + }} + </QTooltip> + </QInput> + </div> + <div class="shelving"> + <div class="shelving-half"> + <div class="shelving-up"></div> + <div + class="shelving-down" + :style="{ backgroundColor: tray.color.rgb }" + @click="showColorPicker(tray)" + ></div> + </div> + <div + class="shelving-divisible" + :class="{ isVisible: !divisible }" + ></div> + <div class="shelving-half"> + <div class="shelving-up"></div> + <div + class="shelving-down" + :style="{ backgroundColor: tray.color.rgb }" + @click="showColorPicker(tray)" + ></div> + </div> + </div> + <div class="action-button"> + <QBtn + flat + round + color="primary" + :icon="tray.action" + @click="doAction(tray)" + /> + </div> + </div> + <div class="q-mb-sm wheels"> + <QIcon color="grey-6" name="trip_origin" size="xl" /> + <QIcon color="grey-6" name="trip_origin" size="xl" /> + </div> + <QDialog + v-model="colorPickerActive" + position="right" + :no-backdrop-dismiss="false" + > + <QCard> + <QCardSection> + <div class="text-h6">{{ t('zone.delivery.trayColor') }}</div> + </QCardSection> + <QCardSection class="row items-center no-wrap"> + <QColor + flat + v-model="zone[currentTrayColorPicked].color.rgb" + no-header + no-footer + default-view="palette" + :palette=" + zoneDeliveryColors.map((color) => { + return color.rgb; + }) + " + @change="updateColor($event)" + /> + <QBtn flat round icon="close" v-close-popup /> + </QCardSection> + </QCard> + </QDialog> + </QCard> + <div class="q-mt-md"> + <QBtn :label="t('zone.delivery.submit')" type="submit" color="primary" /> + <QBtn + :label="t('zone.delivery.reset')" + type="reset" + color="primary" + flat + class="q-ml-sm" + /> + </div> + </QForm> + </QPage> +</template> + +<style lang="scss" scoped> +.q-page { + display: flex; + justify-content: center; + align-items: flex-start; +} + +.q-form { + width: 70%; +} + +.q-dialog { + .q-card { + width: 100%; + } +} + +.wheels { + margin-left: 5%; + display: flex; + justify-content: space-around; +} + +.zone-tray { + display: flex; + height: 6rem; + + .position { + width: 20%; + border-right: 1rem solid gray; + display: flex; + align-items: flex-end; + justify-content: flex-end; + padding-right: 1rem; + } + + .shelving { + display: flex; + width: 75%; + + .shelving-half { + width: 50%; + height: 100%; + + .shelving-up { + height: 80%; + width: 100%; + } + + .shelving-down { + height: 20%; + width: 100%; + } + } + + .shelving-divisible { + width: 1%; + height: 100%; + border-left: 0.5rem dashed grey; + border-right: 0.5rem dashed grey; + } + } + + .action-button { + width: 10%; + border-left: 1rem solid gray; + display: flex; + align-items: flex-end; + justify-content: flex-start; + padding-left: 1rem; + } + + .isVisible { + display: none; + } +} +</style> diff --git a/src/pages/Zone/Delivery/ZoneDeliveryList.vue b/src/pages/Zone/Delivery/ZoneDeliveryList.vue new file mode 100644 index 0000000000..c7a3cbcdbc --- /dev/null +++ b/src/pages/Zone/Delivery/ZoneDeliveryList.vue @@ -0,0 +1,81 @@ +<script setup> +import axios from 'axios'; +import { useQuasar } from 'quasar'; +import VnPaginate from 'src/components/ui/VnPaginate.vue'; +import { useArrayData } from 'src/composables/useArrayData'; +import { useI18n } from 'vue-i18n'; +import { useRouter } from 'vue-router'; +import CardList from 'components/ui/CardList.vue'; +import VnLv from 'components/ui/VnLv.vue'; + +const quasar = useQuasar(); +const arrayData = useArrayData('ZoneDeliveryList'); +const store = arrayData.store; +const router = useRouter(); +const { t } = useI18n(); + +function navigate(id) { + router.push({ path: `/zone/type/${id}/edit` }); +} + +function create() { + router.push({ path: `/zone/type/create` }); +} + +async function remove(row) { + try { + const id = row.id; + await axios + .delete(`ZoneDeliverys/deleteZoneDelivery`, { params: { id } }) + .then(async () => { + quasar.notify({ + message: t('zone.delivery.removeItem'), + type: 'positive', + }); + store.data.splice(store.data.indexOf(row), 1); + }); + } catch (error) { + // + } +} +</script> + +<template> + <QPage class="column items-center q-pa-md"> + <div class="vn-card-list"> + <VnPaginate + data-key="ZoneDeliveryList" + url="/ZoneDeliverys" + order="id DESC" + auto-load + > + <template #body="{ rows }"> + <CardList + v-for="row of rows" + :key="row.id" + :title="(row.name || '').toString()" + :id="row.id" + @click="navigate(row.id)" + > + <template #actions> + <QBtn + :label="t('components.smartCard.openCard')" + @click.stop="navigate(row.id)" + outline + /> + <QBtn + :label="t('zone.list.remove')" + @click.stop="remove(row)" + color="primary" + style="margin-top: 15px" + /> + </template> + </CardList> + </template> + </VnPaginate> + </div> + <QPageSticky position="bottom-right" :offset="[18, 18]"> + <QBtn @click="create" fab icon="add" color="primary" /> + </QPageSticky> + </QPage> +</template> diff --git a/src/pages/Zone/Upcoming/ZoneUpcomingCreate.vue b/src/pages/Zone/Upcoming/ZoneUpcomingCreate.vue new file mode 100644 index 0000000000..6bc04c4282 --- /dev/null +++ b/src/pages/Zone/Upcoming/ZoneUpcomingCreate.vue @@ -0,0 +1,432 @@ +<script setup> +import { computed, ref, onMounted, onUpdated } from 'vue'; +import { useRoute, useRouter } from 'vue-router'; +import { useQuasar } from 'quasar'; + +import VnInput from 'src/components/common/VnInput.vue'; + +import { useI18n } from 'vue-i18n'; +import axios from 'axios'; + +onMounted(() => fetch()); +onUpdated(() => fetch()); + +const { t } = useI18n(); +const route = useRoute(); +const quasar = useQuasar(); +const router = useRouter(); +const $props = defineProps({ + id: { + type: Number, + required: false, + default: null, + }, +}); +const entityId = computed(() => $props.id || route.params.id); + +const zone = ref([]); +const divisible = ref(false); +const name = ref(''); +const colorPickerActive = ref(false); +let originalData = { trays: [] }; +let zoneConfig; +let zoneUpcomingColors; +let currentTrayColorPicked; + +async function fetch() { + try { + await axios.get('ZoneConfigs').then(async (res) => { + if (res.data) { + zoneConfig = res.data[0]; + } + }); + + await axios.get(`ZoneUpcomingColors`).then(async (res) => { + if (res.data) { + zoneUpcomingColors = res.data; + if (!entityId.value) + zone.value.push({ + id: 0, + position: 0, + color: { ...zoneUpcomingColors[0] }, + action: 'add', + }); + else { + await axios + .get(`ZoneUpcomingTrays`, { + params: { filter: { where: { typeFk: entityId.value } } }, + }) + .then(async (res) => { + if (res.data) { + for (let i = 0; i < res.data.length; i++) { + const tray = res.data[i]; + zone.value.push({ + id: res.data.length - i - 1, + position: tray.height, + color: { + ...zoneUpcomingColors.find((color) => { + return color.id === tray.colorFk; + }), + }, + action: tray.height == 0 ? 'add' : 'delete', + }); + } + zone.value.forEach((value) => { + originalData.trays.push({ ...value }); + }); + } + }); + } + } + }); + + if (entityId.value) { + await axios.get(`ZoneUpcomings/${entityId.value}`).then((res) => { + if (res.data) { + originalData.name = name.value = res.data.name; + originalData.divisible = divisible.value = res.data.divisible; + } + }); + } + } catch (e) { + // + } +} + +function addTray() { + if ( + zone.value.find((tray) => { + return tray.position == null; + }) + ) { + quasar.notify({ + message: t('zone.warnings.uncompleteTrays'), + type: 'warning', + }); + return; + } + + if (zone.value.length < zoneConfig.maxTrays) { + zone.value.unshift({ + id: zone.value.length, + position: null, + color: { ...zoneUpcomingColors[0] }, + action: 'delete', + }); + } else { + quasar.notify({ + message: t('zone.warnings.maxTrays'), + type: 'warning', + }); + } +} + +function deleteTray(trayToDelete) { + zone.value = zone.value.filter((tray) => tray.id !== trayToDelete.id); + reorderIds(); +} + +function reorderIds() { + for (let index = zone.value.length - 1; index >= 0; index--) { + zone.value[index].id = index; + } +} + +async function onSubmit() { + try { + const path = entityId.value + ? 'ZoneUpcomings/editZoneUpcoming' + : 'ZoneUpcomings/createZoneUpcoming'; + + const params = { + id: entityId.value, + name: name.value, + divisible: divisible.value, + trays: zone.value, + }; + + await axios.patch(path, params).then((res) => { + if (res.status == 204) router.push({ path: `/zone/type/list` }); + }); + } catch (error) { + // + } +} + +function onReset() { + name.value = entityId.value ? originalData.name : null; + divisible.value = entityId.value ? originalData.divisible : false; + zone.value = entityId.value + ? [...originalData.trays] + : [ + { + id: 0, + position: 0, + color: { ...zoneUpcomingColors[0] }, + action: 'add', + }, + ]; +} + +function doAction(tray) { + if (tray.action == 'add') { + addTray(); + } else { + deleteTray(tray); + } +} + +function showColorPicker(tray) { + colorPickerActive.value = true; + currentTrayColorPicked = zone.value.findIndex((val) => { + return val.id === tray.id; + }); +} + +function updateColor(newColor) { + zone.value[currentTrayColorPicked].color = { + ...zoneUpcomingColors.find((color) => { + return color.rgb === newColor; + }), + }; +} + +function onPositionBlur(tray) { + if (tray.position) { + if (tray.position == '' || tray.position < 0) { + tray.position = null; + return; + } + tray.position = parseInt(tray.position); + zone.value.sort((a, b) => b.position - a.position); + reorderIds(); + for (let index = zone.value.length - 1; index > 0; index--) { + if (exceedMaxHeight(index - 1)) continue; + if ( + zone.value[index - 1].position - zone.value[index].position >= + zoneConfig.minHeightBetweenTrays + ) { + continue; + } else { + zone.value[index - 1].position += + zoneConfig.minHeightBetweenTrays - + (zone.value[index - 1].position - zone.value[index].position); + + quasar.notify({ + message: + t('zone.warnings.minHeightBetweenTrays') + + zoneConfig.minHeightBetweenTrays + + ' cm', + type: 'warning', + }); + + exceedMaxHeight(index - 1); + } + } + } +} + +function exceedMaxHeight(pos) { + if (zone.value[pos].position > zoneConfig.maxZoneHeight) { + zone.value.splice(pos, 1); + quasar.notify({ + message: t('zone.warnings.maxZoneHeight') + zoneConfig.maxZoneHeight + ' cm', + type: 'warning', + }); + return true; + } + return false; +} +</script> + +<template> + <QPage class="q-pa-sm q-mx-xl"> + <QForm @submit="onSubmit()" @reset="onReset()" class="q-pa-sm"> + <QCard class="q-pa-md"> + <VnInput + filled + v-model="name" + :label="t('zone.upcoming.name')" + :rules="[(val) => !!val || t('zone.warnings.nameNotEmpty')]" + /> + <QCheckbox class="q-mb-sm" v-model="divisible" label="Divisible" /> + <div class="zone-tray q-mx-lg" v-for="tray in zone" :key="tray.id"> + <div class="position"> + <QInput + autofocus + filled + type="number" + :class="{ isVisible: tray.action == 'add' }" + v-model="tray.position" + @blur="onPositionBlur(tray)" + > + <QTooltip :delay="2000"> + {{ + t('zone.warnings.minHeightBetweenTrays') + + zoneConfig.minHeightBetweenTrays + + ' cm' + }} + <QSpace /> + {{ + t('zone.warnings.maxZoneHeight') + + zoneConfig.maxZoneHeight + + ' cm' + }} + </QTooltip> + </QInput> + </div> + <div class="shelving"> + <div class="shelving-half"> + <div class="shelving-up"></div> + <div + class="shelving-down" + :style="{ backgroundColor: tray.color.rgb }" + @click="showColorPicker(tray)" + ></div> + </div> + <div + class="shelving-divisible" + :class="{ isVisible: !divisible }" + ></div> + <div class="shelving-half"> + <div class="shelving-up"></div> + <div + class="shelving-down" + :style="{ backgroundColor: tray.color.rgb }" + @click="showColorPicker(tray)" + ></div> + </div> + </div> + <div class="action-button"> + <QBtn + flat + round + color="primary" + :icon="tray.action" + @click="doAction(tray)" + /> + </div> + </div> + <div class="q-mb-sm wheels"> + <QIcon color="grey-6" name="trip_origin" size="xl" /> + <QIcon color="grey-6" name="trip_origin" size="xl" /> + </div> + <QDialog + v-model="colorPickerActive" + position="right" + :no-backdrop-dismiss="false" + > + <QCard> + <QCardSection> + <div class="text-h6">{{ t('zone.upcoming.trayColor') }}</div> + </QCardSection> + <QCardSection class="row items-center no-wrap"> + <QColor + flat + v-model="zone[currentTrayColorPicked].color.rgb" + no-header + no-footer + default-view="palette" + :palette=" + zoneUpcomingColors.map((color) => { + return color.rgb; + }) + " + @change="updateColor($event)" + /> + <QBtn flat round icon="close" v-close-popup /> + </QCardSection> + </QCard> + </QDialog> + </QCard> + <div class="q-mt-md"> + <QBtn :label="t('zone.upcoming.submit')" type="submit" color="primary" /> + <QBtn + :label="t('zone.upcoming.reset')" + type="reset" + color="primary" + flat + class="q-ml-sm" + /> + </div> + </QForm> + </QPage> +</template> + +<style lang="scss" scoped> +.q-page { + display: flex; + justify-content: center; + align-items: flex-start; +} + +.q-form { + width: 70%; +} + +.q-dialog { + .q-card { + width: 100%; + } +} + +.wheels { + margin-left: 5%; + display: flex; + justify-content: space-around; +} + +.zone-tray { + display: flex; + height: 6rem; + + .position { + width: 20%; + border-right: 1rem solid gray; + display: flex; + align-items: flex-end; + justify-content: flex-end; + padding-right: 1rem; + } + + .shelving { + display: flex; + width: 75%; + + .shelving-half { + width: 50%; + height: 100%; + + .shelving-up { + height: 80%; + width: 100%; + } + + .shelving-down { + height: 20%; + width: 100%; + } + } + + .shelving-divisible { + width: 1%; + height: 100%; + border-left: 0.5rem dashed grey; + border-right: 0.5rem dashed grey; + } + } + + .action-button { + width: 10%; + border-left: 1rem solid gray; + display: flex; + align-items: flex-end; + justify-content: flex-start; + padding-left: 1rem; + } + + .isVisible { + display: none; + } +} +</style> diff --git a/src/pages/Zone/Upcoming/ZoneUpcomingList.vue b/src/pages/Zone/Upcoming/ZoneUpcomingList.vue new file mode 100644 index 0000000000..5c417df8fd --- /dev/null +++ b/src/pages/Zone/Upcoming/ZoneUpcomingList.vue @@ -0,0 +1,81 @@ +<script setup> +import axios from 'axios'; +import { useQuasar } from 'quasar'; +import VnPaginate from 'src/components/ui/VnPaginate.vue'; +import { useArrayData } from 'src/composables/useArrayData'; +import { useI18n } from 'vue-i18n'; +import { useRouter } from 'vue-router'; +import CardList from 'components/ui/CardList.vue'; +import VnLv from 'components/ui/VnLv.vue'; + +const quasar = useQuasar(); +const arrayData = useArrayData('ZoneUpcomingList'); +const store = arrayData.store; +const router = useRouter(); +const { t } = useI18n(); + +function navigate(id) { + router.push({ path: `/zone/type/${id}/edit` }); +} + +function create() { + router.push({ path: `/zone/type/create` }); +} + +async function remove(row) { + try { + const id = row.id; + await axios + .delete(`ZoneUpcomings/deleteZoneUpcoming`, { params: { id } }) + .then(async () => { + quasar.notify({ + message: t('zone.upcoming.removeItem'), + type: 'positive', + }); + store.data.splice(store.data.indexOf(row), 1); + }); + } catch (error) { + // + } +} +</script> + +<template> + <QPage class="column items-center q-pa-md"> + <div class="vn-card-list"> + <VnPaginate + data-key="ZoneUpcomingList" + url="/ZoneUpcomings" + order="id DESC" + auto-load + > + <template #body="{ rows }"> + <CardList + v-for="row of rows" + :key="row.id" + :title="(row.name || '').toString()" + :id="row.id" + @click="navigate(row.id)" + > + <template #actions> + <QBtn + :label="t('components.smartCard.openCard')" + @click.stop="navigate(row.id)" + outline + /> + <QBtn + :label="t('zone.list.remove')" + @click.stop="remove(row)" + color="primary" + style="margin-top: 15px" + /> + </template> + </CardList> + </template> + </VnPaginate> + </div> + <QPageSticky position="bottom-right" :offset="[18, 18]"> + <QBtn @click="create" fab icon="add" color="primary" /> + </QPageSticky> + </QPage> +</template> diff --git a/src/pages/Zone/ZoneCreate.vue b/src/pages/Zone/ZoneCreate.vue new file mode 100644 index 0000000000..8c0ba8c176 --- /dev/null +++ b/src/pages/Zone/ZoneCreate.vue @@ -0,0 +1,184 @@ +<script setup> +import { computed, onMounted, onUpdated, ref } from 'vue'; +import { useI18n } from 'vue-i18n'; +import { QIcon, QInput, QItem, QItemSection, QSelect } from 'quasar'; + +import VnInput from 'src/components/common/VnInput.vue'; + +import { useRoute, useRouter } from 'vue-router'; +import axios from 'axios'; + +onMounted(() => fetch()); +onUpdated(() => fetch()); + +const { t } = useI18n(); +const route = useRoute(); +const router = useRouter(); +const $props = defineProps({ + id: { + type: Number, + required: false, + default: null, + }, +}); +const entityId = computed(() => $props.id || route.params.id); + +let zoneTypes = []; +let originalData = {}; +const zone = ref({}); +const filteredZoneTypes = ref(zoneTypes); + +async function onSubmit() { + try { + const params = { + id: entityId.value, + label: zone.value.label, + plate: zone.value.plate, + volume: zone.value.volume, + typeFk: zone.value.typeFk, + }; + await axios.patch('Zones', params).then((res) => { + if (res.status == 200) router.push({ path: `/zone/list` }); + }); + } catch (error) { + // + } +} + +async function onReset() { + if (entityId.value) { + zone.value = { ...originalData }; + } else { + zone.value = {}; + } +} + +async function fetch() { + try { + await axios.get('ZoneTypes').then(async (res) => { + if (res.data) { + filteredZoneTypes.value = zoneTypes = res.data; + } + }); + if (entityId.value) { + await axios.get(`Zones/${entityId.value}`).then(async (res) => { + const data = res.data; + if (data) { + zone.value.label = data.label; + zone.value.plate = data.plate; + zone.value.volume = data.volume; + zone.value.typeFk = data.typeFk; + originalData = { ...zone.value }; + } + }); + } + } catch (e) { + // + } +} + +function filterType(val, update) { + update(() => { + const needle = val.toLowerCase(); + filteredZoneTypes.value = zoneTypes.filter( + (v) => v.name.toLowerCase().indexOf(needle) > -1 + ); + }); +} +</script> + +<template> + <QPage class="q-pa-sm q-mx-xl"> + <QForm @submit="onSubmit()" @reset="onReset()" class="q-pa-sm"> + <QCard class="q-pa-md"> + <div class="row q-col-gutter-md"> + <div class="col"> + <QInput + filled + v-model="zone.label" + :label="t('zone.create.label')" + type="number" + min="0" + :rules="[(val) => !!val || t('zone.warnings.labelNotEmpty')]" + /> + </div> + <div class="col"> + <VnInput + filled + v-model="zone.plate" + :label="t('zone.create.plate')" + :rules="[(val) => !!val || t('zone.warnings.plateNotEmpty')]" + /> + </div> + </div> + <div class="row q-col-gutter-md"> + <div class="col"> + <QInput + filled + v-model="zone.volume" + :label="t('zone.create.volume')" + type="number" + min="0" + :rules="[(val) => !!val || t('zone.warnings.volumeNotEmpty')]" + /> + </div> + <div class="col"> + <QSelect + filled + v-model="zone.typeFk" + use-input + fill-input + hide-selected + input-debounce="0" + option-label="name" + option-value="id" + emit-value + map-options + :label="t('zone.create.type')" + :options="filteredZoneTypes" + :rules="[(val) => !!val || t('zone.warnings.typeNotEmpty')]" + @filter="filterType" + > + <template v-if="zone.typeFk" #append> + <QIcon + name="cancel" + @click.stop.prevent="zone.typeFk = null" + class="cursor-pointer" + /> + </template> + <template #no-option> + <QItem> + <QItemSection class="text-grey"> + {{ t('zone.warnings.noData') }} + </QItemSection> + </QItem> + </template> + </QSelect> + </div> + </div> + </QCard> + <div class="q-mt-md"> + <QBtn :label="t('zone.type.submit')" type="submit" color="primary" /> + <QBtn + :label="t('zone.type.reset')" + type="reset" + color="primary" + flat + class="q-ml-sm" + /> + </div> + </QForm> + </QPage> +</template> + +<style lang="scss" scoped> +.q-page { + display: flex; + justify-content: center; + align-items: flex-start; +} + +.q-form { + width: 70%; +} +</style> diff --git a/src/pages/Zone/ZoneList.vue b/src/pages/Zone/ZoneList.vue new file mode 100644 index 0000000000..00502e1f7c --- /dev/null +++ b/src/pages/Zone/ZoneList.vue @@ -0,0 +1,97 @@ +<script setup> +import axios from 'axios'; +import { useQuasar } from 'quasar'; +import VnPaginate from 'src/components/ui/VnPaginate.vue'; +import { useArrayData } from 'src/composables/useArrayData'; +import { useI18n } from 'vue-i18n'; +import { useRouter } from 'vue-router'; +import CardList from 'components/ui/CardList.vue'; +import VnLv from 'components/ui/VnLv.vue'; + +const quasar = useQuasar(); +const arrayData = useArrayData('ZoneList'); +const store = arrayData.store; +const router = useRouter(); +const { t } = useI18n(); + +const filter = { + include: { + relation: 'type', + scope: { + fields: 'name', + }, + }, +}; + +function navigate(id) { + router.push({ path: `/zone/${id}/edit` }); +} + +function create() { + router.push({ path: `/zone/create` }); +} + +async function remove(row) { + try { + await axios.delete(`Zones/${row.id}`).then(async () => { + quasar.notify({ + message: t('zone.list.removeItem'), + type: 'positive', + }); + store.data.splice(store.data.indexOf(row), 1); + }); + } catch (error) { + // + } +} +</script> + +<template> + <QPage class="column items-center q-pa-md"> + <div class="vn-card-list"> + <VnPaginate + data-key="ZoneList" + url="/Zones" + order="id DESC" + :filter="filter" + auto-load + > + <template #body="{ rows }"> + <CardList + v-for="row of rows" + :key="row.id" + :title="(row.label || '').toString()" + :id="row.id" + @click="navigate(row.id)" + > + <template #list-items> + <VnLv + :label="t('zone.list.plate')" + :title-label="t('zone.list.plate')" + :value="row.plate" + /> + <VnLv :label="t('zone.list.volume')" :value="row?.volume" /> + <VnLv :label="t('zone.list.type')" :value="row?.type?.name" /> + </template> + <template #actions> + <QBtn + :label="t('components.smartCard.openCard')" + @click.stop="navigate(row.id)" + outline + /> + <QBtn + :label="t('zone.list.remove')" + @click.stop="remove(row)" + color="primary" + style="margin-top: 15px" + /> + </template> + </CardList> + </template> + </VnPaginate> + </div> + <QPageSticky position="bottom-right" :offset="[18, 18]"> + <QBtn @click="create" fab icon="add" color="primary" /> + </QPageSticky> + </QPage> +</template> diff --git a/src/pages/Zone/ZoneMain.vue b/src/pages/Zone/ZoneMain.vue new file mode 100644 index 0000000000..66ce78f232 --- /dev/null +++ b/src/pages/Zone/ZoneMain.vue @@ -0,0 +1,17 @@ +<script setup> +import { useStateStore } from 'stores/useStateStore'; +import LeftMenu from 'src/components/LeftMenu.vue'; + +const stateStore = useStateStore(); +</script> + +<template> + <QDrawer v-model="stateStore.leftDrawer" show-if-above :width="256"> + <QScrollArea class="fit text-grey-8"> + <LeftMenu /> + </QScrollArea> + </QDrawer> + <QPageContainer> + <RouterView></RouterView> + </QPageContainer> +</template> From f14d6310514c949c81de6ac07083dd686e37e87b Mon Sep 17 00:00:00 2001 From: Javier Segarra <jsegarra@verdnatura.es> Date: Thu, 25 Apr 2024 14:32:42 +0200 Subject: [PATCH 06/27] feat #7271 ZoneDescriptor --- src/pages/Zone/Card/ZoneDescriptor.vue | 127 ++++++++++++++++++ .../Zone/Card/ZoneDescriptorMenuItems.vue | 108 +++++++++++++++ src/pages/Zone/Card/ZoneDescriptorProxy.vue | 16 +++ 3 files changed, 251 insertions(+) create mode 100644 src/pages/Zone/Card/ZoneDescriptor.vue create mode 100644 src/pages/Zone/Card/ZoneDescriptorMenuItems.vue create mode 100644 src/pages/Zone/Card/ZoneDescriptorProxy.vue diff --git a/src/pages/Zone/Card/ZoneDescriptor.vue b/src/pages/Zone/Card/ZoneDescriptor.vue new file mode 100644 index 0000000000..665cb6f0e6 --- /dev/null +++ b/src/pages/Zone/Card/ZoneDescriptor.vue @@ -0,0 +1,127 @@ +<script setup> +import { ref, computed } from 'vue'; +import { useRoute } from 'vue-router'; +import { useI18n } from 'vue-i18n'; + +import CardDescriptor from 'components/ui/CardDescriptor.vue'; +import VnLv from 'src/components/ui/VnLv.vue'; +import ZoneDescriptorMenuItems from './ZoneDescriptorMenuItems.vue'; + +import useCardDescription from 'src/composables/useCardDescription'; +import { toDate } from 'src/filters'; + +const $props = defineProps({ + id: { + type: Number, + required: false, + default: null, + }, +}); + +const route = useRoute(); +const { t } = useI18n(); + +const filter = { + fields: [ + 'id', + 'ref', + 'shipped', + 'landed', + 'totalEntries', + 'warehouseInFk', + 'warehouseOutFk', + 'cargoSupplierFk', + 'agencyModeFk', + ], + include: [ + { + relation: 'warehouseIn', + scope: { + fields: ['name'], + }, + }, + { + relation: 'warehouseOut', + scope: { + fields: ['name'], + }, + }, + ], +}; + +const entityId = computed(() => { + return $props.id || route.params.id; +}); + +const data = ref(useCardDescription()); + +const setData = (entity) => { + data.value = useCardDescription(entity.ref, entity.id); +}; +</script> + +<template> + <CardDescriptor + module="Zone" + :url="`Zones/${entityId}`" + :title="data.title" + :subtitle="data.subtitle" + :filter="filter" + @on-fetch="setData" + data-key="travelData" + > + <template #header-extra-action> + <QBtn + round + flat + dense + size="md" + icon="local_airport" + color="white" + class="link" + :to="{ name: 'ZoneList' }" + > + <QTooltip> + {{ t('Go to module index') }} + </QTooltip> + </QBtn> + </template> + <template #menu="{ entity }"> + <ZoneDescriptorMenuItems :travel="entity" /> + </template> + <template #body="{ entity }"> + <VnLv :label="t('globals.wareHouseIn')" :value="entity.warehouseIn.name" /> + <VnLv :label="t('globals.wareHouseOut')" :value="entity.warehouseOut.name" /> + <VnLv :label="t('globals.shipped')" :value="toDate(entity.shipped)" /> + <VnLv :label="t('globals.landed')" :value="toDate(entity.landed)" /> + <VnLv :label="t('globals.totalEntries')" :value="entity.totalEntries" /> + </template> + <template #actions="{ entity }"> + <QCardActions> + <QBtn + :to="{ + name: 'ZoneList', + query: { + params: JSON.stringify({ + agencyModeFk: entity.agencyModeFk, + }), + }, + }" + size="md" + icon="local_airport" + color="primary" + > + <QTooltip>{{ t('All travels with current agency') }}</QTooltip> + </QBtn> + </QCardActions> + </template> + </CardDescriptor> +</template> + +<i18n> +es: + Go to module index: Ir al índice del módulo + The travel will be deleted: El envío será eliminado + Do you want to delete this travel?: ¿Quieres eliminar este envío? + All travels with current agency: Todos los envíos con la agencia actual +</i18n> diff --git a/src/pages/Zone/Card/ZoneDescriptorMenuItems.vue b/src/pages/Zone/Card/ZoneDescriptorMenuItems.vue new file mode 100644 index 0000000000..920d83dfe9 --- /dev/null +++ b/src/pages/Zone/Card/ZoneDescriptorMenuItems.vue @@ -0,0 +1,108 @@ +<script setup> +import { computed } from 'vue'; +import { useQuasar } from 'quasar'; +import { useRouter } from 'vue-router'; +import { useI18n } from 'vue-i18n'; + +import VnConfirm from 'components/ui/VnConfirm.vue'; + +import axios from 'axios'; +import useNotify from 'src/composables/useNotify.js'; +import { useRole } from 'src/composables/useRole'; + +const $props = defineProps({ + travel: { + type: Object, + default: () => {}, + }, +}); + +const { t } = useI18n(); +const router = useRouter(); +const quasar = useQuasar(); +const { notify } = useNotify(); +const role = useRole(); + +const redirectToCreateView = (queryParams) => { + router.push({ name: 'ZoneCreate', query: { travelData: queryParams } }); +}; + +const cloneZone = () => { + const stringifiedZoneData = JSON.stringify($props.travel); + redirectToCreateView(stringifiedZoneData); +}; + +const cloneZoneWithEntries = () => { + try { + axios.post(`Zones/${$props.travel.id}/cloneWithEntries`); + notify('globals.dataSaved', 'positive'); + } catch (err) { + console.err('Error cloning travel with entries'); + } +}; + +const isBuyer = computed(() => { + return role.hasAny(['buyer']); +}); + +const openDeleteEntryDialog = (id) => { + quasar + .dialog({ + component: VnConfirm, + componentProps: { + title: t('The travel will be deleted'), + message: t('Do you want to delete this travel?'), + }, + }) + .onOk(async () => { + await deleteZone(id); + }); +}; + +const deleteZone = async (id) => { + try { + await axios.delete(`Zones/${id}`); + router.push({ name: 'ZoneList' }); + notify('globals.dataDeleted', 'positive'); + } catch (err) { + console.error('Error deleting travel'); + } +}; +</script> + +<template> + <QItem v-ripple clickable @click="cloneZone(travel)"> + <QItemSection>{{ t('travel.summary.cloneShipping') }}</QItemSection> + </QItem> + <QItem v-ripple clickable @click="cloneZoneWithEntries()"> + <QItemSection> + {{ t('travel.summary.CloneZoneAndEntries') }} + </QItemSection> + </QItem> + <QItem + v-if="isBuyer && travel.totalEntries === 0" + v-ripple + clickable + @click="openDeleteEntryDialog(travel.id)" + > + <QItemSection> + {{ t('travel.summary.deleteZone') }} + </QItemSection> + </QItem> + <QItem v-ripple clickable> + <QItemSection> + <RouterLink + :to="{ name: 'EntryCreate', query: { travelFk: travel.id } }" + class="color-vn-text" + > + {{ t('travel.summary.AddEntry') }} + </RouterLink> + </QItemSection> + </QItem> +</template> + +<i18n> +es: + The travel will be deleted: El envío será eliminado + Do you want to delete this travel?: ¿Quieres eliminar este envío? +</i18n> diff --git a/src/pages/Zone/Card/ZoneDescriptorProxy.vue b/src/pages/Zone/Card/ZoneDescriptorProxy.vue new file mode 100644 index 0000000000..15c5fb0e54 --- /dev/null +++ b/src/pages/Zone/Card/ZoneDescriptorProxy.vue @@ -0,0 +1,16 @@ +<script setup> +import ZoneDescriptor from './ZoneDescriptor.vue'; + +const $props = defineProps({ + id: { + type: Number, + required: true, + }, +}); +</script> + +<template> + <QPopupProxy> + <ZoneDescriptor v-if="$props.id" :id="$props.id" /> + </QPopupProxy> +</template> From ac219bfbb79d748b9f19d51d816d457af235368e Mon Sep 17 00:00:00 2001 From: wbuezas <wbuezas@verdnatura.es> Date: Tue, 30 Apr 2024 16:15:48 -0300 Subject: [PATCH 07/27] Item shelvings --- src/i18n/locale/en.yml | 1 + src/i18n/locale/es.yml | 1 + src/pages/Item/Card/ItemShelving.vue | 275 +++++++++++++++++++++++++++ src/pages/Item/locale/en.yml | 14 ++ src/pages/Item/locale/es.yml | 14 ++ src/router/modules/item.js | 10 + 6 files changed, 315 insertions(+) create mode 100644 src/pages/Item/Card/ItemShelving.vue create mode 100644 src/pages/Item/locale/en.yml create mode 100644 src/pages/Item/locale/es.yml diff --git a/src/i18n/locale/en.yml b/src/i18n/locale/en.yml index abe59fe18d..1000fd3089 100644 --- a/src/i18n/locale/en.yml +++ b/src/i18n/locale/en.yml @@ -1135,6 +1135,7 @@ item: tax: Tax barcode: Barcode botanical: Botanical + shelving: Shelving descriptor: item: Item buyer: Buyer diff --git a/src/i18n/locale/es.yml b/src/i18n/locale/es.yml index 06aa057e39..2727b91135 100644 --- a/src/i18n/locale/es.yml +++ b/src/i18n/locale/es.yml @@ -1134,6 +1134,7 @@ item: botanical: 'Botánico' barcode: 'Código de barras' log: Historial + shelving: Carros descriptor: item: Artículo buyer: Comprador diff --git a/src/pages/Item/Card/ItemShelving.vue b/src/pages/Item/Card/ItemShelving.vue new file mode 100644 index 0000000000..573c4be0c0 --- /dev/null +++ b/src/pages/Item/Card/ItemShelving.vue @@ -0,0 +1,275 @@ +<script setup> +import { onMounted, ref, computed, reactive } from 'vue'; +import { useI18n } from 'vue-i18n'; +import { useRoute } from 'vue-router'; + +import FetchData from 'components/FetchData.vue'; +import VnInput from 'src/components/common/VnInput.vue'; +import VnSelect from 'src/components/common/VnSelect.vue'; +import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue'; + +import { toDateFormat } from 'src/filters/date.js'; +import { dashIfEmpty } from 'src/filters'; +import { useArrayData } from 'src/composables/useArrayData'; +import useNotify from 'src/composables/useNotify.js'; +import { useVnConfirm } from 'composables/useVnConfirm'; +import axios from 'axios'; + +const route = useRoute(); +const { t } = useI18n(); +const { notify } = useNotify(); +const { openConfirmationModal } = useVnConfirm(); + +const rowsSelected = ref([]); +const parkingsOptions = ref([]); +const shelvingsOptions = ref([]); + +const exprBuilder = (param, value) => { + switch (param) { + case 'parking': + case 'shelving': + case 'label': + case 'packing': + case 'itemFk': + return { [param]: value }; + } +}; + +const params = reactive({ itemFk: route.params.id }); + +const arrayData = useArrayData('ItemShelvings', { + url: 'ItemShelvingPlacementSupplyStocks', + userParams: params, + exprBuilder: exprBuilder, +}); +const rows = computed(() => arrayData.store.data || []); + +const applyColumnFilter = async (col) => { + try { + const paramKey = col.columnFilter?.filterParamKey || col.field; + params[paramKey] = col.columnFilter.filterValue; + await arrayData.addFilter({ filter: null, params }); + } catch (err) { + console.error('Error applying column filter', err); + } +}; + +const getInputEvents = (col) => { + return col.columnFilter.type === 'select' + ? { 'update:modelValue': () => applyColumnFilter(col) } + : { + 'keyup.enter': () => applyColumnFilter(col), + }; +}; + +const columns = computed(() => [ + { + label: t('shelvings.created'), + name: 'created', + field: 'created', + align: 'left', + sortable: true, + columnFilter: null, + format: (val) => toDateFormat(val), + }, + + { + label: t('shelvings.item'), + name: 'item', + field: 'itemFk', + align: 'left', + sortable: true, + columnFilter: null, + }, + { + label: t('shelvings.concept'), + name: 'concept', + align: 'left', + sortable: true, + columnFilter: null, + }, + { + label: t('shelvings.parking'), + name: 'parking', + field: 'parking', + align: 'left', + sortable: true, + format: (val) => dashIfEmpty(val), + columnFilter: { + component: VnSelect, + type: 'select', + filterValue: null, + event: getInputEvents, + attrs: { + options: parkingsOptions.value, + 'option-value': 'code', + 'option-label': 'code', + dense: true, + }, + }, + }, + { + label: t('shelvings.shelving'), + name: 'shelving', + field: 'shelving', + align: 'left', + sortable: true, + format: (val) => dashIfEmpty(val), + columnFilter: { + component: VnSelect, + type: 'select', + filterValue: null, + event: getInputEvents, + attrs: { + options: shelvingsOptions.value, + 'option-value': 'code', + 'option-label': 'code', + dense: true, + }, + }, + }, + { + label: t('shelvings.label'), + name: 'label', + align: 'left', + sortable: true, + format: (_, row) => (row.stock / row.packing).toFixed(2), + columnFilter: { + component: VnInput, + type: 'text', + filterParamKey: 'label', + filterValue: null, + event: getInputEvents, + attrs: { + dense: true, + }, + }, + }, + { + label: t('shelvings.packing'), + field: 'packing', + name: 'packing', + align: 'left', + sortable: true, + columnFilter: { + component: VnInput, + type: 'text', + filterValue: null, + event: getInputEvents, + attrs: { + dense: true, + }, + }, + format: (val) => dashIfEmpty(val), + }, +]); + +const totalLabels = computed(() => + rows.value.reduce((acc, row) => acc + row.stock / row.packing, 0).toFixed(2) +); + +const removeLines = async () => { + try { + const itemShelvingIds = rowsSelected.value.map((row) => row.itemShelvingFk); + await axios.post('ItemShelvings/deleteItemShelvings', { itemShelvingIds }); + rowsSelected.value = []; + notify('shelvings.shelvingsRemoved', 'positive'); + await arrayData.fetch({ append: false }); + } catch (err) { + console.error('Error removing lines', err); + } +}; +onMounted(async () => { + await arrayData.fetch({ append: false }); +}); +</script> + +<template> + <FetchData + url="parkings" + :filter="{ fields: ['code'], order: 'code ASC' }" + auto-load + @on-fetch="(data) => (parkingsOptions = data)" + /> + <FetchData + url="shelvings" + :filter="{ fields: ['code'], order: 'code ASC' }" + auto-load + @on-fetch="(data) => (shelvingsOptions = data)" + /> + <QToolbar class="bg-vn-dark justify-end"> + <div id="st-data" class="q-py-sm flex items-center"> + <div class="q-pa-md q-mr-lg" style="border: 2px solid #222"> + <QCardSection horizontal> + <span class="text-weight-bold text-subtitle1 text-center full-width"> + {{ t('shelvings.total') }} + </span> + </QCardSection> + <QCardSection class="column items-center" horizontal> + <div> + <span class="details-label" + >{{ t('shelvings.totalLabels') }} + </span> + <span>: {{ totalLabels }}</span> + </div> + </QCardSection> + </div> + <QBtn + color="primary" + icon="delete" + :disabled="!rowsSelected.length" + @click=" + openConfirmationModal( + t('shelvings.removeConfirmTitle'), + t('shelvings.removeConfirmSubtitle'), + removeLines + ) + " + > + <QTooltip> + {{ t('shelvings.removeLines') }} + </QTooltip> + </QBtn> + </div> + <QSpace /> + <div id="st-actions"></div> + </QToolbar> + <QPage class="column items-center q-pa-md"> + <QTable + :rows="rows" + :columns="columns" + row-key="id" + :pagination="{ rowsPerPage: 0 }" + class="full-width q-mt-md" + selection="multiple" + v-model:selected="rowsSelected" + :no-data-label="t('globals.noResults')" + > + <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 #body-cell-concept="{ row }"> + <QTd @click.stop> + <span class="link">{{ row.longName }}</span> + <ItemDescriptorProxy :id="row.itemFk" /> + </QTd> + </template> + </QTable> + </QPage> +</template> diff --git a/src/pages/Item/locale/en.yml b/src/pages/Item/locale/en.yml new file mode 100644 index 0000000000..19bbd7f06c --- /dev/null +++ b/src/pages/Item/locale/en.yml @@ -0,0 +1,14 @@ +shelvings: + created: Created + item: Item + concept: Concept + parking: Parking + shelving: Shelving + label: Label + packing: Packing + total: Total + totalLabels: Total labels + removeLines: Remove selected lines + shelvingsRemoved: ItemShelvings removed + removeConfirmTitle: Selected lines will be deleted + removeConfirmSubtitle: Are you sure you want to continue? diff --git a/src/pages/Item/locale/es.yml b/src/pages/Item/locale/es.yml new file mode 100644 index 0000000000..6f99bb4bec --- /dev/null +++ b/src/pages/Item/locale/es.yml @@ -0,0 +1,14 @@ +shelvings: + created: Creado + item: Artículo + concept: Concepto + parking: Parking + shelving: Matrícula + label: Etiqueta + packing: Packing + total: Total + totalLabels: Total etiquetas + removeLines: Eliminar líneas seleccionadas + shelvingsRemoved: Carros eliminados + removeConfirmTitle: Las líneas seleccionadas serán eliminadas + removeConfirmSubtitle: ¿Seguro que quieres continuar? diff --git a/src/router/modules/item.js b/src/router/modules/item.js index bc1e72a948..59bd07d209 100644 --- a/src/router/modules/item.js +++ b/src/router/modules/item.js @@ -20,6 +20,7 @@ export default { 'ItemTax', 'ItemBotanical', 'ItemBarcode', + 'ItemShelving', ], }, children: [ @@ -157,6 +158,15 @@ export default { }, component: () => import('src/pages/Item/Card/ItemBotanical.vue'), }, + { + path: 'shelving', + name: 'ItemShelving', + meta: { + title: 'shelving', + icon: 'vn:inventory', + }, + component: () => import('src/pages/Item/Card/ItemShelving.vue'), + }, ], }, ], From 554b9e814b9af83e65c091cf1d0a30c61d6ead3e Mon Sep 17 00:00:00 2001 From: wbuezas <wbuezas@verdnatura.es> Date: Thu, 2 May 2024 09:08:03 -0300 Subject: [PATCH 08/27] WIP --- src/components/FilterItemForm.vue | 11 +- src/pages/Entry/Card/EntryBuysImport.vue | 1 + src/pages/Item/Card/CreateIntrastatForm.vue | 52 +++++ src/pages/Item/Card/ItemBasicData.vue | 240 +++++++++++++++++++- src/pages/Item/locale/en.yml | 17 ++ src/pages/Item/locale/es.yml | 17 ++ 6 files changed, 334 insertions(+), 4 deletions(-) create mode 100644 src/pages/Item/Card/CreateIntrastatForm.vue diff --git a/src/components/FilterItemForm.vue b/src/components/FilterItemForm.vue index e031999e2d..00659f8fd2 100644 --- a/src/components/FilterItemForm.vue +++ b/src/components/FilterItemForm.vue @@ -1,7 +1,6 @@ <script setup> import { ref, reactive, computed } from 'vue'; import { useI18n } from 'vue-i18n'; -import { useRoute } from 'vue-router'; import VnRow from 'components/ui/VnRow.vue'; import FetchData from 'components/FetchData.vue'; @@ -12,10 +11,16 @@ import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue'; import axios from 'axios'; import { dashIfEmpty } from 'src/filters'; +const props = defineProps({ + url: { + type: String, + required: true, + }, +}); + const emit = defineEmits(['itemSelected']); const { t } = useI18n(); -const route = useRoute(); const itemFilter = { include: [ @@ -100,7 +105,7 @@ const fetchResults = async () => { } filter.where = where; - const { data } = await axios.get(`Entries/${route.params.id}/lastItemBuys`, { + const { data } = await axios.get(props.url, { params: { filter: JSON.stringify(filter) }, }); tableRows.value = data; diff --git a/src/pages/Entry/Card/EntryBuysImport.vue b/src/pages/Entry/Card/EntryBuysImport.vue index 705f56b682..6d856f0689 100644 --- a/src/pages/Entry/Card/EntryBuysImport.vue +++ b/src/pages/Entry/Card/EntryBuysImport.vue @@ -251,6 +251,7 @@ const redirectToBuysView = () => { > <template #form> <FilterItemForm + :url="`Entries/${route.params.id}/lastItemBuys`" @item-selected="row[col.field] = $event" /> </template> diff --git a/src/pages/Item/Card/CreateIntrastatForm.vue b/src/pages/Item/Card/CreateIntrastatForm.vue new file mode 100644 index 0000000000..3fd1ffe22c --- /dev/null +++ b/src/pages/Item/Card/CreateIntrastatForm.vue @@ -0,0 +1,52 @@ +<script setup> +import { reactive, ref, onMounted, nextTick } from 'vue'; +import { useI18n } from 'vue-i18n'; +import { useRoute } from 'vue-router'; + +import VnInput from 'src/components/common/VnInput.vue'; +import VnRow from 'components/ui/VnRow.vue'; +import FormModelPopup from 'components/FormModelPopup.vue'; + +const { t } = useI18n(); +const emit = defineEmits(['onDataSaved']); +const route = useRoute(); + +const identifierInputRef = ref(null); +const intrastatFormData = reactive({}); + +const onDataSaved = (formData, requestResponse) => { + emit('onDataSaved', formData, requestResponse); +}; + +onMounted(async () => { + await nextTick(); + identifierInputRef.value.focus(); +}); +</script> + +<template> + <FormModelPopup + :url-update="`Items/${route.params.id}/createIntrastat`" + model="itemGenus" + :title="t('createIntrastatForm.title')" + :form-initial-data="intrastatFormData" + @on-data-saved="onDataSaved" + > + <template #form-inputs="{ data }"> + <VnRow class="row q-gutter-md q-mb-md"> + <VnInput + ref="identifierInputRef" + :label="t('createIntrastatForm.identifier')" + type="number" + v-model.number="data.intrastatId" + :required="true" + /> + <VnInput + :label="t('createIntrastatForm.description')" + v-model="data.description" + :required="true" + /> + </VnRow> + </template> + </FormModelPopup> +</template> diff --git a/src/pages/Item/Card/ItemBasicData.vue b/src/pages/Item/Card/ItemBasicData.vue index 334cf049d5..dc6868fba5 100644 --- a/src/pages/Item/Card/ItemBasicData.vue +++ b/src/pages/Item/Card/ItemBasicData.vue @@ -1 +1,239 @@ -<template>Item basic data</template> +<script setup> +import { ref } from 'vue'; +import { useRoute } from 'vue-router'; +import { useI18n } from 'vue-i18n'; + +import FetchData from 'components/FetchData.vue'; +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 VnSelectDialog from 'src/components/common/VnSelectDialog.vue'; +import FilterItemForm from 'src/components/FilterItemForm.vue'; +import CreateIntrastatForm from './CreateIntrastatForm.vue'; + +const route = useRoute(); +const { t } = useI18n(); + +const itemTypesOptions = ref([]); +const itemsWithNameOptions = ref([]); +const intrastatsOptions = ref([]); +const expensesOptions = ref([]); + +const onIntrastatCreated = (response, formData) => { + intrastatsOptions.value = [...intrastatsOptions.value, response]; + formData.intrastatFk = response.id; +}; +</script> +<template> + <FetchData + url="ItemTypes" + :filter="{ + fields: ['id', 'name', 'categoryFk'], + include: 'category', + order: 'name ASC', + }" + @on-fetch="(data) => (itemTypesOptions = data)" + auto-load + /> + <FetchData + url="Items/withName" + :filter="{ + fields: ['id', 'name'], + order: 'id DESC', + }" + @on-fetch="(data) => (itemsWithNameOptions = data)" + auto-load + /> + <FetchData + url="Intrastats" + :filter="{ + fields: ['id', 'description'], + order: 'description ASC', + }" + @on-fetch="(data) => (intrastatsOptions = data)" + auto-load + /> + <FetchData + url="Expenses" + :filter="{ + fields: ['id', 'name'], + order: 'name ASC', + }" + @on-fetch="(data) => (expensesOptions = data)" + auto-load + /> + <FormModel + :url="`Items/${route.params.id}`" + :url-update="`Items/${route.params.id}`" + model="item" + auto-load + :clear-store-on-unmount="false" + > + <template #form="{ data }"> + <VnRow class="row q-gutter-md q-mb-md"> + <VnSelect + :label="t('basicData.type')" + v-model="data.typeFk" + :options="itemTypesOptions" + option-value="id" + option-label="name" + hide-selected + map-options + > + <template #option="scope"> + <QItem v-bind="scope.itemProps"> + <QItemSection> + <QItemLabel>{{ scope.opt?.name }}</QItemLabel> + <QItemLabel caption> + {{ scope.opt?.category?.name }} + </QItemLabel> + </QItemSection> + </QItem> + </template> + </VnSelect> + <VnInput :label="t('basicData.reference')" v-model="data.comment" /> + <VnInput :label="t('basicData.relevancy')" v-model="data.relevancy" /> + </VnRow> + <VnRow class="row q-gutter-md q-mb-md"> + <VnInput :label="t('basicData.stems')" v-model="data.stems" /> + <VnInput + :label="t('basicData.multiplier')" + v-model="data.stemMultiplier" + /> + <VnSelectDialog + :label="t('basicData.generic')" + v-model="data.genericFk" + :options="itemsWithNameOptions" + option-value="id" + option-label="name" + map-options + hide-selected + action-icon="filter_alt" + > + <template #form> + <FilterItemForm + url="Items/withName" + @item-selected="data.genericFk = $event" + /> + </template> + <template #option="scope"> + <QItem v-bind="scope.itemProps"> + <QItemSection> + <QItemLabel>{{ scope.opt?.name }}</QItemLabel> + <QItemLabel caption> #{{ scope.opt?.id }} </QItemLabel> + </QItemSection> + </QItem> + </template> + </VnSelectDialog> + </VnRow> + <VnRow class="row q-gutter-md q-mb-md"> + <VnSelectDialog + :label="t('basicData.intrastat')" + v-model="data.intrastatFk" + :options="intrastatsOptions" + option-value="id" + option-label="description" + map-options + hide-selected + > + <template #form> + <CreateIntrastatForm + @on-data-saved=" + (_, requestResponse) => + onIntrastatCreated(requestResponse, data) + " + /> + </template> + <template #option="scope"> + <QItem v-bind="scope.itemProps"> + <QItemSection> + <QItemLabel>{{ scope.opt?.description }}</QItemLabel> + <QItemLabel caption> #{{ scope.opt?.id }} </QItemLabel> + </QItemSection> + </QItem> + </template> + </VnSelectDialog> + <div class="col"> + <VnSelect + :label="t('basicData.expense')" + v-model="data.expenseFk" + :options="expensesOptions" + option-value="id" + option-label="name" + hide-selected + map-options + /> + </div> + </VnRow> + <VnRow class="row q-gutter-md q-mb-md"> + <VnInput + :label="t('basicData.weightByPiece')" + v-model.number="data.weightByPiece" + :min="0" + type="number" + /> + <VnInput + :label="t('basicData.boxUnits')" + v-model.number="data.packingOut" + :min="0" + type="number" + /> + <VnInput + :label="t('basicData.recycledPlastic')" + v-model.number="data.recycledPlastic" + :min="0" + type="number" + /> + <VnInput + :label="t('basicData.nonRecycledPlastic')" + v-model.number="data.nonRecycledPlastic" + :min="0" + type="number" + /> + </VnRow> + <!-- <VnRow class="row q-gutter-md q-mb-md"> + <div class="col"> + <QInput + :label="t('entry.basicData.observation')" + type="textarea" + v-model="data.observation" + :maxlength="45" + counter + fill-input + /> + </div> + </VnRow> --> + <!-- <VnRow class="row q-gutter-md q-mb-md"> + <div class="col"> + <QCheckbox + v-model="data.isOrdered" + :label="t('entry.basicData.ordered')" + /> + </div> + <div class="col"> + <QCheckbox + v-model="data.isConfirmed" + :label="t('entry.basicData.confirmed')" + /> + </div> + <div class="col"> + <QCheckbox + v-model="data.isExcludedFromAvailable" + :label="t('entry.basicData.excludedFromAvailable')" + /> + </div> + <div class="col"> + <QCheckbox v-model="data.isRaid" :label="t('entry.basicData.raid')" /> + </div> + <div class="col"> + <QCheckbox + v-if="isAdministrative()" + v-model="data.isBooked" + :label="t('entry.basicData.booked')" + /> + </div> + </VnRow> --> + </template> + </FormModel> +</template> diff --git a/src/pages/Item/locale/en.yml b/src/pages/Item/locale/en.yml index ec3b134e81..410ed5edcf 100644 --- a/src/pages/Item/locale/en.yml +++ b/src/pages/Item/locale/en.yml @@ -11,3 +11,20 @@ itemDiary: showBefore: Show what's before the inventory since: Since warehouse: Warehouse +basicData: + type: Type + reference: Reference + relevancy: Relevancy + stems: Stems + multiplier: Multiplier + generic: Generic + intrastat: Intrastat + expense: Expense + weightByPiece: Weight/Piece + boxUnits: Units/Box + recycledPlastic: Recycled plastic + nonRecycledPlastic: Non recycled plastic +createIntrastatForm: + title: New intrastat + identifier: Identifier + description: Description diff --git a/src/pages/Item/locale/es.yml b/src/pages/Item/locale/es.yml index 4f76313fa6..6540c36c86 100644 --- a/src/pages/Item/locale/es.yml +++ b/src/pages/Item/locale/es.yml @@ -11,3 +11,20 @@ itemDiary: showBefore: Mostrar lo anterior al inventario since: Desde warehouse: Almacén +basicData: + type: Tipo + reference: Referencia + relevancy: Relevancia + stems: Tallos + multiplier: Multiplicador + generic: Genérico + intrastat: Intrastat + expense: Gasto + weightByPiece: Peso (gramos)/tallo + boxUnits: Unidades/caja + recycledPlastic: Plástico reciclado + nonRecycledPlastic: Plástico no reciclado +createIntrastatForm: + title: Nuevo intrastat + identifier: Identificador + description: Descripción From cb0d7214650b02e1ab5a7af4e348e037a03b8e84 Mon Sep 17 00:00:00 2001 From: Javier Segarra <jsegarra@verdnatura.es> Date: Thu, 2 May 2024 14:40:56 +0200 Subject: [PATCH 09/27] feat: zoneSummary --- src/pages/Zone/Card/ZoneSummary.vue | 94 +++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 src/pages/Zone/Card/ZoneSummary.vue diff --git a/src/pages/Zone/Card/ZoneSummary.vue b/src/pages/Zone/Card/ZoneSummary.vue new file mode 100644 index 0000000000..00df03cb01 --- /dev/null +++ b/src/pages/Zone/Card/ZoneSummary.vue @@ -0,0 +1,94 @@ +<script setup> +import { ref, onMounted, computed } from 'vue'; +import { useRoute } from 'vue-router'; +import { useI18n } from 'vue-i18n'; +import { dashIfEmpty } from 'src/filters'; +import { getUrl } from 'src/composables/getUrl'; +import VnLv from 'src/components/ui/VnLv.vue'; +import VnLinkPhone from 'src/components/ui/VnLinkPhone.vue'; +import CardSummary from 'components/ui/CardSummary.vue'; +import VnUserLink from 'src/components/ui/VnUserLink.vue'; +import VnTitle from 'src/components/common/VnTitle.vue'; + +const route = useRoute(); +const { t } = useI18n(); + +const $props = defineProps({ + id: { + type: Number, + default: 0, + }, +}); + +const entityId = computed(() => $props.id || route.params.id); +const zoneUrl = ref(); + +onMounted(async () => { + zoneUrl.value = (await getUrl('')) + `zone/${entityId.value}/`; +}); + +const filter = computed(() => { + return { where: { id: entityId.value } }; +}); +</script> + +<template> + <CardSummary + data-key="zoneData" + ref="summary" + :url="`Zones/summary`" + :filter="filter" + > + <template #header="{ entity }"> + <div>{{ entity.id }} - {{ entity.firstName }} {{ entity.lastName }}</div> + </template> + <template #body="{ entity: zone }"> + <QCard class="vn-one"> + <VnTitle + :url="zoneUrl + `basic-data`" + :text="t('zone.summary.basicData')" + /> + <VnLv :label="t('zone.card.name')" :value="zone.user?.nickname" /> + <VnLv + :label="t('zone.list.department')" + :value="zone.department?.department?.name" + /> + <VnLv :label="t('zone.list.email')" :value="zone.user.email" copy /> + <VnLv :label="t('zone.summary.boss')" link> + <template #value> + <VnUserLink + v-if="zone.boss" + :name="dashIfEmpty(zone.boss?.name)" + :zone-id="zone.bossFk" + /> + </template> + </VnLv> + <VnLv :value="zone.mobileExtension"> + <template #label> + {{ t('zone.summary.phoneExtension') }} + <VnLinkPhone :phone-number="zone.mobileExtension" /> + </template> + </VnLv> + <VnLv :value="zone.phone"> + <template #label> + {{ t('zone.summary.entPhone') }} + <VnLinkPhone :phone-number="zone.phone" /> + </template> + </VnLv> + <VnLv :label="t('zone.summary.locker')" :value="zone.locker" /> + </QCard> + <QCard class="vn-one"> + <VnTitle :text="t('zone.summary.userData')" /> + <VnLv :label="t('zone.summary.userId')" :value="zone.user.id" /> + <VnLv :label="t('zone.card.name')" :value="zone.user.nickname" /> + <VnLv :label="t('zone.summary.role')" :value="zone.user.role.name" /> + <VnLv :value="zone?.sip?.extension"> + <template #label> + {{ t('zone.summary.sipExtension') }} + <VnLinkPhone :phone-number="zone?.sip?.extension" /> + </template> + </VnLv> + </QCard> + </template> + </CardSummary> +</template> From d68934a5269ff18f69ef8398e32914228bccab22 Mon Sep 17 00:00:00 2001 From: wbuezas <wbuezas@verdnatura.es> Date: Thu, 2 May 2024 11:51:04 -0300 Subject: [PATCH 10/27] Item basic data --- src/pages/Item/Card/ItemBasicData.vue | 66 ++++++++++++--------------- src/pages/Item/locale/en.yml | 7 +++ src/pages/Item/locale/es.yml | 7 +++ 3 files changed, 44 insertions(+), 36 deletions(-) diff --git a/src/pages/Item/Card/ItemBasicData.vue b/src/pages/Item/Card/ItemBasicData.vue index dc6868fba5..7e8fa1d923 100644 --- a/src/pages/Item/Card/ItemBasicData.vue +++ b/src/pages/Item/Card/ItemBasicData.vue @@ -192,48 +192,42 @@ const onIntrastatCreated = (response, formData) => { type="number" /> </VnRow> - <!-- <VnRow class="row q-gutter-md q-mb-md"> - <div class="col"> - <QInput - :label="t('entry.basicData.observation')" - type="textarea" - v-model="data.observation" - :maxlength="45" - counter - fill-input - /> - </div> - </VnRow> --> - <!-- <VnRow class="row q-gutter-md q-mb-md"> - <div class="col"> + <VnRow class="row q-gutter-md q-mb-md"> + <QCheckbox v-model="data.isActive" :label="t('basicData.isActive')" /> + <QCheckbox v-model="data.hasKgPrice" :label="t('basicData.hasKgPrice')" /> + <div> <QCheckbox - v-model="data.isOrdered" - :label="t('entry.basicData.ordered')" + v-model="data.isFragile" + :label="t('basicData.isFragile')" + class="q-mr-sm" /> + <QIcon name="info" class="cursor-pointer" size="xs"> + <QTooltip max-width="300px"> + {{ t('basicData.isFragileTooltip') }} + </QTooltip> + </QIcon> </div> - <div class="col"> + <div> <QCheckbox - v-model="data.isConfirmed" - :label="t('entry.basicData.confirmed')" + v-model="data.isPhotoRequested" + :label="t('basicData.isPhotoRequested')" + class="q-mr-sm" /> + <QIcon name="info" class="cursor-pointer" size="xs"> + <QTooltip> + {{ t('basicData.isPhotoRequestedTooltip') }} + </QTooltip> + </QIcon> </div> - <div class="col"> - <QCheckbox - v-model="data.isExcludedFromAvailable" - :label="t('entry.basicData.excludedFromAvailable')" - /> - </div> - <div class="col"> - <QCheckbox v-model="data.isRaid" :label="t('entry.basicData.raid')" /> - </div> - <div class="col"> - <QCheckbox - v-if="isAdministrative()" - v-model="data.isBooked" - :label="t('entry.basicData.booked')" - /> - </div> - </VnRow> --> + </VnRow> + <VnRow class="row q-gutter-md q-mb-md"> + <QInput + :label="t('basicData.description')" + type="textarea" + v-model="data.description" + fill-input + /> + </VnRow> </template> </FormModel> </template> diff --git a/src/pages/Item/locale/en.yml b/src/pages/Item/locale/en.yml index 410ed5edcf..35d65b2dab 100644 --- a/src/pages/Item/locale/en.yml +++ b/src/pages/Item/locale/en.yml @@ -24,6 +24,13 @@ basicData: boxUnits: Units/Box recycledPlastic: Recycled plastic nonRecycledPlastic: Non recycled plastic + description: Description + isActive: Active + hasKgPrice: Price in kg + isFragile: Fragile + isFragileTooltip: Is shown at website, app that this item cannot travel (wreath, palms, ...) + isPhotoRequested: Do photo + isPhotoRequestedTooltip: This item does need a photo createIntrastatForm: title: New intrastat identifier: Identifier diff --git a/src/pages/Item/locale/es.yml b/src/pages/Item/locale/es.yml index 6540c36c86..498520c269 100644 --- a/src/pages/Item/locale/es.yml +++ b/src/pages/Item/locale/es.yml @@ -24,6 +24,13 @@ basicData: boxUnits: Unidades/caja recycledPlastic: Plástico reciclado nonRecycledPlastic: Plástico no reciclado + description: Descripción + isActive: Activo + hasKgPrice: Precio en kg + isFragile: Frágil + isFragileTooltip: Se muestra en la web app, que este artículo no puede viajar (coronas, palmas, ...) + isPhotoRequested: Hacer foto + isPhotoRequestedTooltip: Este artículo necesita una foto createIntrastatForm: title: Nuevo intrastat identifier: Identificador From 1a5f2fbea2a7c821ca7d9e70fa5224f6a82dc22f Mon Sep 17 00:00:00 2001 From: Jon <jon@verdnatura.es> Date: Fri, 3 May 2024 12:30:53 +0200 Subject: [PATCH 11/27] feat: refs #7271 advanced structure --- src/i18n/locale/en.yml | 4 + src/i18n/locale/es.yml | 6 +- src/pages/Zone/Card/ZoneBasicData.vue | 103 +++++++++++++++++++ src/pages/Zone/Card/ZoneCalendar.vue | 0 src/pages/Zone/Card/ZoneLocations.vue | 0 src/pages/Zone/Card/ZoneLog.vue | 6 ++ src/pages/Zone/Card/ZoneSummary.vue | 3 + src/pages/Zone/Card/ZoneWarehouses.vue | 59 +++++++++++ src/pages/Zone/Delivery/ZoneDeliveryList.vue | 3 +- src/pages/Zone/Upcoming/ZoneUpcomingList.vue | 3 +- src/pages/Zone/ZoneCreate.vue | 8 +- src/pages/Zone/ZoneDeliveryDays.vue | 0 src/pages/Zone/ZoneFilterPanel.vue | 55 ++++++++++ src/pages/Zone/ZoneList.vue | 67 +++++++----- src/pages/Zone/ZoneUpcoming.vue | 53 ++++++++++ src/pages/Zone/locale/en.yml | 19 ++++ src/pages/Zone/locale/es.yml | 19 ++++ 17 files changed, 376 insertions(+), 32 deletions(-) create mode 100644 src/pages/Zone/Card/ZoneBasicData.vue create mode 100644 src/pages/Zone/Card/ZoneCalendar.vue create mode 100644 src/pages/Zone/Card/ZoneLocations.vue create mode 100644 src/pages/Zone/Card/ZoneLog.vue create mode 100644 src/pages/Zone/Card/ZoneSummary.vue create mode 100644 src/pages/Zone/Card/ZoneWarehouses.vue create mode 100644 src/pages/Zone/ZoneDeliveryDays.vue create mode 100644 src/pages/Zone/ZoneFilterPanel.vue create mode 100644 src/pages/Zone/ZoneUpcoming.vue create mode 100644 src/pages/Zone/locale/en.yml create mode 100644 src/pages/Zone/locale/es.yml diff --git a/src/i18n/locale/en.yml b/src/i18n/locale/en.yml index aa65ce08c7..a580f25536 100644 --- a/src/i18n/locale/en.yml +++ b/src/i18n/locale/en.yml @@ -90,6 +90,10 @@ globals: basicData: Basic data log: Logs parkingList: Parkings list + zones: Zones + zonesList: Zones + deliveryList: Delivery days + upcomingList: Upcoming deliveries created: Created worker: Worker now: Now diff --git a/src/i18n/locale/es.yml b/src/i18n/locale/es.yml index da421432de..ef7ebf22b2 100644 --- a/src/i18n/locale/es.yml +++ b/src/i18n/locale/es.yml @@ -90,6 +90,10 @@ globals: basicData: Datos básicos log: Historial parkingList: Listado de parkings + zones: Zonas + zonesList: Zonas + deliveryList: Días de entrega + upcomingList: Próximos repartos created: Fecha creación worker: Trabajador now: Ahora @@ -287,7 +291,7 @@ customer: hasSepaVnl: Recibido B2B VNL entry: pageTitles: - entries: Entrasdadas + entries: Entradas list: Listado summary: Resumen basicData: Datos básicos diff --git a/src/pages/Zone/Card/ZoneBasicData.vue b/src/pages/Zone/Card/ZoneBasicData.vue new file mode 100644 index 0000000000..5d57b920e6 --- /dev/null +++ b/src/pages/Zone/Card/ZoneBasicData.vue @@ -0,0 +1,103 @@ +<script setup> +import { useRoute } from 'vue-router'; +import { useI18n } from 'vue-i18n'; + +import FetchData from 'components/FetchData.vue'; +import FormModel from 'src/components/FormModel.vue'; +import VnRow from 'components/ui/VnRow.vue'; +import VnInput from 'src/components/common/VnInput.vue'; +import { QCheckbox } from 'quasar'; + +const route = useRoute(); +const { t } = useI18n(); +const zoneFilter = { + include: [ + { + relation: 'agency', + scope: { + fields: ['name'], + include: { relation: 'agencyModeFk', scope: { fields: ['id'] } }, + }, + }, + { relation: 'sip', scope: { fields: ['extension', 'secret'] } }, + { relation: 'department', scope: { include: { relation: 'department' } } }, + { relation: 'client', scope: { fields: ['phone'] } }, + ], +}; +const agencyFilter = { + fields: ['id', 'name'], + order: 'name ASC', + limit: 30, +}; +</script> + +<template> + <FetchData + :filter="agencyFilter" + @on-fetch="(data) => (agencyOptions = data)" + auto-load + url="agencies" + /> + <FetchData + :filter="zoneFilter" + @on-fetch="(data) => (zoneOptions = data)" + auto-load + url="zones" + /> + + <FormModel + :filter="zoneFilter" + :url="`zone/${route.params.id}/basic-data`" + auto-load + model="Zone" + > + <template #form="{ data }"> + <VnRow class="row q-gutter-md q-mb-md"> + <VnInput :label="t('Name')" clearable v-model="data.zone.name" /> + </VnRow> + + <VnRow class="row q-gutter-md q-mb-md"> + <VnInput v-model="data.agency.name" :label="t('Agency')" clearable /> + <VnInput v-model="data.zone.itemMaxSize" :label="t('Max m³')" clearable /> + <VnInput v-model="data.zone.m3Max" :label="t('Maximum m³')" clearable /> + </VnRow> + + <VnRow class="row q-gutter-md q-mb-md"> + <VnInput + v-model="data.zone.travelingDays" + :label="t('Traveling days')" + clearable + /> + <VnInput v-model="data.zone.hour" :label="t('Closing')" clearable /> + </VnRow> + + <VnRow class="row q-gutter-md q-mb-md"> + <VnInput v-model="data.zone.price" :label="t('Price')" clearable /> + <VnInput v-model="data.zone.bonus" :label="t('Bonus')" clearable /> + </VnRow> + + <VnRow class="row q-gutter-md q-mb-md"> + <VnInput + v-model="data.zone.inflation" + :label="t('Inflation')" + clearable + /> + <QCheckbox v-model="data.zone.isVolumetric" :label="t('Volumetric')" /> + </VnRow> + </template> + </FormModel> +</template> + +<i18n> +es: + Name: Nombre + Agency: Agencia + Max m³: Medida máxima + Maximum m³: M³ maximo + Traveling days: Dias de viaje + Closing: Cierre + Price: Precio + Bonus: Bonificación + Inflation: Inflación + Volumetric: Volumétrico +</i18n> diff --git a/src/pages/Zone/Card/ZoneCalendar.vue b/src/pages/Zone/Card/ZoneCalendar.vue new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/pages/Zone/Card/ZoneLocations.vue b/src/pages/Zone/Card/ZoneLocations.vue new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/pages/Zone/Card/ZoneLog.vue b/src/pages/Zone/Card/ZoneLog.vue new file mode 100644 index 0000000000..373d210b57 --- /dev/null +++ b/src/pages/Zone/Card/ZoneLog.vue @@ -0,0 +1,6 @@ +<script setup> +import VnLog from 'src/components/common/VnLog.vue'; +</script> +<template> + <VnLog model="Zone" url="/ZoneLogs"></VnLog> +</template> diff --git a/src/pages/Zone/Card/ZoneSummary.vue b/src/pages/Zone/Card/ZoneSummary.vue new file mode 100644 index 0000000000..63090b5d4d --- /dev/null +++ b/src/pages/Zone/Card/ZoneSummary.vue @@ -0,0 +1,3 @@ +<template> + <div>Si</div> +</template> diff --git a/src/pages/Zone/Card/ZoneWarehouses.vue b/src/pages/Zone/Card/ZoneWarehouses.vue new file mode 100644 index 0000000000..67a81ba4d2 --- /dev/null +++ b/src/pages/Zone/Card/ZoneWarehouses.vue @@ -0,0 +1,59 @@ +<script setup> +import { useRoute } from 'vue-router'; +import VnPaginate from 'components/ui/VnPaginate.vue'; +import CardList from 'components/CardList.vue'; +import VnLv from 'components/ui/VnLv.vue'; + +const route = useRoute(); + +function deleteWarehouse() { + let row = this.deleteRow; + if (!row) return; + return this.$http.delete(`${this.path}/${row.id}`).then(() => { + let index = this.$.data.indexOf(row); + if (index !== -1) this.$.data.splice(index, 1); + this.deleteRow = null; + }); +} +</script> + +<template> + <QPage class="column items-center q-pa-md"> + <div class="vn-card-list"> + <VnPaginate + data-key="ZoneWarehouses" + :url="`Zones/${route.params.id}/warehouses`" + auto-load + > + <template #body="{ rows }"> + <CardList + v-for="row of rows" + :key="row.id" + :title="row.name" + :id="row.id" + > + <template #list-items> + <VnLv :value="row.name" /> + <QIcon + name="delete" + size="sm" + class="cursor-pointer" + color="primary" + @click="deleteWarehouse()" + > + <QTooltip> + {{ t('Remove row') }} + </QTooltip> + </QIcon> + </template> + </CardList> + </template> + </VnPaginate> + </div> + </QPage> +</template> + +<i18n> + es: + Remove row: Eliminar fila +</i18n> diff --git a/src/pages/Zone/Delivery/ZoneDeliveryList.vue b/src/pages/Zone/Delivery/ZoneDeliveryList.vue index c7a3cbcdbc..695388a9b1 100644 --- a/src/pages/Zone/Delivery/ZoneDeliveryList.vue +++ b/src/pages/Zone/Delivery/ZoneDeliveryList.vue @@ -6,7 +6,6 @@ import { useArrayData } from 'src/composables/useArrayData'; import { useI18n } from 'vue-i18n'; import { useRouter } from 'vue-router'; import CardList from 'components/ui/CardList.vue'; -import VnLv from 'components/ui/VnLv.vue'; const quasar = useQuasar(); const arrayData = useArrayData('ZoneDeliveryList'); @@ -45,7 +44,7 @@ async function remove(row) { <div class="vn-card-list"> <VnPaginate data-key="ZoneDeliveryList" - url="/ZoneDeliverys" + url="/Zones/getEvents" order="id DESC" auto-load > diff --git a/src/pages/Zone/Upcoming/ZoneUpcomingList.vue b/src/pages/Zone/Upcoming/ZoneUpcomingList.vue index 5c417df8fd..2d3016f258 100644 --- a/src/pages/Zone/Upcoming/ZoneUpcomingList.vue +++ b/src/pages/Zone/Upcoming/ZoneUpcomingList.vue @@ -6,7 +6,6 @@ import { useArrayData } from 'src/composables/useArrayData'; import { useI18n } from 'vue-i18n'; import { useRouter } from 'vue-router'; import CardList from 'components/ui/CardList.vue'; -import VnLv from 'components/ui/VnLv.vue'; const quasar = useQuasar(); const arrayData = useArrayData('ZoneUpcomingList'); @@ -45,7 +44,7 @@ async function remove(row) { <div class="vn-card-list"> <VnPaginate data-key="ZoneUpcomingList" - url="/ZoneUpcomings" + url="/Zones/getUpcomingDeliveries" order="id DESC" auto-load > diff --git a/src/pages/Zone/ZoneCreate.vue b/src/pages/Zone/ZoneCreate.vue index 8c0ba8c176..93ea9589b1 100644 --- a/src/pages/Zone/ZoneCreate.vue +++ b/src/pages/Zone/ZoneCreate.vue @@ -96,7 +96,7 @@ function filterType(val, update) { <QInput filled v-model="zone.label" - :label="t('zone.create.label')" + :label="t('zone.create.name')" type="number" min="0" :rules="[(val) => !!val || t('zone.warnings.labelNotEmpty')]" @@ -106,7 +106,7 @@ function filterType(val, update) { <VnInput filled v-model="zone.plate" - :label="t('zone.create.plate')" + :label="t('zone.create.agency')" :rules="[(val) => !!val || t('zone.warnings.plateNotEmpty')]" /> </div> @@ -116,7 +116,7 @@ function filterType(val, update) { <QInput filled v-model="zone.volume" - :label="t('zone.create.volume')" + :label="t('zone.create.close')" type="number" min="0" :rules="[(val) => !!val || t('zone.warnings.volumeNotEmpty')]" @@ -134,7 +134,7 @@ function filterType(val, update) { option-value="id" emit-value map-options - :label="t('zone.create.type')" + :label="t('zone.create.price')" :options="filteredZoneTypes" :rules="[(val) => !!val || t('zone.warnings.typeNotEmpty')]" @filter="filterType" diff --git a/src/pages/Zone/ZoneDeliveryDays.vue b/src/pages/Zone/ZoneDeliveryDays.vue new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/pages/Zone/ZoneFilterPanel.vue b/src/pages/Zone/ZoneFilterPanel.vue new file mode 100644 index 0000000000..94765919d0 --- /dev/null +++ b/src/pages/Zone/ZoneFilterPanel.vue @@ -0,0 +1,55 @@ +<script setup> +import { ref } from 'vue'; +import { useI18n } from 'vue-i18n'; +import VnInput from 'components/common/VnInput.vue'; +import FetchData from 'components/FetchData.vue'; +import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue'; +import VnSelectFilter from 'components/common/VnSelectFilter.vue'; + +const { t } = useI18n(); +const props = defineProps({ + dataKey: { + type: String, + required: true, + }, + exprBuilder: { + type: Function, + default: null, + }, +}); +const agencies = ref([]); +</script> + +<template> + <FetchData + url="agencies" + limit="30" + @on-fetch="(data) => (agencies = data)" + auto-load + /> + <VnFilterPanel :data-key="props.dataKey" :search-button="true"> + <template #body="{ params }"> + <QItem> + <QItemSection> + <VnInput :label="t('Name')" v-model="params.name" is-outlined /> + </QItemSection> + </QItem> + <QItem> + <QItemSection> + <VnSelectFilter + :label="t('Agency')" + v-model="params.agencyModefK" + :options="agencies" + option-value="id" + option-label="name" + @input-value="agencies.fetch()" + dense + outlined + rounded + > + </VnSelectFilter> + </QItemSection> + </QItem> + </template> + </VnFilterPanel> +</template> diff --git a/src/pages/Zone/ZoneList.vue b/src/pages/Zone/ZoneList.vue index 00502e1f7c..2140c5e140 100644 --- a/src/pages/Zone/ZoneList.vue +++ b/src/pages/Zone/ZoneList.vue @@ -7,21 +7,15 @@ import { useI18n } from 'vue-i18n'; import { useRouter } from 'vue-router'; import CardList from 'components/ui/CardList.vue'; import VnLv from 'components/ui/VnLv.vue'; +import FetchData from 'src/components/FetchData.vue'; +import { useSummaryDialog } from 'src/composables/useSummaryDialog'; const quasar = useQuasar(); const arrayData = useArrayData('ZoneList'); const store = arrayData.store; const router = useRouter(); const { t } = useI18n(); - -const filter = { - include: { - relation: 'type', - scope: { - fields: 'name', - }, - }, -}; +const { viewSummary } = useSummaryDialog(); function navigate(id) { router.push({ path: `/zone/${id}/edit` }); @@ -44,18 +38,25 @@ async function remove(row) { // } } + +function extractHour(dateTime) { + const date = new Date(dateTime); + const hours = date.getHours().toString().padStart(2, '0'); + const minutes = date.getMinutes().toString().padStart(2, '0'); + return `${hours}:${minutes}`; +} </script> <template> + <FetchData + url="/Agencies" + @on-fetch="(data) => (agencyOptions = data)" + :filter="{ fields: ['id', 'name'] }" + auto-load + /> <QPage class="column items-center q-pa-md"> <div class="vn-card-list"> - <VnPaginate - data-key="ZoneList" - url="/Zones" - order="id DESC" - :filter="filter" - auto-load - > + <VnPaginate data-key="ZoneList" url="/Zones" order="id DESC" auto-load> <template #body="{ rows }"> <CardList v-for="row of rows" @@ -66,12 +67,23 @@ async function remove(row) { > <template #list-items> <VnLv - :label="t('zone.list.plate')" - :title-label="t('zone.list.plate')" - :value="row.plate" + :label="t('zone.list.id')" + :title-label="t('zone.list.id')" + :value="row.id" /> - <VnLv :label="t('zone.list.volume')" :value="row?.volume" /> - <VnLv :label="t('zone.list.type')" :value="row?.type?.name" /> + <VnLv :label="t('zone.list.name')" :value="row?.name" /> + <VnLv + :label="t('zone.list.agency')" + :options="agencyOptions" + option-value="id" + option-label="name" + :value="row?.agencyFk" + /> + <VnLv + :label="t('zone.list.close')" + :value="extractHour(row?.hour)" + /> + <VnLv :label="t('zone.list.price')" :value="row?.price" /> </template> <template #actions> <QBtn @@ -80,7 +92,14 @@ async function remove(row) { outline /> <QBtn - :label="t('zone.list.remove')" + :label="t('zone.list.openSummary')" + @click.stop="viewSummary(row.id, ZoneSummary)" + color="primary" + style="margin-top: 15px" + /> + <!--AQUI PONER BOTÓN CLONAR--> + <QBtn + :label="t('zone.list.clone')" @click.stop="remove(row)" color="primary" style="margin-top: 15px" @@ -91,7 +110,9 @@ async function remove(row) { </VnPaginate> </div> <QPageSticky position="bottom-right" :offset="[18, 18]"> - <QBtn @click="create" fab icon="add" color="primary" /> + <QBtn @click="create" fab icon="add" color="primary"> + <QTooltip>{{ t('zone.list.create') }}</QTooltip> + </QBtn> </QPageSticky> </QPage> </template> diff --git a/src/pages/Zone/ZoneUpcoming.vue b/src/pages/Zone/ZoneUpcoming.vue new file mode 100644 index 0000000000..d405c95f6b --- /dev/null +++ b/src/pages/Zone/ZoneUpcoming.vue @@ -0,0 +1,53 @@ +<script setup> +import { ref, computed } from 'vue'; +import ZoneFilterPanel from 'components/InvoiceOutNegativeFilter.vue'; +import VnSubToolbar from 'components/ui/VnSubToolbar.vue'; +import { useI18n } from 'vue-i18n'; + +const { t } = useI18n(); +const arrayData = ref(null); +const rows = computed(() => arrayData.value.store.data); + +const columns = computed(() => [ + { + label: t('Province'), + //field: '', + //name: '', + align: 'left', + }, + { + label: t('Closing'), + //field: '', + //name: '', + align: 'left', + }, + { + label: t('Id'), + //field: '', + //name: '', + align: 'left', + }, +]); + +function getWeekDay(jsonDate) { + const weekDay = new Date(jsonDate).getDay(); + + return this.days[weekDay].locale; +} +</script> + +<template> + <QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above> + <QScrollArea class="fit text-grey-8"> + <ZoneFilterPanel data-key="ZoneUpcoming" /> + </QScrollArea> + </QDrawer> + <VnSubToolbar /> + <QPage class="column items-center q-pa-md"> + <span> + {{ t(`${getWeekDay(/*detail.shipped*/)}`) }} - + {{ t /*'detail.shipped'*/() }} + </span> + <QTable :columns="columns" :rows="rows" class="full-width q-mt-md"> </QTable> + </QPage> +</template> diff --git a/src/pages/Zone/locale/en.yml b/src/pages/Zone/locale/en.yml new file mode 100644 index 0000000000..e62111d577 --- /dev/null +++ b/src/pages/Zone/locale/en.yml @@ -0,0 +1,19 @@ +zone: + list: + volume: Volume + clone: Clone + id: Id + name: Name + agency: Agency + close: Close + price: Price + create: Create zone + openSummary: Details + create: + name: Name + agency: Agency + close: Close + price: Price + type: + submit: Save + reset: Reset diff --git a/src/pages/Zone/locale/es.yml b/src/pages/Zone/locale/es.yml new file mode 100644 index 0000000000..5d7a265bfd --- /dev/null +++ b/src/pages/Zone/locale/es.yml @@ -0,0 +1,19 @@ +zone: + list: + volume: Volumen + clone: Clonar + id: Id + name: Nombre + agency: Agencia + close: Cierre + price: Precio + create: Crear zona + openSummary: Detalles + create: + name: Nombre + agency: Agencia + close: Cierre + price: Precio + type: + submit: Guardar + reset: Reiniciar From 710b45f43ab0ecf3a90142708f060bfe3151f782 Mon Sep 17 00:00:00 2001 From: carlossa <carlossa@verdnatura.es> Date: Fri, 3 May 2024 14:49:50 +0200 Subject: [PATCH 12/27] refs #6842 isFreelance --- src/pages/Worker/Card/WorkerCalendar.vue | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/pages/Worker/Card/WorkerCalendar.vue b/src/pages/Worker/Card/WorkerCalendar.vue index 288e78dcf4..c0a70571e2 100644 --- a/src/pages/Worker/Card/WorkerCalendar.vue +++ b/src/pages/Worker/Card/WorkerCalendar.vue @@ -13,7 +13,7 @@ import axios from 'axios'; const stateStore = useStateStore(); const route = useRoute(); const { t } = useI18n(); - +const workerIsFreelance = ref(); const workerCalendarFilterRef = ref(null); const workerCalendarRef = ref(null); const absenceType = ref(null); @@ -32,6 +32,12 @@ const onFetchActiveContract = (data) => { hasWorkCenter.value = Boolean(data?.workCenterFk); }; +const isFreelance = async () => { + const { data } = await axios.get(`Workers/${route.params.id}`); + + workerIsFreelance.value = data.isFreelance; +}; + const addEvent = (day, newEvent, isFestive = false) => { const timestamp = new Date(day).getTime(); let event = eventsMap.value[timestamp]; @@ -128,6 +134,7 @@ const refreshData = () => { updateYearHolidays(); updateContractHolidays(); getAbsences(); + isFreelance(); }; const onDeletedEvent = (timestamp) => { @@ -151,6 +158,11 @@ watch([year, businessFk], () => refreshData()); @on-fetch="(data) => (isSubordinate = data)" auto-load /> + <FetchData + :url="`Workers/${route.params.id}`" + @on-fetch="(data) => (workerIsFreelance = data.isFreelance)" + auto-load + /> <template v-if="stateStore.isHeaderMounted()"> <Teleport to="#actions-append"> <div class="row q-gutter-x-sm"> @@ -181,7 +193,7 @@ watch([year, businessFk], () => refreshData()); </QScrollArea> </QDrawer> <QPage class="column items-center"> - <QCard v-if="!hasWorkCenter"> + <QCard v-if="workerIsFreelance"> <QCardSection class="text-center"> {{ t('Autonomous worker') }} </QCardSection> From 743faef9567f4c7f942a3ebd1d27b45d0f510aec Mon Sep 17 00:00:00 2001 From: Javier Segarra <jsegarra@verdnatura.es> Date: Fri, 3 May 2024 15:05:37 +0200 Subject: [PATCH 13/27] fix: #6842 Not update calendar correctly --- src/pages/Worker/Card/WorkerCalendar.vue | 13 ++++++++++++- src/pages/Worker/Card/WorkerDescriptor.vue | 1 - 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/pages/Worker/Card/WorkerCalendar.vue b/src/pages/Worker/Card/WorkerCalendar.vue index c0a70571e2..6677cb351f 100644 --- a/src/pages/Worker/Card/WorkerCalendar.vue +++ b/src/pages/Worker/Card/WorkerCalendar.vue @@ -1,5 +1,5 @@ <script setup> -import { ref, watch } from 'vue'; +import { nextTick, ref, watch } from 'vue'; import { useI18n } from 'vue-i18n'; import { useRoute } from 'vue-router'; @@ -10,6 +10,8 @@ import WorkerCalendarItem from 'pages/Worker/Card/WorkerCalendarItem.vue'; import { useStateStore } from 'stores/useStateStore'; import axios from 'axios'; +import { useRouter } from 'vue-router'; +const router = useRouter(); const stateStore = useStateStore(); const route = useRoute(); const { t } = useI18n(); @@ -143,12 +145,21 @@ const onDeletedEvent = (timestamp) => { if (festiveEventsMap.value[timestamp]) eventsMap.value[timestamp] = festiveEventsMap.value[timestamp]; }; +const activeContractRef = ref(null); +watch( + () => router.currentRoute.value.params.id, + async () => { + await nextTick(); + await activeContractRef.value.fetch(); + } +); watch([year, businessFk], () => refreshData()); </script> <template> <FetchData + ref="activeContractRef" :url="`Workers/${route.params.id}/activeContract`" @on-fetch="onFetchActiveContract" auto-load diff --git a/src/pages/Worker/Card/WorkerDescriptor.vue b/src/pages/Worker/Card/WorkerDescriptor.vue index 6f05fcdfb9..922c31d999 100644 --- a/src/pages/Worker/Card/WorkerDescriptor.vue +++ b/src/pages/Worker/Card/WorkerDescriptor.vue @@ -36,7 +36,6 @@ const filter = computed(() => { }); const sip = ref(null); - watch( () => [worker.value?.sip?.extension, state.get('extension')], ([newWorkerSip, newStateExtension], [oldWorkerSip, oldStateExtension]) => { From f3b1de1ee41777981e0bc7e6cbc654f075745693 Mon Sep 17 00:00:00 2001 From: wbuezas <wbuezas@verdnatura.es> Date: Fri, 3 May 2024 17:19:13 -0300 Subject: [PATCH 14/27] Item tags --- src/pages/Item/Card/ItemTags.vue | 119 +++++++++++++++---------------- src/pages/Item/locale/en.yml | 6 ++ src/pages/Item/locale/es.yml | 6 ++ 3 files changed, 69 insertions(+), 62 deletions(-) diff --git a/src/pages/Item/Card/ItemTags.vue b/src/pages/Item/Card/ItemTags.vue index 3067c97683..01df1009bc 100644 --- a/src/pages/Item/Card/ItemTags.vue +++ b/src/pages/Item/Card/ItemTags.vue @@ -1,5 +1,5 @@ <script setup> -import { ref, onMounted, computed } from 'vue'; +import { ref } from 'vue'; import { useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; @@ -10,65 +10,58 @@ import FetchData from 'components/FetchData.vue'; import VnSelect from 'src/components/common/VnSelect.vue'; import axios from 'axios'; -import { useArrayData } from 'composables/useArrayData'; const route = useRoute(); const { t } = useI18n(); const itemTagsRef = ref(null); const tagOptions = ref([]); - -const arrayData = useArrayData('ItemTags'); -const itemTags = computed(() => { - console.log('arrayData.store.data:: ', arrayData.store.data); - let map = new Map(); - (arrayData.store.data || []).forEach((tag) => { - map.set(tag.id, tag); - }); - return map; -}); -// const getHighestPriority = () => { -// let max = 0; -// console.log('formData:: ', itemTagsRef.value.formData); -// itemTagsRef.value.formData.forEach((tag) => { -// if (tag.priority > max) max = tag.priority; -// }); -// return max + 1; -// }; - -const getHighestPriority = computed(() => { - let max = 0; - if (!itemTagsRef.value || !itemTagsRef.value.length) return max; - console.log('formData:: ', itemTagsRef.value.formData); - itemTagsRef.value.formData.forEach((tag) => { - if (tag.priority > max) max = tag.priority; - }); - return max + 1; -}); +const valueOptionsMap = ref(new Map()); const getSelectedTagValues = async (tag) => { try { - console.log('tag:: ', tag); - tag.value = null; + if (!tag.tagFk && tag.tag.isFree) return; const filter = { fields: ['value'], order: 'value ASC', }; const params = { filter: JSON.stringify(filter) }; - const { data } = await axios.get(`Tags/${tag.selectedTag.id}/filterValue`, { + const { data } = await axios.get(`Tags/${tag.tagFk}/filterValue`, { params, }); - tag.valueOptions = data; + valueOptionsMap.value.set(tag.tagFk, data); } catch (err) { console.error('Error getting selected tag values'); } }; -onMounted(() => { - // if (itemTagsRef.value) itemTagsRef.value.reload(); - console.log('itemTagsRef:: ', itemTagsRef.value.formData); -}); +const onItemTagsFetched = async (itemTags) => { + (itemTags || []).forEach((tag) => { + getSelectedTagValues(tag); + }); +}; + +const handleTagSelected = (rows, index, tag) => { + rows[index].tag = tag; + rows[index].tagFk = tag.id; + rows[index].value = null; + getSelectedTagValues(rows[index]); +}; + +const getHighestPriority = (rows) => { + let max = 0; + rows.forEach((tag) => { + if (tag.priority > max) max = tag.priority; + }); + return max + 1; +}; + +const insertTag = (rows) => { + itemTagsRef.value.insert(); + itemTagsRef.value.formData[itemTagsRef.value.formData.length - 1].priority = + getHighestPriority(rows); +}; </script> <template> @@ -85,14 +78,17 @@ onMounted(() => { data-key="ItemTags" model="ItemTags" url="ItemTags" - save-url="Tags/onSubmit" - auto-load + update-url="Tags/onSubmit" :data-required="{ + $index: undefined, itemFk: route.params.id, - priority: getHighestPriority, + priority: undefined, tag: { isFree: undefined, + value: undefined, + name: undefined, }, + tagFk: undefined, }" :default-remove="false" :filter="{ @@ -106,6 +102,8 @@ onMounted(() => { }, }, }" + auto-load + @on-fetch="onItemTagsFetched" > <template #body="{ rows }"> <QCard class="q-pl-lg q-py-md"> @@ -115,32 +113,39 @@ onMounted(() => { class="row q-gutter-md q-mb-md" > <VnSelect - :label="t('Tag')" - v-model="row.tagFk" + :label="t('itemTags.tag')" :options="tagOptions" + :model-value="row.tag" option-label="name" - option-value="id" hide-selected - @update:model-value="getSelectedTagValues(row)" + @update:model-value=" + ($event) => handleTagSelected(rows, index, $event) + " /> <VnSelect v-if="row.tag?.isFree === false" + :key="row.tagFk" :label="t('Value')" v-model="row.value" - option-value="value" + :options="valueOptionsMap.get(row.tagFk)" option-label="value" + option-value="value" emit-value use-input + class="col" :is-clearable="false" /> <VnInput - v-if="row.tag?.isFree || row.tag?.isFree == undefined" + v-else-if=" + row.tag?.isFree || row.tag?.isFree == undefined + " v-model="row.value" - :label="t('Value')" + :label="t('itemTags.value')" :is-clearable="false" + style="width: 100%" /> <VnInput - :label="t('Relevancy')" + :label="t('itemTags.relevancy')" type="number" v-model="row.priority" /> @@ -153,37 +158,27 @@ onMounted(() => { size="sm" > <QTooltip> - {{ t('Remove tag') }} + {{ t('itemTags.removeTag') }} </QTooltip> </QIcon> </div> </VnRow> <VnRow> <QIcon - @click="itemTagsRef.insert()" + @click="insertTag(rows)" class="cursor-pointer" color="primary" name="add" size="sm" > <QTooltip> - {{ t('Add tag') }} + {{ t('itemTags.addTag') }} </QTooltip> </QIcon> </VnRow> </QCard> </template> </CrudModel> - <pre>{{ itemTags }}</pre> </QPage> </div> </template> - -<i18n> -es: - Remove tag: Quitar etiqueta - Add tag: Añadir etiqueta - Tag: Etiqueta - Value: Valor - Relevancy: Relevancia -</i18n> diff --git a/src/pages/Item/locale/en.yml b/src/pages/Item/locale/en.yml index 9acd1de4d8..2ffcd36618 100644 --- a/src/pages/Item/locale/en.yml +++ b/src/pages/Item/locale/en.yml @@ -34,3 +34,9 @@ lastEntries: package: Package freight: Freight comission: Comission +itemTags: + removeTag: Remove tag + addTag: Add tag + tag: Tag + value: Value + relevancy: Relevancy diff --git a/src/pages/Item/locale/es.yml b/src/pages/Item/locale/es.yml index 46e0d9eb79..3047239f2b 100644 --- a/src/pages/Item/locale/es.yml +++ b/src/pages/Item/locale/es.yml @@ -34,3 +34,9 @@ lastEntries: package: Embalaje freight: Porte comission: Comisión +itemTags: + removeTag: Quitar etiqueta + addTag: Añadir etiqueta + tag: Etiqueta + value: Valor + relevancy: Relevancia From 0f5d06614d2c1e0e21a433be89957ea340c43b49 Mon Sep 17 00:00:00 2001 From: Javier Segarra <jsegarra@verdnatura.es> Date: Mon, 6 May 2024 07:24:52 +0200 Subject: [PATCH 15/27] feat: VnInput numberValidation --- src/components/common/VnInput.vue | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/components/common/VnInput.vue b/src/components/common/VnInput.vue index 96028862ad..0d7d6edca7 100644 --- a/src/components/common/VnInput.vue +++ b/src/components/common/VnInput.vue @@ -52,6 +52,12 @@ const focus = () => { defineExpose({ focus, }); + +const inputRules = (val) => { + const { min } = vnInputRef.value.$attrs; + if (min >= 0) + if (val.toString().indexOf('.') < min) return t('inputMin', { value: min }); +}; </script> <template> @@ -68,6 +74,8 @@ defineExpose({ :class="{ required: $attrs.required }" @keyup.enter="onEnterPress()" :clearable="false" + :rules="[inputRules]" + :lazy-rules="true" > <template v-if="$slots.prepend" #prepend> <slot name="prepend" /> @@ -85,3 +93,9 @@ defineExpose({ </QInput> </div> </template> +<i18n> + en: + inputMin: Must be more than {value} + es: + inputMin: Debe ser mayor a {value} +</i18n> From f85665b271e1c01a665a1369052446a67f916ba0 Mon Sep 17 00:00:00 2001 From: Javier Segarra <jsegarra@verdnatura.es> Date: Mon, 6 May 2024 08:37:41 +0200 Subject: [PATCH 16/27] fix: replace QToolbar by Teleport --- src/pages/Item/Card/ItemShelving.vue | 76 +++++++++++++++------------- 1 file changed, 41 insertions(+), 35 deletions(-) diff --git a/src/pages/Item/Card/ItemShelving.vue b/src/pages/Item/Card/ItemShelving.vue index 573c4be0c0..830628f3b1 100644 --- a/src/pages/Item/Card/ItemShelving.vue +++ b/src/pages/Item/Card/ItemShelving.vue @@ -14,6 +14,9 @@ import { useArrayData } from 'src/composables/useArrayData'; import useNotify from 'src/composables/useNotify.js'; import { useVnConfirm } from 'composables/useVnConfirm'; import axios from 'axios'; +import { useStateStore } from 'stores/useStateStore'; + +const stateStore = useStateStore(); const route = useRoute(); const { t } = useI18n(); @@ -197,43 +200,46 @@ onMounted(async () => { auto-load @on-fetch="(data) => (shelvingsOptions = data)" /> - <QToolbar class="bg-vn-dark justify-end"> - <div id="st-data" class="q-py-sm flex items-center"> - <div class="q-pa-md q-mr-lg" style="border: 2px solid #222"> - <QCardSection horizontal> - <span class="text-weight-bold text-subtitle1 text-center full-width"> - {{ t('shelvings.total') }} - </span> - </QCardSection> - <QCardSection class="column items-center" horizontal> - <div> - <span class="details-label" - >{{ t('shelvings.totalLabels') }} + <template v-if="stateStore.isHeaderMounted()"> + <Teleport to="#st-data"> + <div class="q-py-sm flex items-center"> + <div class="q-pa-md q-mr-lg" style="border: 2px solid #222"> + <QCardSection horizontal> + <span + class="text-weight-bold text-subtitle1 text-center full-width" + > + {{ t('shelvings.total') }} </span> - <span>: {{ totalLabels }}</span> - </div> - </QCardSection> + </QCardSection> + <QCardSection class="column items-center" horizontal> + <div> + <span class="details-label" + >{{ t('shelvings.totalLabels') }} + </span> + <span>: {{ totalLabels }}</span> + </div></QCardSection + > + </div> + <QBtn + color="primary" + icon="delete" + :disabled="!rowsSelected.length" + @click=" + openConfirmationModal( + t('shelvings.removeConfirmTitle'), + t('shelvings.removeConfirmSubtitle'), + removeLines + ) + " + > + <QTooltip> + {{ t('shelvings.removeLines') }} + </QTooltip> + </QBtn> </div> - <QBtn - color="primary" - icon="delete" - :disabled="!rowsSelected.length" - @click=" - openConfirmationModal( - t('shelvings.removeConfirmTitle'), - t('shelvings.removeConfirmSubtitle'), - removeLines - ) - " - > - <QTooltip> - {{ t('shelvings.removeLines') }} - </QTooltip> - </QBtn> - </div> - <QSpace /> - <div id="st-actions"></div> - </QToolbar> + </Teleport> + </template> + <QPage class="column items-center q-pa-md"> <QTable :rows="rows" From 62bf968b0bb056c4669d377cec72589891ee7439 Mon Sep 17 00:00:00 2001 From: Javier Segarra <jsegarra@verdnatura.es> Date: Mon, 6 May 2024 08:41:12 +0200 Subject: [PATCH 17/27] change action to st-actions --- src/pages/Item/Card/ItemShelving.vue | 62 ++++++++++++++-------------- 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/src/pages/Item/Card/ItemShelving.vue b/src/pages/Item/Card/ItemShelving.vue index 830628f3b1..7e7faab361 100644 --- a/src/pages/Item/Card/ItemShelving.vue +++ b/src/pages/Item/Card/ItemShelving.vue @@ -202,42 +202,40 @@ onMounted(async () => { /> <template v-if="stateStore.isHeaderMounted()"> <Teleport to="#st-data"> - <div class="q-py-sm flex items-center"> - <div class="q-pa-md q-mr-lg" style="border: 2px solid #222"> - <QCardSection horizontal> - <span - class="text-weight-bold text-subtitle1 text-center full-width" - > - {{ t('shelvings.total') }} + <div class="q-pa-md q-mr-lg q-ma-xs" style="border: 2px solid #222"> + <QCardSection horizontal> + <span class="text-weight-bold text-subtitle1 text-center full-width"> + {{ t('shelvings.total') }} + </span> + </QCardSection> + <QCardSection class="column items-center" horizontal> + <div> + <span class="details-label" + >{{ t('shelvings.totalLabels') }} </span> - </QCardSection> - <QCardSection class="column items-center" horizontal> - <div> - <span class="details-label" - >{{ t('shelvings.totalLabels') }} - </span> - <span>: {{ totalLabels }}</span> - </div></QCardSection - > - </div> - <QBtn - color="primary" - icon="delete" - :disabled="!rowsSelected.length" - @click=" - openConfirmationModal( - t('shelvings.removeConfirmTitle'), - t('shelvings.removeConfirmSubtitle'), - removeLines - ) - " + <span>: {{ totalLabels }}</span> + </div></QCardSection > - <QTooltip> - {{ t('shelvings.removeLines') }} - </QTooltip> - </QBtn> </div> </Teleport> + <Teleport to="#st-actions"> + <QBtn + color="primary" + icon="delete" + :disabled="!rowsSelected.length" + @click=" + openConfirmationModal( + t('shelvings.removeConfirmTitle'), + t('shelvings.removeConfirmSubtitle'), + removeLines + ) + " + > + <QTooltip> + {{ t('shelvings.removeLines') }} + </QTooltip> + </QBtn> + </Teleport> </template> <QPage class="column items-center q-pa-md"> From 6a81076874456a8a31510bf8f77af13e107eff1e Mon Sep 17 00:00:00 2001 From: Javier Segarra <jsegarra@verdnatura.es> Date: Mon, 6 May 2024 08:45:29 +0200 Subject: [PATCH 18/27] fix: replace inputMin rule --- src/components/common/VnInput.vue | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/common/VnInput.vue b/src/components/common/VnInput.vue index 0d7d6edca7..3f7e46367b 100644 --- a/src/components/common/VnInput.vue +++ b/src/components/common/VnInput.vue @@ -55,8 +55,7 @@ defineExpose({ const inputRules = (val) => { const { min } = vnInputRef.value.$attrs; - if (min >= 0) - if (val.toString().indexOf('.') < min) return t('inputMin', { value: min }); + if (min >= 0) if (Math.floor(val) < min) return t('inputMin', { value: min }); }; </script> From 49b6c57ad4b34505f7add33c21449d04b229aade Mon Sep 17 00:00:00 2001 From: Javier Segarra <jsegarra@verdnatura.es> Date: Mon, 6 May 2024 08:53:12 +0200 Subject: [PATCH 19/27] fix: show Color --- src/components/FilterItemForm.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/FilterItemForm.vue b/src/components/FilterItemForm.vue index 00659f8fd2..7af9adf939 100644 --- a/src/components/FilterItemForm.vue +++ b/src/components/FilterItemForm.vue @@ -78,7 +78,7 @@ const tableColumns = computed(() => [ { label: t('entry.buys.color'), name: 'ink', - field: 'inkName', + field: (row) => row?.ink?.name, align: 'left', }, ]); From 4d490652ea4c009e3ddf9b80bc1aea26f8767a0d Mon Sep 17 00:00:00 2001 From: Javier Segarra <jsegarra@verdnatura.es> Date: Mon, 6 May 2024 10:11:07 +0200 Subject: [PATCH 20/27] feat #7271: add template --- src/pages/Zone/Card/ZoneLocations.vue | 1 + src/pages/Zone/Card/ZoneWarehouses.vue | 12 +++--------- src/pages/Zone/ZoneDeliveryDays.vue | 1 + 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/pages/Zone/Card/ZoneLocations.vue b/src/pages/Zone/Card/ZoneLocations.vue index e69de29bb2..e4305c8983 100644 --- a/src/pages/Zone/Card/ZoneLocations.vue +++ b/src/pages/Zone/Card/ZoneLocations.vue @@ -0,0 +1 @@ +<template>Zone Locations</template> diff --git a/src/pages/Zone/Card/ZoneWarehouses.vue b/src/pages/Zone/Card/ZoneWarehouses.vue index 67a81ba4d2..7ff73a5e95 100644 --- a/src/pages/Zone/Card/ZoneWarehouses.vue +++ b/src/pages/Zone/Card/ZoneWarehouses.vue @@ -6,15 +6,9 @@ import VnLv from 'components/ui/VnLv.vue'; const route = useRoute(); -function deleteWarehouse() { - let row = this.deleteRow; - if (!row) return; - return this.$http.delete(`${this.path}/${row.id}`).then(() => { - let index = this.$.data.indexOf(row); - if (index !== -1) this.$.data.splice(index, 1); - this.deleteRow = null; - }); -} +const deleteWarehouse = () => { + return true; +}; </script> <template> diff --git a/src/pages/Zone/ZoneDeliveryDays.vue b/src/pages/Zone/ZoneDeliveryDays.vue index e69de29bb2..485500dbaa 100644 --- a/src/pages/Zone/ZoneDeliveryDays.vue +++ b/src/pages/Zone/ZoneDeliveryDays.vue @@ -0,0 +1 @@ +<template>Zone Delivery days</template> From 4caf27b4f88b6430001c2aedbc1f3b0f5150d966 Mon Sep 17 00:00:00 2001 From: alexm <alexm@verdnatura.es> Date: Tue, 7 May 2024 08:37:52 +0200 Subject: [PATCH 21/27] deploy: init version 2422 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 063cf6de4a..7be20a8428 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "salix-front", - "version": "24.20.0", + "version": "24.22.0", "description": "Salix frontend", "productName": "Salix", "author": "Verdnatura", From ac0993aa11e40a72126ac232d713269ab706c9f0 Mon Sep 17 00:00:00 2001 From: carlossa <carlossa@verdnatura.es> Date: Tue, 7 May 2024 10:20:26 +0200 Subject: [PATCH 22/27] refs #6842 pr changes --- src/pages/Worker/Card/WorkerCalendar.vue | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/pages/Worker/Card/WorkerCalendar.vue b/src/pages/Worker/Card/WorkerCalendar.vue index 6677cb351f..2e525aa303 100644 --- a/src/pages/Worker/Card/WorkerCalendar.vue +++ b/src/pages/Worker/Card/WorkerCalendar.vue @@ -16,6 +16,7 @@ const stateStore = useStateStore(); const route = useRoute(); const { t } = useI18n(); const workerIsFreelance = ref(); +const WorkerFreelanceRef = ref(); const workerCalendarFilterRef = ref(null); const workerCalendarRef = ref(null); const absenceType = ref(null); @@ -34,12 +35,6 @@ const onFetchActiveContract = (data) => { hasWorkCenter.value = Boolean(data?.workCenterFk); }; -const isFreelance = async () => { - const { data } = await axios.get(`Workers/${route.params.id}`); - - workerIsFreelance.value = data.isFreelance; -}; - const addEvent = (day, newEvent, isFestive = false) => { const timestamp = new Date(day).getTime(); let event = eventsMap.value[timestamp]; @@ -136,7 +131,7 @@ const refreshData = () => { updateYearHolidays(); updateContractHolidays(); getAbsences(); - isFreelance(); + WorkerFreelanceRef.value.fetch(); }; const onDeletedEvent = (timestamp) => { @@ -172,6 +167,7 @@ watch([year, businessFk], () => refreshData()); <FetchData :url="`Workers/${route.params.id}`" @on-fetch="(data) => (workerIsFreelance = data.isFreelance)" + ref="WorkerFreelanceRef" auto-load /> <template v-if="stateStore.isHeaderMounted()"> From c0dd140a3ab0a22d475be5216421e50186721128 Mon Sep 17 00:00:00 2001 From: Javier Segarra <jsegarra@verdnatura.es> Date: Tue, 7 May 2024 11:38:12 +0200 Subject: [PATCH 23/27] fix: validTag --- src/components/common/VnSelect.vue | 3 ++- src/pages/Item/Card/ItemTags.vue | 9 ++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/components/common/VnSelect.vue b/src/components/common/VnSelect.vue index 05c74f00a0..e8083dec29 100644 --- a/src/components/common/VnSelect.vue +++ b/src/components/common/VnSelect.vue @@ -57,7 +57,7 @@ const $props = defineProps({ }); const { t } = useI18n(); -const requiredFieldRule = (val) => !!val || t('globals.fieldRequired'); +const requiredFieldRule = (val) => val ?? t('globals.fieldRequired'); const { optionLabel, optionValue, options, modelValue } = toRefs($props); const myOptions = ref([]); @@ -167,6 +167,7 @@ watch(modelValue, (newValue) => { hide-selected fill-input ref="vnSelectRef" + lazy-rules :class="{ required: $attrs.required }" :rules="$attrs.required ? [requiredFieldRule] : null" virtual-scroll-slice-size="options.length" diff --git a/src/pages/Item/Card/ItemTags.vue b/src/pages/Item/Card/ItemTags.vue index 01df1009bc..b3cdfffb18 100644 --- a/src/pages/Item/Card/ItemTags.vue +++ b/src/pages/Item/Card/ItemTags.vue @@ -105,7 +105,7 @@ const insertTag = (rows) => { auto-load @on-fetch="onItemTagsFetched" > - <template #body="{ rows }"> + <template #body="{ rows, validate }"> <QCard class="q-pl-lg q-py-md"> <VnRow v-for="(row, index) in rows" @@ -121,6 +121,8 @@ const insertTag = (rows) => { @update:model-value=" ($event) => handleTagSelected(rows, index, $event) " + :required="true" + :rules="validate('itemTag.tagFk')" /> <VnSelect v-if="row.tag?.isFree === false" @@ -134,6 +136,8 @@ const insertTag = (rows) => { use-input class="col" :is-clearable="false" + :required="false" + :rules="validate('itemTag.tagFk')" /> <VnInput v-else-if=" @@ -148,6 +152,8 @@ const insertTag = (rows) => { :label="t('itemTags.relevancy')" type="number" v-model="row.priority" + :required="true" + :rules="validate('itemTag.priority')" /> <div class="col-1 row justify-center items-center"> <QIcon @@ -167,6 +173,7 @@ const insertTag = (rows) => { <QIcon @click="insertTag(rows)" class="cursor-pointer" + :disable="!validRow" color="primary" name="add" size="sm" From 5712810ae28548f97727adaeefed9aceb2015ca1 Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Tue, 7 May 2024 12:00:20 +0200 Subject: [PATCH 24/27] fix: refs #6938 rollback phone --- src/pages/Worker/Card/WorkerBasicData.vue | 3 -- src/pages/Worker/Card/WorkerDescriptor.vue | 26 +++++++++++- src/pages/Worker/Card/WorkerSummary.vue | 49 ++++++++++++++++++++-- 3 files changed, 69 insertions(+), 9 deletions(-) diff --git a/src/pages/Worker/Card/WorkerBasicData.vue b/src/pages/Worker/Card/WorkerBasicData.vue index 7754720658..c59f4281dc 100644 --- a/src/pages/Worker/Card/WorkerBasicData.vue +++ b/src/pages/Worker/Card/WorkerBasicData.vue @@ -27,7 +27,6 @@ const workerFilter = { }, { relation: 'sip', scope: { fields: ['extension', 'secret'] } }, { relation: 'department', scope: { include: { relation: 'department' } } }, - { relation: 'client', scope: {fields:['phone']} }, ], }; const workersFilter = { @@ -87,7 +86,6 @@ const maritalStatus = [ :label="t('Mobile extension')" clearable /> - <VnInput v-model="data.client.phone" :label="t('Personal phone')" clearable /> </VnRow> <VnRow class="row q-gutter-md q-mb-md"> @@ -159,7 +157,6 @@ es: Last name: Apellidos Business phone: Teléfono de empresa Mobile extension: Extensión móvil - Personal phone: Teléfono personal Boss: Jefe Marital status: Estado civil Married: Casado/a diff --git a/src/pages/Worker/Card/WorkerDescriptor.vue b/src/pages/Worker/Card/WorkerDescriptor.vue index 6f876b8fa7..a20ad55469 100644 --- a/src/pages/Worker/Card/WorkerDescriptor.vue +++ b/src/pages/Worker/Card/WorkerDescriptor.vue @@ -31,7 +31,29 @@ const entityId = computed(() => { }); const worker = ref(); -const filter = { where: { id: entityId } }; +const filter = { + include: [ + { + relation: 'user', + scope: { + fields: ['email', 'name', 'nickname'], + }, + }, + { + relation: 'department', + scope: { + include: [ + { + relation: 'department', + }, + ], + }, + }, + { + relation: 'sip', + }, + ], +}; const sip = ref(null); @@ -60,7 +82,7 @@ const setData = (entity) => { <CardDescriptor module="Worker" data-key="workerData" - url="Workers/summary" + :url="`Workers/${entityId}`" :filter="filter" :title="data.title" :subtitle="data.subtitle" diff --git a/src/pages/Worker/Card/WorkerSummary.vue b/src/pages/Worker/Card/WorkerSummary.vue index 43c493565b..dad21cda5d 100644 --- a/src/pages/Worker/Card/WorkerSummary.vue +++ b/src/pages/Worker/Card/WorkerSummary.vue @@ -10,7 +10,7 @@ import CardSummary from 'components/ui/CardSummary.vue'; import VnUserLink from 'src/components/ui/VnUserLink.vue'; import VnTitle from 'src/components/common/VnTitle.vue'; -const { params } = useRoute(); +const route = useRoute(); const { t } = useI18n(); const $props = defineProps({ @@ -20,18 +20,53 @@ const $props = defineProps({ }, }); -const entityId = computed(() => $props.id || params.id); +const entityId = computed(() => $props.id || route.params.id); const workerUrl = ref(); onMounted(async () => { workerUrl.value = (await getUrl('')) + `worker/${entityId.value}/`; }); -const filter = { where: { id: entityId.value } }; +const filter = { + include: [ + { + relation: 'user', + scope: { + fields: ['email', 'name', 'nickname', 'roleFk'], + include: { + relation: 'role', + scope: { + fields: ['name'], + }, + }, + }, + }, + { + relation: 'department', + scope: { + include: { + relation: 'department', + scope: { + fields: ['name'], + }, + }, + }, + }, + { + relation: 'boss', + }, + { + relation: 'client', + }, + { + relation: 'sip', + }, + ], +}; </script> <template> - <CardSummary ref="summary" :url="`Workers/summary`" :filter="filter"> + <CardSummary ref="summary" :url="`Workers/${entityId}`" :filter="filter"> <template #header="{ entity }"> <div>{{ entity.id }} - {{ entity.firstName }} {{ entity.lastName }}</div> </template> @@ -68,6 +103,12 @@ const filter = { where: { id: entityId.value } }; <VnLinkPhone :phone-number="worker.phone" /> </template> </VnLv> + <VnLv :value="worker.client?.phone"> + <template #label> + {{ t('worker.summary.personalPhone') }} + <VnLinkPhone :phone-number="worker.client?.phone" /> + </template> + </VnLv> <VnLv :label="t('worker.summary.locker')" :value="worker.locker" /> </QCard> <QCard class="vn-one"> From 0a71060f603ead9badd05d1f09b0424f4a14e012 Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Tue, 7 May 2024 15:28:00 +0200 Subject: [PATCH 25/27] fix: refs #6938 workerCard --- src/pages/Worker/Card/WorkerCard.vue | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/pages/Worker/Card/WorkerCard.vue b/src/pages/Worker/Card/WorkerCard.vue index e0047bf9ef..d76ef6f59e 100644 --- a/src/pages/Worker/Card/WorkerCard.vue +++ b/src/pages/Worker/Card/WorkerCard.vue @@ -1,15 +1,12 @@ <script setup> import VnCard from 'components/common/VnCard.vue'; import WorkerDescriptor from './WorkerDescriptor.vue'; - -const filter = { where: {} }; </script> <template> <VnCard data-key="Worker" - custom-url="Workers/Summary" + base-url="Workers" :descriptor="WorkerDescriptor" - :filter="filter" searchbar-data-key="WorkerList" searchbar-url="Workers/filter" searchbar-label="Search worker" From 5e0db81a211b29b50ec27878434c9d63e76dc3fe Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Tue, 7 May 2024 15:51:55 +0200 Subject: [PATCH 26/27] fix: refs #6938 conflicts --- src/pages/Worker/Card/WorkerBasicData.vue | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/pages/Worker/Card/WorkerBasicData.vue b/src/pages/Worker/Card/WorkerBasicData.vue index 0b4250c687..4f1786a67b 100644 --- a/src/pages/Worker/Card/WorkerBasicData.vue +++ b/src/pages/Worker/Card/WorkerBasicData.vue @@ -86,13 +86,6 @@ const maritalStatus = [ :label="t('Mobile extension')" clearable /> - <<<<<<< HEAD - <VnInput - v-model="data.client.phone" - :label="t('Personal phone')" - clearable - /> - ======= >>>>>>> 095e62ebac4b6b88bc431f7c87455a0bb0989e80 </VnRow> <VnRow class="row q-gutter-md q-mb-md"> From 9a6dd6577460fb2fca8c486f62339670e4b900f8 Mon Sep 17 00:00:00 2001 From: Javier Segarra <jsegarra@verdnatura.es> Date: Wed, 8 May 2024 07:52:18 +0200 Subject: [PATCH 27/27] feat: remove deprecatedHour fn --- src/pages/Zone/Card/ZoneDescriptor.vue | 11 ++--------- src/pages/Zone/ZoneList.vue | 10 ++-------- 2 files changed, 4 insertions(+), 17 deletions(-) diff --git a/src/pages/Zone/Card/ZoneDescriptor.vue b/src/pages/Zone/Card/ZoneDescriptor.vue index 486e4f063d..93e951801b 100644 --- a/src/pages/Zone/Card/ZoneDescriptor.vue +++ b/src/pages/Zone/Card/ZoneDescriptor.vue @@ -5,7 +5,7 @@ import { useI18n } from 'vue-i18n'; import CardDescriptor from 'components/ui/CardDescriptor.vue'; import VnLv from 'src/components/ui/VnLv.vue'; -import ZoneDescriptorMenuItems from './ZoneDescriptorMenuItems.vue'; +import { toTimeFormat } from 'src/filters/date'; import useCardDescription from 'src/composables/useCardDescription'; @@ -40,13 +40,6 @@ const data = ref(useCardDescription()); const setData = (entity) => { data.value = useCardDescription(entity.ref, entity.id); }; - -function extractHour(dateTime) { - const date = new Date(dateTime); - const hours = date.getHours().toString().padStart(2, '0'); - const minutes = date.getMinutes().toString().padStart(2, '0'); - return `${hours}:${minutes}`; -} </script> <template> @@ -81,7 +74,7 @@ function extractHour(dateTime) { <template #body="{ entity }"> {{ console.log('entity', entity) }} <VnLv :label="t('Agency')" :value="entity.agencyMode.name" /> - <VnLv :label="t('Closing hour')" :value="extractHour(entity.hour)" /> + <VnLv :label="t('Closing hour')" :value="toTimeFormat(entity.hour)" /> <VnLv :label="t('zoneing days')" :value="entity.zoneingDays" /> <VnLv :label="t('Price')" :value="entity.price" /> <VnLv :label="t('Bonus')" :value="entity.bonus" /> diff --git a/src/pages/Zone/ZoneList.vue b/src/pages/Zone/ZoneList.vue index 2140c5e140..f260eb1342 100644 --- a/src/pages/Zone/ZoneList.vue +++ b/src/pages/Zone/ZoneList.vue @@ -9,6 +9,7 @@ import CardList from 'components/ui/CardList.vue'; import VnLv from 'components/ui/VnLv.vue'; import FetchData from 'src/components/FetchData.vue'; import { useSummaryDialog } from 'src/composables/useSummaryDialog'; +import { toTimeFormat } from 'src/filters/date'; const quasar = useQuasar(); const arrayData = useArrayData('ZoneList'); @@ -38,13 +39,6 @@ async function remove(row) { // } } - -function extractHour(dateTime) { - const date = new Date(dateTime); - const hours = date.getHours().toString().padStart(2, '0'); - const minutes = date.getMinutes().toString().padStart(2, '0'); - return `${hours}:${minutes}`; -} </script> <template> @@ -81,7 +75,7 @@ function extractHour(dateTime) { /> <VnLv :label="t('zone.list.close')" - :value="extractHour(row?.hour)" + :value="toTimeFormat(row?.hour)" /> <VnLv :label="t('zone.list.price')" :value="row?.price" /> </template>