From 907bf3cf3bccb541cd24e17d2ca7ff25a7297d88 Mon Sep 17 00:00:00 2001 From: alexm <alexm@verdnatura.es> Date: Mon, 2 Dec 2024 11:39:01 +0100 Subject: [PATCH 01/34] feat(Account & AccountRole): refs #8197 add VnCardMain --- src/components/common/VnCard.vue | 34 ++--- src/components/common/VnCardMain.vue | 20 +++ src/components/common/VnSectionMain.vue | 3 +- src/composables/useArrayData.js | 9 +- src/pages/Account/AccountList.vue | 56 ++++---- src/pages/Account/Card/AccountCard.vue | 14 +- src/pages/Account/Role/AccountRoles.vue | 63 +++++---- src/pages/Account/Role/Card/RoleCard.vue | 15 +-- .../Account/Role/Card/RoleDescriptor.vue | 2 +- src/pages/Account/Role/Card/RoleSummary.vue | 4 +- src/router/modules/account.js | 120 ++++-------------- src/router/modules/account/accountCard.js | 71 +++++++++++ src/router/modules/account/roleCard.js | 54 ++++++++ src/router/modules/index.js | 2 - src/router/modules/role.js | 76 ----------- src/router/routes.js | 2 - src/utils/getSections.js | 8 ++ 17 files changed, 266 insertions(+), 287 deletions(-) create mode 100644 src/components/common/VnCardMain.vue create mode 100644 src/router/modules/account/accountCard.js create mode 100644 src/router/modules/account/roleCard.js delete mode 100644 src/router/modules/role.js create mode 100644 src/utils/getSections.js diff --git a/src/components/common/VnCard.vue b/src/components/common/VnCard.vue index 0d80f43ce94..88d374c74e3 100644 --- a/src/components/common/VnCard.vue +++ b/src/components/common/VnCard.vue @@ -59,32 +59,16 @@ if (props.baseUrl) { } </script> <template> - <QDrawer - v-model="stateStore.leftDrawer" - show-if-above - :width="256" - v-if="stateStore.isHeaderMounted()" - > - <QScrollArea class="fit"> - <component :is="descriptor" /> - <QSeparator /> - <LeftMenu source="card" /> - </QScrollArea> - </QDrawer> <slot name="searchbar" v-if="props.searchDataKey"> <VnSearchbar :data-key="props.searchDataKey" v-bind="props.searchbarProps" /> </slot> - <RightMenu> - <template #right-panel v-if="props.filterPanel"> - <component :is="props.filterPanel" :data-key="searchRightDataKey" /> - </template> - </RightMenu> - <QPageContainer> - <QPage> - <VnSubToolbar /> - <div :class="[useCardSize(), $attrs.class]"> - <RouterView :key="route.path" /> - </div> - </QPage> - </QPageContainer> + <Teleport to="#left-panel" v-if="stateStore.isHeaderMounted()"> + <component :is="descriptor" /> + <QSeparator /> + <LeftMenu source="card" /> + </Teleport> + <VnSubToolbar /> + <div :class="[useCardSize(), $attrs.class]"> + <RouterView :key="route.path" /> + </div> </template> diff --git a/src/components/common/VnCardMain.vue b/src/components/common/VnCardMain.vue new file mode 100644 index 00000000000..6e023153739 --- /dev/null +++ b/src/components/common/VnCardMain.vue @@ -0,0 +1,20 @@ +<script setup> +import LeftMenu from '../LeftMenu.vue'; +import { useStateStore } from 'stores/useStateStore'; +const stateStore = useStateStore(); + +defineProps({ + section: { + type: String, + required: true, + }, +}); +</script> +<template> + <slot name="searchbar" /> + <Teleport to="#left-panel" v-if="stateStore.isHeaderMounted()"> + <LeftMenu v-if="section == $route.name" /> + </Teleport> + <slot name="body" v-if="section == $route.name" /> + <RouterView v-else /> +</template> diff --git a/src/components/common/VnSectionMain.vue b/src/components/common/VnSectionMain.vue index 15be6ad9a43..c1b9808b5cf 100644 --- a/src/components/common/VnSectionMain.vue +++ b/src/components/common/VnSectionMain.vue @@ -1,6 +1,5 @@ <script setup> import { useStateStore } from 'stores/useStateStore'; -import LeftMenu from 'components/LeftMenu.vue'; import { onMounted } from 'vue'; import { useQuasar } from 'quasar'; @@ -19,7 +18,7 @@ onMounted( <template> <QDrawer v-model="stateStore.leftDrawer" show-if-above :width="256"> <QScrollArea class="fit text-grey-8"> - <LeftMenu /> + <div id="left-panel"></div> </QScrollArea> </QDrawer> <QPageContainer> diff --git a/src/composables/useArrayData.js b/src/composables/useArrayData.js index da62eee3eb9..028819a835f 100644 --- a/src/composables/useArrayData.js +++ b/src/composables/useArrayData.js @@ -75,18 +75,13 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) { limit: store.limit, }; - let userParams = { ...store.userParams }; - Object.assign(filter, store.userFilter); - let where; - if (filter?.where || store.filter?.where) - where = Object.assign(filter?.where ?? {}, store.filter?.where ?? {}); + delete store.filter.where; Object.assign(filter, store.filter); - filter.where = where; const params = { filter }; - Object.assign(params, userParams); + Object.assign(params, store.userParams); if (params.filter) params.filter.skip = store.skip; if (store?.order && typeof store?.order == 'string') store.order = [store.order]; if (store.order?.length) params.filter.order = [...store.order]; diff --git a/src/pages/Account/AccountList.vue b/src/pages/Account/AccountList.vue index cbaaf8e26af..0c88e6ac88d 100644 --- a/src/pages/Account/AccountList.vue +++ b/src/pages/Account/AccountList.vue @@ -5,14 +5,15 @@ import VnTable from 'components/VnTable/VnTable.vue'; import VnSearchbar from 'components/ui/VnSearchbar.vue'; import AccountSummary from './Card/AccountSummary.vue'; import { useSummaryDialog } from 'src/composables/useSummaryDialog'; -import AccountFilter from './AccountFilter.vue'; -import RightMenu from 'src/components/common/RightMenu.vue'; +import VnCardMain from 'src/components/common/VnCardMain.vue'; const { t } = useI18n(); const { viewSummary } = useSummaryDialog(); const tableRef = ref(); const filter = { include: { relation: 'role', scope: { fields: ['id', 'name'] } }, }; +const dataKey = 'AccountList'; +const url = 'VnUsers/preview'; const columns = computed(() => [ { align: 'left', @@ -103,31 +104,34 @@ const exprBuilder = (param, value) => { </script> <template> - <VnSearchbar - data-key="AccountList" - :expr-builder="exprBuilder" - :label="t('account.search')" - :info="t('account.searchInfo')" - :filter="filter" - /> - <RightMenu> - <template #right-panel> - <AccountFilter data-key="AccountList" /> + <VnCardMain :section="dataKey"> + <template #searchbar> + <VnSearchbar + :data-key="dataKey" + :expr-builder="exprBuilder" + :label="t('account.search')" + :info="t('account.searchInfo')" + :filter="filter" + :url="url" + /> </template> - </RightMenu> - <VnTable - ref="tableRef" - data-key="AccountList" - url="VnUsers/preview" - :filter="filter" - order="id DESC" - :columns="columns" - default-mode="table" - redirect="account" - :use-model="true" - :right-search="false" - auto-load - /> + <template #body> + <VnTable + :style="{ display: !!$route.name.endsWith('List') ? '' : 'none' }" + ref="tableRef" + :data-key="dataKey" + :url="url" + :filter="filter" + order="id DESC" + :columns="columns" + default-mode="table" + redirect="account" + :use-model="true" + :right-search="true" + :expr-builder="exprBuilder" + /> + </template> + </VnCardMain> </template> <i18n> diff --git a/src/pages/Account/Card/AccountCard.vue b/src/pages/Account/Card/AccountCard.vue index 119a7fd07ed..f69bba77842 100644 --- a/src/pages/Account/Card/AccountCard.vue +++ b/src/pages/Account/Card/AccountCard.vue @@ -1,20 +1,8 @@ <script setup> -import { useI18n } from 'vue-i18n'; import VnCard from 'components/common/VnCard.vue'; import AccountDescriptor from './AccountDescriptor.vue'; - -const { t } = useI18n(); </script> <template> - <VnCard - data-key="Account" - :descriptor="AccountDescriptor" - search-data-key="AccountList" - :searchbar-props="{ - url: 'VnUsers/preview', - label: t('account.search'), - info: t('account.searchInfo'), - }" - /> + <VnCard data-key="Account" :descriptor="AccountDescriptor" /> </template> diff --git a/src/pages/Account/Role/AccountRoles.vue b/src/pages/Account/Role/AccountRoles.vue index 5a27e2ed607..683de061675 100644 --- a/src/pages/Account/Role/AccountRoles.vue +++ b/src/pages/Account/Role/AccountRoles.vue @@ -6,8 +6,11 @@ import { useRoute } from 'vue-router'; import VnSearchbar from 'components/ui/VnSearchbar.vue'; import { useSummaryDialog } from 'src/composables/useSummaryDialog'; import RoleSummary from './Card/RoleSummary.vue'; +import VnCardMain from 'src/components/common/VnCardMain.vue'; + const route = useRoute(); const { t } = useI18n(); +const { viewSummary } = useSummaryDialog(); const $props = defineProps({ id: { type: Number, @@ -15,8 +18,10 @@ const $props = defineProps({ }, }); const tableRef = ref(); +const url = 'VnRoles'; +const dataKey = 'AccountRoleList'; + const entityId = computed(() => $props.id || route.params.id); -const { viewSummary } = useSummaryDialog(); const columns = computed(() => [ { align: 'left', @@ -63,6 +68,7 @@ const columns = computed(() => [ }, ]); const exprBuilder = (param, value) => { + console.log('param: ', param); switch (param) { case 'search': return /^\d+$/.test(value) @@ -81,30 +87,37 @@ const exprBuilder = (param, value) => { </script> <template> - <VnSearchbar - data-key="AccountRolesList" - :expr-builder="exprBuilder" - :label="t('role.searchRoles')" - :info="t('role.searchInfo')" - /> - <VnTable - ref="tableRef" - data-key="AccountRolesList" - :url="`VnRoles`" - :create="{ - urlCreate: 'VnRoles', - title: t('Create rol'), - onDataSaved: ({ id }) => tableRef.redirect(id), - formInitialData: { - editorFk: entityId, - }, - }" - order="id ASC" - :disable-option="{ card: true }" - :columns="columns" - default-mode="table" - redirect="account/role" - /> + <VnCardMain :section="dataKey"> + <template #searchbar> + <VnSearchbar + :url="url" + :data-key="dataKey" + :expr-builder="exprBuilder" + :label="t('role.searchRoles')" + :info="t('role.searchInfo')" + /> + </template> + <template #body> + <VnTable + ref="tableRef" + :data-key="dataKey" + :url="url" + :create="{ + urlCreate: 'VnRoles', + title: t('Create rol'), + onDataSaved: ({ id }) => tableRef.redirect(id), + formInitialData: { + editorFk: entityId, + }, + }" + order="id ASC" + :disable-option="{ card: true }" + :columns="columns" + default-mode="table" + redirect="account/role" + /> + </template> + </VnCardMain> </template> <i18n> diff --git a/src/pages/Account/Role/Card/RoleCard.vue b/src/pages/Account/Role/Card/RoleCard.vue index a2d5710f47a..da6ac61d87a 100644 --- a/src/pages/Account/Role/Card/RoleCard.vue +++ b/src/pages/Account/Role/Card/RoleCard.vue @@ -1,20 +1,7 @@ <script setup> -import { useI18n } from 'vue-i18n'; import VnCard from 'components/common/VnCard.vue'; import RoleDescriptor from './RoleDescriptor.vue'; - -const { t } = useI18n(); </script> <template> - <VnCard - data-key="Role" - :descriptor="RoleDescriptor" - search-data-key="AccountRolesList" - :searchbar-props="{ - url: 'VnRoles', - label: t('role.searchRoles'), - info: t('role.searchInfo'), - searchUrl: 'table', - }" - /> + <VnCard data-key="Role" :descriptor="RoleDescriptor" /> </template> diff --git a/src/pages/Account/Role/Card/RoleDescriptor.vue b/src/pages/Account/Role/Card/RoleDescriptor.vue index 693fcdf48a6..b4b4fe3168d 100644 --- a/src/pages/Account/Role/Card/RoleDescriptor.vue +++ b/src/pages/Account/Role/Card/RoleDescriptor.vue @@ -43,7 +43,7 @@ const removeRole = async () => { :filter="filter" module="Role" @on-fetch="setData" - data-key="accountData" + data-key="Role" :title="data.title" :subtitle="data.subtitle" :summary="$props.summary" diff --git a/src/pages/Account/Role/Card/RoleSummary.vue b/src/pages/Account/Role/Card/RoleSummary.vue index fef85f9193a..f0daa77fb8f 100644 --- a/src/pages/Account/Role/Card/RoleSummary.vue +++ b/src/pages/Account/Role/Card/RoleSummary.vue @@ -27,10 +27,10 @@ const filter = { <template> <CardSummary ref="summary" - :url="`VnRoles`" + :url="`VnRoles/${entityId}`" :filter="filter" @on-fetch="(data) => (role = data)" - data-key="RoleSummary" + data-key="Role" > <template #header> {{ role.id }} - {{ role.name }} </template> <template #body> diff --git a/src/router/modules/account.js b/src/router/modules/account.js index 7200131dafa..ece0ab2bb89 100644 --- a/src/router/modules/account.js +++ b/src/router/modules/account.js @@ -1,4 +1,7 @@ import { RouterView } from 'vue-router'; +import accountCard from './account/accountCard'; +import roleCard from './account/roleCard'; +import getSections from 'src/utils/getSections'; export default { path: '/account', @@ -22,39 +25,48 @@ export default { 'AccountAcls', 'AccountConnections', ], - card: [ - 'AccountBasicData', - 'AccountInheritedRoles', - 'AccountMailForwarding', - 'AccountMailAlias', - 'AccountPrivileges', - 'AccountLog', - ], + card: getSections(accountCard.children), }, children: [ { path: '', name: 'AccountMain', component: () => import('src/components/common/VnSectionMain.vue'), - redirect: { name: 'AccountList' }, + redirect: { name: 'AccountIndexMain' }, children: [ { - path: 'list', - name: 'AccountList', - meta: { - title: 'list', - icon: 'view_list', - }, + path: '', + name: 'AccountIndexMain', + redirect: { name: 'AccountList' }, component: () => import('src/pages/Account/AccountList.vue'), + children: [ + { + name: 'AccountList', + path: 'list', + meta: { + title: 'list', + icon: 'view_list', + }, + }, + accountCard, + ], }, { - path: 'role-list', + path: 'role', name: 'AccountRoles', + redirect: { name: 'AccountRoleList' }, meta: { title: 'roles', icon: 'group', }, component: () => import('src/pages/Account/Role/AccountRoles.vue'), + children: [ + { + name: 'AccountRoleList', + path: 'list', + }, + roleCard, + ], }, { path: 'alias-list', @@ -120,81 +132,5 @@ export default { }, ], }, - { - name: 'AccountCard', - path: ':id', - component: () => import('src/pages/Account/Card/AccountCard.vue'), - redirect: { name: 'AccountSummary' }, - children: [ - { - name: 'AccountSummary', - path: 'summary', - meta: { - title: 'summary', - icon: 'launch', - }, - component: () => import('src/pages/Account/Card/AccountSummary.vue'), - }, - { - name: 'AccountBasicData', - path: 'basic-data', - meta: { - title: 'basicData', - icon: 'vn:settings', - }, - component: () => - import('src/pages/Account/Card/AccountBasicData.vue'), - }, - { - name: 'AccountInheritedRoles', - path: 'inherited-roles', - meta: { - title: 'inheritedRoles', - icon: 'group', - }, - component: () => - import('src/pages/Account/Card/AccountInheritedRoles.vue'), - }, - { - name: 'AccountMailForwarding', - path: 'mail-forwarding', - meta: { - title: 'mailForwarding', - icon: 'forward', - }, - component: () => - import('src/pages/Account/Card/AccountMailForwarding.vue'), - }, - { - name: 'AccountMailAlias', - path: 'mail-alias', - meta: { - title: 'mailAlias', - icon: 'email', - }, - component: () => - import('src/pages/Account/Card/AccountMailAlias.vue'), - }, - { - name: 'AccountPrivileges', - path: 'privileges', - meta: { - title: 'privileges', - icon: 'badge', - }, - component: () => - import('src/pages/Account/Card/AccountPrivileges.vue'), - }, - { - name: 'AccountLog', - path: 'log', - meta: { - title: 'log', - icon: 'history', - }, - component: () => import('src/pages/Account/Card/AccountLog.vue'), - }, - ], - }, ], }; diff --git a/src/router/modules/account/accountCard.js b/src/router/modules/account/accountCard.js new file mode 100644 index 00000000000..0d8850f10aa --- /dev/null +++ b/src/router/modules/account/accountCard.js @@ -0,0 +1,71 @@ +export default { + name: 'AccountCard', + path: ':id', + redirect: { name: 'AccountSummary' }, + component: () => import('src/pages/Account/Card/AccountCard.vue'), + children: [ + { + name: 'AccountSummary', + path: 'summary', + meta: { + title: 'summary', + icon: 'launch', + }, + component: () => import('src/pages/Account/Card/AccountSummary.vue'), + }, + { + name: 'AccountBasicData', + path: 'basic-data', + meta: { + title: 'basicData', + icon: 'vn:settings', + }, + component: () => import('src/pages/Account/Card/AccountBasicData.vue'), + }, + { + name: 'AccountInheritedRoles', + path: 'inherited-roles', + meta: { + title: 'inheritedRoles', + icon: 'group', + }, + component: () => import('src/pages/Account/Card/AccountInheritedRoles.vue'), + }, + { + name: 'AccountMailForwarding', + path: 'mail-forwarding', + meta: { + title: 'mailForwarding', + icon: 'forward', + }, + component: () => import('src/pages/Account/Card/AccountMailForwarding.vue'), + }, + { + name: 'AccountMailAlias', + path: 'mail-alias', + meta: { + title: 'mailAlias', + icon: 'email', + }, + component: () => import('src/pages/Account/Card/AccountMailAlias.vue'), + }, + { + name: 'AccountPrivileges', + path: 'privileges', + meta: { + title: 'privileges', + icon: 'badge', + }, + component: () => import('src/pages/Account/Card/AccountPrivileges.vue'), + }, + { + name: 'AccountLog', + path: 'log', + meta: { + title: 'log', + icon: 'history', + }, + component: () => import('src/pages/Account/Card/AccountLog.vue'), + }, + ], +}; diff --git a/src/router/modules/account/roleCard.js b/src/router/modules/account/roleCard.js new file mode 100644 index 00000000000..2a538756873 --- /dev/null +++ b/src/router/modules/account/roleCard.js @@ -0,0 +1,54 @@ +export default { + name: 'RoleCard', + path: ':id', + component: () => import('src/pages/Account/Role/Card/RoleCard.vue'), + redirect: { name: 'RoleSummary' }, + children: [ + { + name: 'RoleSummary', + path: 'summary', + meta: { + title: 'summary', + icon: 'launch', + }, + component: () => import('src/pages/Account/Role/Card/RoleSummary.vue'), + }, + { + name: 'RoleBasicData', + path: 'basic-data', + meta: { + title: 'basicData', + icon: 'vn:settings', + }, + component: () => import('src/pages/Account/Role/Card/RoleBasicData.vue'), + }, + { + name: 'SubRoles', + path: 'sub-roles', + meta: { + title: 'subRoles', + icon: 'group', + }, + component: () => import('src/pages/Account/Role/Card/SubRoles.vue'), + }, + + { + name: 'InheritedRoles', + path: 'inherited-roles', + meta: { + title: 'inheritedRoles', + icon: 'account_tree', + }, + component: () => import('src/pages/Account/Role/Card/InheritedRoles.vue'), + }, + { + name: 'RoleLog', + path: 'log', + meta: { + title: 'log', + icon: 'history', + }, + component: () => import('src/pages/Account/Role/Card/RoleLog.vue'), + }, + ], +}; diff --git a/src/router/modules/index.js b/src/router/modules/index.js index bf7e46b000d..fb1bdc46667 100644 --- a/src/router/modules/index.js +++ b/src/router/modules/index.js @@ -21,7 +21,6 @@ import Zone from './zone'; import Account from './account'; import Monitor from './monitor'; import MailAlias from './mailAlias'; -import Role from './role'; export default [ Item, @@ -47,5 +46,4 @@ export default [ Account, MailAlias, Monitor, - Role, ]; diff --git a/src/router/modules/role.js b/src/router/modules/role.js deleted file mode 100644 index 47cd10b1889..00000000000 --- a/src/router/modules/role.js +++ /dev/null @@ -1,76 +0,0 @@ -import { RouterView } from 'vue-router'; - -export default { - path: 'account/role', - name: 'Role', - meta: { - title: 'role', - icon: 'vn:greuge', - moduleName: 'Role', - }, - component: RouterView, - redirect: { name: 'AccountRoles' }, - menus: { - main: [], - card: ['RoleBasicData', 'SubRoles', 'InheritedRoles', 'RoleLog'], - }, - children: [ - { - name: 'RoleCard', - path: ':id', - component: () => import('src/pages/Account/Role/Card/RoleCard.vue'), - redirect: { name: 'RoleSummary' }, - children: [ - { - name: 'RoleSummary', - path: 'summary', - meta: { - title: 'summary', - icon: 'launch', - }, - component: () => - import('src/pages/Account/Role/Card/RoleSummary.vue'), - }, - { - name: 'RoleBasicData', - path: 'basic-data', - meta: { - title: 'basicData', - icon: 'vn:settings', - }, - component: () => - import('src/pages/Account/Role/Card/RoleBasicData.vue'), - }, - { - name: 'SubRoles', - path: 'sub-roles', - meta: { - title: 'subRoles', - icon: 'group', - }, - component: () => import('src/pages/Account/Role/Card/SubRoles.vue'), - }, - - { - name: 'InheritedRoles', - path: 'inherited-roles', - meta: { - title: 'inheritedRoles', - icon: 'account_tree', - }, - component: () => - import('src/pages/Account/Role/Card/InheritedRoles.vue'), - }, - { - name: 'RoleLog', - path: 'log', - meta: { - title: 'log', - icon: 'history', - }, - component: () => import('src/pages/Account/Role/Card/RoleLog.vue'), - }, - ], - }, - ], -}; diff --git a/src/router/routes.js b/src/router/routes.js index cced308b5a3..d332be94194 100644 --- a/src/router/routes.js +++ b/src/router/routes.js @@ -10,7 +10,6 @@ import wagon from './modules/wagon'; import supplier from './modules/Supplier'; import travel from './modules/travel'; import department from './modules/department'; -import role from './modules/role'; import ItemType from './modules/itemType'; import shelving from 'src/router/modules/shelving'; import order from 'src/router/modules/order'; @@ -95,7 +94,6 @@ const routes = [ ItemType, zone, account, - role, mailAlias, { path: '/:catchAll(.*)*', diff --git a/src/utils/getSections.js b/src/utils/getSections.js new file mode 100644 index 00000000000..f70daf4685c --- /dev/null +++ b/src/utils/getSections.js @@ -0,0 +1,8 @@ +export default (sections) => { + const names = []; + for (const section of sections) { + if (section.path == 'summary') continue; + names.push(section.name); + } + return names; +}; From 68fc5653241e7cb67cb580599dcc67bc9fd28394 Mon Sep 17 00:00:00 2001 From: alexm <alexm@verdnatura.es> Date: Wed, 4 Dec 2024 09:44:09 +0100 Subject: [PATCH 02/34] feat(VnPaginate): refs #8197 hold data when change to Card --- src/components/common/VnSelect.vue | 2 +- src/components/ui/VnFilterPanel.vue | 2 +- src/components/ui/VnPaginate.vue | 6 ++++-- src/components/ui/VnSearchbar.vue | 2 +- src/composables/useArrayData.js | 10 +++++++--- src/pages/Account/AccountList.vue | 2 +- src/pages/Account/Card/AccountCard.vue | 2 +- src/pages/Account/Card/AccountDescriptor.vue | 2 +- src/stores/useArrayDataStore.js | 6 ++++++ 9 files changed, 23 insertions(+), 11 deletions(-) diff --git a/src/components/common/VnSelect.vue b/src/components/common/VnSelect.vue index f24f054a5fb..db47231f48a 100644 --- a/src/components/common/VnSelect.vue +++ b/src/components/common/VnSelect.vue @@ -201,7 +201,7 @@ async function fetchFilter(val) { const fetchOptions = { where, include, limit }; if (fields) fetchOptions.fields = fields; if (sortBy) fetchOptions.order = sortBy; - arrayData.reset(['skip', 'filter.skip', 'page']); + arrayData.resetPagination(); const { data } = await arrayData.applyFilter({ filter: fetchOptions }); setOptions(data); diff --git a/src/components/ui/VnFilterPanel.vue b/src/components/ui/VnFilterPanel.vue index b188bde4820..716d8331fcf 100644 --- a/src/components/ui/VnFilterPanel.vue +++ b/src/components/ui/VnFilterPanel.vue @@ -138,7 +138,7 @@ async function clearFilters() { try { isLoading.value = true; store.userParamsChanged = true; - arrayData.reset(['skip', 'filter.skip', 'page']); + arrayData.resetPagination(); // Filtrar los params no removibles const removableFilters = Object.keys(userParams.value).filter((param) => $props.unremovableParams.includes(param) diff --git a/src/components/ui/VnPaginate.vue b/src/components/ui/VnPaginate.vue index 3649ba8f551..c5fbbb7314b 100644 --- a/src/components/ui/VnPaginate.vue +++ b/src/components/ui/VnPaginate.vue @@ -104,7 +104,9 @@ onMounted(async () => { mounted.value = true; }); -onBeforeUnmount(() => arrayData.reset()); +onBeforeUnmount(() => { + arrayData.resetPagination(); +}); watch( () => props.data, @@ -132,7 +134,7 @@ const addFilter = async (filter, params) => { async function fetch(params) { useArrayData(props.dataKey, params); - arrayData.reset(['filter.skip', 'skip', 'page']); + arrayData.resetPagination(); await arrayData.fetch({ append: false, updateRouter: mounted.value }); return emitStoreData(); } diff --git a/src/components/ui/VnSearchbar.vue b/src/components/ui/VnSearchbar.vue index da2d370fe09..a5690f35a9c 100644 --- a/src/components/ui/VnSearchbar.vue +++ b/src/components/ui/VnSearchbar.vue @@ -101,7 +101,7 @@ onMounted(() => { async function search() { const staticParams = Object.entries(store.userParams); - arrayData.reset(['skip', 'page']); + arrayData.resetPagination(); const filter = { params: { diff --git a/src/composables/useArrayData.js b/src/composables/useArrayData.js index 028819a835f..ee66f6be7d8 100644 --- a/src/composables/useArrayData.js +++ b/src/composables/useArrayData.js @@ -142,6 +142,10 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) { if (arrayDataStore.get(key)) arrayDataStore.reset(key, opts); } + function resetPagination() { + if (arrayDataStore.get(key)) arrayDataStore.resetPagination(key); + } + function cancelRequest() { if (canceller) { canceller.abort(); @@ -165,7 +169,7 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) { userParams = sanitizerParams(userParams, store?.exprBuilder); store.userParams = userParams; - reset(['skip', 'filter.skip', 'page']); + resetPagination(); await fetch({}); return { filter, params }; @@ -192,7 +196,7 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) { } store.order = order; - reset(['skip', 'filter.skip', 'page']); + resetPagination(); fetch({}); index++; @@ -275,7 +279,6 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) { const pushUrl = { path: to }; if (to.endsWith('/list') || to.endsWith('/')) pushUrl.query = newUrl.query; - else destroy(); return router.push(pushUrl); } } @@ -302,5 +305,6 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) { isLoading, deleteOption, reset, + resetPagination, }; } diff --git a/src/pages/Account/AccountList.vue b/src/pages/Account/AccountList.vue index 0c88e6ac88d..4b8e8fb2836 100644 --- a/src/pages/Account/AccountList.vue +++ b/src/pages/Account/AccountList.vue @@ -6,6 +6,7 @@ import VnSearchbar from 'components/ui/VnSearchbar.vue'; import AccountSummary from './Card/AccountSummary.vue'; import { useSummaryDialog } from 'src/composables/useSummaryDialog'; import VnCardMain from 'src/components/common/VnCardMain.vue'; + const { t } = useI18n(); const { viewSummary } = useSummaryDialog(); const tableRef = ref(); @@ -117,7 +118,6 @@ const exprBuilder = (param, value) => { </template> <template #body> <VnTable - :style="{ display: !!$route.name.endsWith('List') ? '' : 'none' }" ref="tableRef" :data-key="dataKey" :url="url" diff --git a/src/pages/Account/Card/AccountCard.vue b/src/pages/Account/Card/AccountCard.vue index f69bba77842..ba9040852cf 100644 --- a/src/pages/Account/Card/AccountCard.vue +++ b/src/pages/Account/Card/AccountCard.vue @@ -4,5 +4,5 @@ import AccountDescriptor from './AccountDescriptor.vue'; </script> <template> - <VnCard data-key="Account" :descriptor="AccountDescriptor" /> + <VnCard data-key="AccountId" :descriptor="AccountDescriptor" /> </template> diff --git a/src/pages/Account/Card/AccountDescriptor.vue b/src/pages/Account/Card/AccountDescriptor.vue index 3156f8e1ec3..4e10e1366d6 100644 --- a/src/pages/Account/Card/AccountDescriptor.vue +++ b/src/pages/Account/Card/AccountDescriptor.vue @@ -41,7 +41,7 @@ const hasAccount = ref(false); /> <CardDescriptor ref="descriptor" - :url="`VnUsers/preview`" + url="VnUsers/preview" :filter="filter" module="Account" @on-fetch="setData" diff --git a/src/stores/useArrayDataStore.js b/src/stores/useArrayDataStore.js index 6a0e7dfa8d1..be65de19a65 100644 --- a/src/stores/useArrayDataStore.js +++ b/src/stores/useArrayDataStore.js @@ -49,10 +49,16 @@ export const useArrayDataStore = defineStore('arrayDataStore', () => { }); } + function resetPagination(key) { + reset(key, ['skip', 'filter.skip', 'page']); + } + return { + state, get, set, clear, reset, + resetPagination, }; }); From 1b2af7cb84e0b033ba3425a552f627e92feaaf09 Mon Sep 17 00:00:00 2001 From: alexm <alexm@verdnatura.es> Date: Wed, 4 Dec 2024 09:44:21 +0100 Subject: [PATCH 03/34] chore: refs #8197 remove console log --- src/pages/Account/Role/AccountRoles.vue | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/Account/Role/AccountRoles.vue b/src/pages/Account/Role/AccountRoles.vue index 683de061675..74c4ab8a558 100644 --- a/src/pages/Account/Role/AccountRoles.vue +++ b/src/pages/Account/Role/AccountRoles.vue @@ -68,7 +68,6 @@ const columns = computed(() => [ }, ]); const exprBuilder = (param, value) => { - console.log('param: ', param); switch (param) { case 'search': return /^\d+$/.test(value) From 2d2501838b63303ae4bbbbfe5d2688f17b8c15cc Mon Sep 17 00:00:00 2001 From: alexm <alexm@verdnatura.es> Date: Wed, 4 Dec 2024 09:49:48 +0100 Subject: [PATCH 04/34] revert: refs #8197 arrayData changes --- src/composables/useArrayData.js | 9 +++++++-- src/pages/Account/Card/AccountSummary.vue | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/composables/useArrayData.js b/src/composables/useArrayData.js index ee66f6be7d8..c36eb99900c 100644 --- a/src/composables/useArrayData.js +++ b/src/composables/useArrayData.js @@ -75,13 +75,18 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) { limit: store.limit, }; + let userParams = { ...store.userParams }; + Object.assign(filter, store.userFilter); - delete store.filter.where; + let where; + if (filter?.where || store.filter?.where) + where = Object.assign(filter?.where ?? {}, store.filter?.where ?? {}); Object.assign(filter, store.filter); + filter.where = where; const params = { filter }; - Object.assign(params, store.userParams); + Object.assign(params, userParams); if (params.filter) params.filter.skip = store.skip; if (store?.order && typeof store?.order == 'string') store.order = [store.order]; if (store.order?.length) params.filter.order = [...store.order]; diff --git a/src/pages/Account/Card/AccountSummary.vue b/src/pages/Account/Card/AccountSummary.vue index 5a21e18a5c9..e6c21ed34af 100644 --- a/src/pages/Account/Card/AccountSummary.vue +++ b/src/pages/Account/Card/AccountSummary.vue @@ -30,7 +30,7 @@ const filter = { <template> <CardSummary - data-key="AccountSummary" + data-key="AccountId" ref="AccountSummary" url="VnUsers/preview" :filter="filter" From 1d86b2912944b9c1abc5d7b9e040a93966c1077d Mon Sep 17 00:00:00 2001 From: alexm <alexm@verdnatura.es> Date: Mon, 9 Dec 2024 11:21:18 +0100 Subject: [PATCH 05/34] feat: refs #8197 vnTableFilter --- src/components/VnTable/VnFilter.vue | 5 +- src/components/VnTable/VnTable.vue | 138 +++-------------------- src/components/VnTable/VnTableFilter.vue | 85 ++++++++++++++ src/components/common/VnCard.vue | 11 +- src/components/common/VnCardMain.vue | 2 + src/components/ui/VnFilterPanel.vue | 93 +++++---------- src/components/ui/VnPaginate.vue | 10 +- src/composables/useArrayData.js | 8 +- src/composables/useFilterParams.js | 65 +++++++++++ src/pages/Account/AccountList.vue | 30 +++-- src/utils/getUserParams.js | 0 11 files changed, 233 insertions(+), 214 deletions(-) create mode 100644 src/components/VnTable/VnTableFilter.vue create mode 100644 src/composables/useFilterParams.js create mode 100644 src/utils/getUserParams.js diff --git a/src/components/VnTable/VnFilter.vue b/src/components/VnTable/VnFilter.vue index 86802ee92ae..d859d12aa28 100644 --- a/src/components/VnTable/VnFilter.vue +++ b/src/components/VnTable/VnFilter.vue @@ -32,7 +32,10 @@ const $props = defineProps({ defineExpose({ addFilter, props: $props }); const model = defineModel(undefined, { required: true }); -const arrayData = useArrayData($props.dataKey, { searchUrl: $props.searchUrl }); +const arrayData = useArrayData( + $props.dataKey, + $props.searchUrl ? { searchUrl: $props.searchUrl } : null +); const columnFilter = computed(() => $props.column?.columnFilter); const updateEvent = { 'update:modelValue': addFilter }; diff --git a/src/components/VnTable/VnTable.vue b/src/components/VnTable/VnTable.vue index 94147708408..324c49cde98 100644 --- a/src/components/VnTable/VnTable.vue +++ b/src/components/VnTable/VnTable.vue @@ -1,20 +1,21 @@ <script setup> -import { ref, onBeforeMount, onMounted, computed, watch } from 'vue'; +import { ref, onBeforeMount, onMounted, computed, watch, useAttrs } from 'vue'; import { useI18n } from 'vue-i18n'; import { useRoute, useRouter } from 'vue-router'; import { useQuasar } from 'quasar'; import { useStateStore } from 'stores/useStateStore'; +import { useFilterParams } from 'src/composables/useFilterParams'; import CrudModel from 'src/components/CrudModel.vue'; import FormModelPopup from 'components/FormModelPopup.vue'; -import VnFilterPanel from 'components/ui/VnFilterPanel.vue'; import VnTableColumn from 'components/VnTable/VnColumn.vue'; import VnFilter from 'components/VnTable/VnFilter.vue'; import VnTableChip from 'components/VnTable/VnChip.vue'; import VnVisibleColumn from 'src/components/VnTable/VnVisibleColumn.vue'; import VnLv from 'components/ui/VnLv.vue'; import VnTableOrder from 'src/components/VnTable/VnOrder.vue'; +import VnTableFilter from './VnTableFilter.vue'; const $props = defineProps({ columns: { @@ -33,6 +34,10 @@ const $props = defineProps({ type: Boolean, default: true, }, + rightSearchIcon: { + type: Boolean, + default: true, + }, rowClick: { type: [Function, Boolean], default: null, @@ -101,10 +106,6 @@ const $props = defineProps({ type: String, default: '90vh', }, - chipLocale: { - type: String, - default: null, - }, footer: { type: Boolean, default: false, @@ -119,22 +120,21 @@ const stateStore = useStateStore(); const route = useRoute(); const router = useRouter(); const quasar = useQuasar(); +const $attrs = useAttrs(); const CARD_MODE = 'card'; const TABLE_MODE = 'table'; const mode = ref(CARD_MODE); const selected = ref([]); const hasParams = ref(false); -const routeQuery = JSON.parse(route?.query[$props.searchUrl] ?? '{}'); -const params = ref({ ...routeQuery, ...routeQuery.filter?.where }); -const orders = ref(parseOrder(routeQuery.filter?.order)); const CrudModelRef = ref({}); const showForm = ref(false); const splittedColumns = ref({ columns: [] }); const columnsVisibilitySkipped = ref(); const createForm = ref(); -const tableFilterRef = ref([]); const tableRef = ref(); +const params = ref(useFilterParams($attrs['data-key']).params); +const orders = ref(useFilterParams($attrs['data-key']).orders); const tableModes = [ { @@ -163,7 +163,7 @@ onMounted(() => { stateStore.rightDrawer = quasar.screen.gt.xs; columnsVisibilitySkipped.value = [ ...splittedColumns.value.columns - .filter((c) => c.visible == false) + .filter((c) => c.visible === false) .map((c) => c.name), ...['tableActions'], ]; @@ -183,41 +183,8 @@ watch( { immediate: true } ); -watch( - () => route.query[$props.searchUrl], - (val) => setUserParams(val), - { immediate: true, deep: true } -); - const isTableMode = computed(() => mode.value == TABLE_MODE); - -function setUserParams(watchedParams, watchedOrder) { - if (!watchedParams) return; - - if (typeof watchedParams == 'string') watchedParams = JSON.parse(watchedParams); - const filter = - typeof watchedParams?.filter == 'string' - ? JSON.parse(watchedParams?.filter ?? '{}') - : watchedParams?.filter; - const where = filter?.where; - const order = watchedOrder ?? filter?.order; - - watchedParams = { ...watchedParams, ...where }; - delete watchedParams.filter; - delete params.value?.filter; - params.value = { ...params.value, ...sanitizer(watchedParams) }; - orders.value = parseOrder(order); -} - -function sanitizer(params) { - for (const [key, value] of Object.entries(params)) { - if (value && typeof value == 'object') { - const param = Object.values(value)[0]; - if (typeof param == 'string') params[key] = param.replaceAll('%', ''); - } - } - return params; -} +const showRightIcon = computed(() => $props.rightSearch || $props.rightSearchIcon); function splitColumns(columns) { splittedColumns.value = { @@ -298,17 +265,6 @@ function getColAlign(col) { return 'text-' + (col.align ?? 'left'); } -function parseOrder(urlOrders) { - const orderObject = {}; - if (!urlOrders) return orderObject; - if (typeof urlOrders == 'string') urlOrders = [urlOrders]; - for (const [index, orders] of urlOrders.entries()) { - const [name, direction] = orders.split(' '); - orderObject[name] = { direction, index: index + 1 }; - } - return orderObject; -} - const emit = defineEmits(['onFetch', 'update:selected', 'saveChanges']); defineExpose({ create: createForm, @@ -349,71 +305,11 @@ function handleSelection({ evt, added, rows: selectedRows }, rows) { } </script> <template> - <QDrawer + <VnTableFilter v-if="$props.rightSearch" - v-model="stateStore.rightDrawer" - side="right" - :width="256" - show-if-above - > - <QScrollArea class="fit"> - <VnFilterPanel - :data-key="$attrs['data-key']" - :search-button="true" - v-model="params" - :search-url="searchUrl" - :redirect="!!redirect" - @set-user-params="setUserParams" - :disable-submit-event="true" - @remove=" - (key) => - tableFilterRef - .find((f) => f.props?.column.name == key) - ?.addFilter() - " - > - <template #body> - <div - class="row no-wrap flex-center" - v-for="col of splittedColumns.columns.filter( - (c) => c.columnFilter ?? true - )" - :key="col.id" - > - <VnFilter - ref="tableFilterRef" - :column="col" - :data-key="$attrs['data-key']" - v-model="params[columnName(col)]" - :search-url="searchUrl" - /> - <VnTableOrder - v-if=" - col?.columnFilter !== false && - col?.name !== 'tableActions' - " - v-model="orders[col.orderBy ?? col.name]" - :name="col.orderBy ?? col.name" - :data-key="$attrs['data-key']" - :search-url="searchUrl" - :vertical="true" - /> - </div> - <slot - name="moreFilterPanel" - :params="params" - :columns="splittedColumns.columns" - /> - </template> - <template #tags="{ tag, formatFn }" v-if="chipLocale"> - <div class="q-gutter-x-xs"> - <strong>{{ t(`${chipLocale}.${tag.label}`) }}: </strong> - <span>{{ formatFn(tag.value) }}</span> - </div> - </template> - </VnFilterPanel> - </QScrollArea> - </QDrawer> + :data-key="$attrs['data-key']" + :columns="columns" + /> <CrudModel v-bind="$attrs" :class="$attrs['class'] ?? 'q-px-md'" @@ -467,7 +363,7 @@ function handleSelection({ evt, added, rows: selectedRows }, rows) { :options="tableModes.filter((mode) => !mode.disable)" /> <QBtn - v-if="$props.rightSearch" + v-if="showRightIcon" icon="filter_alt" class="bg-vn-section-color q-ml-sm" dense diff --git a/src/components/VnTable/VnTableFilter.vue b/src/components/VnTable/VnTableFilter.vue new file mode 100644 index 00000000000..2d1758786e7 --- /dev/null +++ b/src/components/VnTable/VnTableFilter.vue @@ -0,0 +1,85 @@ +<script setup> +import { ref } from 'vue'; +import { useI18n } from 'vue-i18n'; +import { useStateStore } from 'stores/useStateStore'; + +import VnFilterPanel from 'components/ui/VnFilterPanel.vue'; +import VnFilter from 'components/VnTable/VnFilter.vue'; +import VnTableOrder from 'src/components/VnTable/VnOrder.vue'; + +defineProps({ + columns: { + type: Array, + required: true, + }, + chipLocale: { + type: String, + default: null, + }, + searchUrl: { + type: [String, Boolean], + default: 'table', + }, +}); +const { t } = useI18n(); +const stateStore = useStateStore(); + +const tableFilterRef = ref([]); + +function columnName(col) { + const column = { ...col, ...col.columnFilter }; + let name = column.name; + if (column.alias) name = column.alias + '.' + name; + return name; +} +</script> +<template> + <QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above> + <QScrollArea class="fit"> + <VnFilterPanel + v-bind="$attrs" + :search-button="true" + :disable-submit-event="true" + > + <template #body="{ params, orders }"> + <div + class="row no-wrap flex-center" + v-for="col of columns.filter((c) => c.columnFilter ?? true)" + :key="col.id" + > + <VnFilter + ref="tableFilterRef" + :column="col" + :data-key="$attrs['data-key']" + v-model="params[columnName(col)]" + :search-url="searchUrl" + /> + <VnTableOrder + v-if=" + col?.columnFilter !== false && + col?.name !== 'tableActions' + " + v-model="orders[col.orderBy ?? col.name]" + :name="col.orderBy ?? col.name" + :data-key="$attrs['data-key']" + :search-url="searchUrl" + :vertical="true" + /> + </div> + <slot + name="moreFilterPanel" + :params="params" + :orders="orders" + :columns="columns" + /> + </template> + <template #tags="{ tag, formatFn }" v-if="chipLocale"> + <div class="q-gutter-x-xs"> + <strong>{{ t(`${chipLocale}.${tag.label}`) }}: </strong> + <span>{{ formatFn(tag.value) }}</span> + </div> + </template> + </VnFilterPanel> + </QScrollArea> + </QDrawer> +</template> diff --git a/src/components/common/VnCard.vue b/src/components/common/VnCard.vue index 88d374c74e3..16a077a79f1 100644 --- a/src/components/common/VnCard.vue +++ b/src/components/common/VnCard.vue @@ -4,10 +4,7 @@ import { useRoute, useRouter, onBeforeRouteUpdate } from 'vue-router'; import { useArrayData } from 'src/composables/useArrayData'; import { useStateStore } from 'stores/useStateStore'; import useCardSize from 'src/composables/useCardSize'; -import VnSubToolbar from '../ui/VnSubToolbar.vue'; -import VnSearchbar from 'components/ui/VnSearchbar.vue'; import LeftMenu from 'components/LeftMenu.vue'; -import RightMenu from 'components/common/RightMenu.vue'; const props = defineProps({ dataKey: { type: String, required: true }, baseUrl: { type: String, default: undefined }, @@ -29,10 +26,7 @@ const url = computed(() => { } return props.customUrl; }); -const searchRightDataKey = computed(() => { - if (!props.searchDataKey) return route.name; - return props.searchDataKey; -}); + const arrayData = useArrayData(props.dataKey, { url: url.value, filter: props.filter, @@ -59,9 +53,6 @@ if (props.baseUrl) { } </script> <template> - <slot name="searchbar" v-if="props.searchDataKey"> - <VnSearchbar :data-key="props.searchDataKey" v-bind="props.searchbarProps" /> - </slot> <Teleport to="#left-panel" v-if="stateStore.isHeaderMounted()"> <component :is="descriptor" /> <QSeparator /> diff --git a/src/components/common/VnCardMain.vue b/src/components/common/VnCardMain.vue index 6e023153739..3ebfcfb8bdc 100644 --- a/src/components/common/VnCardMain.vue +++ b/src/components/common/VnCardMain.vue @@ -12,9 +12,11 @@ defineProps({ </script> <template> <slot name="searchbar" /> + {{ stateStore.isHeaderMounted() }} <Teleport to="#left-panel" v-if="stateStore.isHeaderMounted()"> <LeftMenu v-if="section == $route.name" /> </Teleport> <slot name="body" v-if="section == $route.name" /> <RouterView v-else /> + <slot name="rightPanel" /> </template> diff --git a/src/components/ui/VnFilterPanel.vue b/src/components/ui/VnFilterPanel.vue index 7319dc866cd..b59df89904d 100644 --- a/src/components/ui/VnFilterPanel.vue +++ b/src/components/ui/VnFilterPanel.vue @@ -1,10 +1,10 @@ <script setup> -import { onMounted, ref, computed, watch } from 'vue'; +import { ref, computed } from 'vue'; import { useI18n } from 'vue-i18n'; import { useArrayData } from 'composables/useArrayData'; -import { useRoute } from 'vue-router'; import toDate from 'filters/toDate'; import VnFilterPanelChip from 'components/ui/VnFilterPanelChip.vue'; +import { useFilterParams } from 'src/composables/useFilterParams'; const { t } = useI18n(); const $props = defineProps({ @@ -55,6 +55,10 @@ const $props = defineProps({ type: Boolean, default: true, }, + arrayData: { + type: Object, + default: null, + }, }); const emit = defineEmits([ @@ -67,52 +71,19 @@ const emit = defineEmits([ 'setUserParams', ]); -const arrayData = useArrayData($props.dataKey, { - exprBuilder: $props.exprBuilder, - searchUrl: $props.searchUrl, - navigate: $props.redirect ? {} : null, -}); -const route = useRoute(); +const arrayData = + $props.arrayData ?? + useArrayData($props.dataKey, { + exprBuilder: $props.exprBuilder, + searchUrl: $props.searchUrl, + navigate: $props.redirect ? {} : null, + }); + const store = arrayData.store; -const userParams = ref({}); +const userParams = ref(useFilterParams($props.dataKey).params); +const userOrders = ref(useFilterParams($props.dataKey).orders); -defineExpose({ search, sanitizer, params: userParams }); - -onMounted(() => { - if (!userParams.value) userParams.value = $props.modelValue ?? {}; - emit('init', { params: userParams.value }); -}); - -function setUserParams(watchedParams) { - if (!watchedParams || Object.keys(watchedParams).length == 0) return; - - if (typeof watchedParams == 'string') watchedParams = JSON.parse(watchedParams); - if (typeof watchedParams?.filter == 'string') - watchedParams.filter = JSON.parse(watchedParams.filter); - - watchedParams = { ...watchedParams, ...watchedParams.filter?.where }; - const order = watchedParams.filter?.order; - - delete watchedParams.filter; - userParams.value = sanitizer(watchedParams); - emit('setUserParams', userParams.value, order); -} - -watch( - () => route.query[$props.searchUrl], - (val, oldValue) => (val || oldValue) && setUserParams(val) -); - -watch( - () => arrayData.store.userParams, - (val, oldValue) => (val || oldValue) && setUserParams(val), - { immediate: true } -); - -watch( - () => $props.modelValue, - (val) => (userParams.value = val ?? {}) -); +defineExpose({ search, params: userParams, remove }); const isLoading = ref(false); async function search(evt) { @@ -123,10 +94,9 @@ async function search(evt) { isLoading.value = true; const filter = { ...userParams.value, ...$props.modelValue }; store.userParamsChanged = true; - const { params: newParams } = await arrayData.addFilter({ + await arrayData.addFilter({ params: filter, }); - userParams.value = newParams; if (!$props.showAll && !Object.values(filter).length) store.data = []; emit('search'); @@ -149,9 +119,8 @@ async function clearFilters() { for (const key of removableFilters) { newParams[key] = userParams.value[key]; } - userParams.value = {}; - userParams.value = { ...newParams }; // Actualizar los params con los removibles - await arrayData.applyFilter({ params: userParams.value }); + + await arrayData.applyFilter({ params: { ...newParams } }); if (!$props.showAll) { store.data = []; @@ -213,21 +182,6 @@ function formatValue(value) { return `"${value}"`; } - -function sanitizer(params) { - for (const [key, value] of Object.entries(params)) { - if (key === 'and' && Array.isArray(value)) { - value.forEach((item) => { - Object.assign(params, item); - }); - delete params[key]; - } else if (value && typeof value === 'object') { - const param = Object.values(value)[0]; - if (typeof param == 'string') params[key] = param.replaceAll('%', ''); - } - } - return params; -} </script> <template> @@ -296,7 +250,12 @@ function sanitizer(params) { <QSeparator /> </QList> <QList dense class="list q-gutter-y-sm q-mt-sm"> - <slot name="body" :params="sanitizer(userParams)" :search-fn="search"></slot> + <slot + name="body" + :params="userParams" + :orders="userOrders" + :search-fn="search" + ></slot> </QList> </QForm> <QInnerLoading diff --git a/src/components/ui/VnPaginate.vue b/src/components/ui/VnPaginate.vue index c5fbbb7314b..13361bd0639 100644 --- a/src/components/ui/VnPaginate.vue +++ b/src/components/ui/VnPaginate.vue @@ -106,6 +106,7 @@ onMounted(async () => { onBeforeUnmount(() => { arrayData.resetPagination(); + arrayData.reset(['currentFilter', 'userParams', 'userFilter']); }); watch( @@ -197,7 +198,14 @@ async function onLoad(index, done) { done(isDone); } -defineExpose({ fetch, update, addFilter, paginate }); +defineExpose({ + fetch, + update, + addFilter, + paginate, + userParams: arrayData.store.userParams, + currentFilter: arrayData.store.currentFilter, +}); </script> <template> diff --git a/src/composables/useArrayData.js b/src/composables/useArrayData.js index c36eb99900c..c0c744852ee 100644 --- a/src/composables/useArrayData.js +++ b/src/composables/useArrayData.js @@ -25,11 +25,14 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) { const searchUrl = store.searchUrl; if (query[searchUrl]) { const params = JSON.parse(query[searchUrl]); - const filter = params?.filter && JSON.parse(params?.filter ?? '{}'); + const filter = + params?.filter && typeof params?.filter == 'object' + ? params?.filter + : JSON.parse(params?.filter ?? '{}'); delete params.filter; store.userParams = { ...store.userParams, ...params }; - store.userFilter = { ...filter, ...store.userFilter }; + store.filter = { ...filter, ...store.userFilter }; if (filter?.order) store.order = filter.order; } }); @@ -74,7 +77,6 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) { const filter = { limit: store.limit, }; - let userParams = { ...store.userParams }; Object.assign(filter, store.userFilter); diff --git a/src/composables/useFilterParams.js b/src/composables/useFilterParams.js new file mode 100644 index 00000000000..2878e4b76ab --- /dev/null +++ b/src/composables/useFilterParams.js @@ -0,0 +1,65 @@ +import { useArrayData } from 'src/composables/useArrayData'; +import { onBeforeMount, ref, watch } from 'vue'; + +export function useFilterParams(key) { + if (!key) throw new Error('ArrayData: A key is required to use this composable'); + const params = ref({}); + const orders = ref({}); + const arrayData = ref({}); + + onBeforeMount(() => { + arrayData.value = useArrayData(key); + }); + + watch( + () => arrayData.value.store?.currentFilter, + (val, oldValue) => (val || oldValue) && setUserParams(val), + { immediate: true, deep: true } + ); + + function parseOrder(urlOrders) { + const orderObject = {}; + if (urlOrders) { + if (typeof urlOrders == 'string') urlOrders = [urlOrders]; + for (const [index, orders] of urlOrders.entries()) { + const [name, direction] = orders.split(' '); + orderObject[name] = { direction, index: index + 1 }; + } + } + orders.value = orderObject; + } + + function setUserParams(watchedParams) { + if (!watchedParams || Object.keys(watchedParams).length == 0) return; + + if (typeof watchedParams == 'string') watchedParams = JSON.parse(watchedParams); + if (typeof watchedParams?.filter == 'string') + watchedParams.filter = JSON.parse(watchedParams.filter); + + watchedParams = { ...watchedParams, ...watchedParams.filter?.where }; + parseOrder(watchedParams.filter?.order); + + delete watchedParams.filter; + params.value = sanitizer(watchedParams); + } + + function sanitizer(params) { + for (const [key, value] of Object.entries(params)) { + if (key === 'and' && Array.isArray(value)) { + value.forEach((item) => { + Object.assign(params, item); + }); + delete params[key]; + } else if (value && typeof value === 'object') { + const param = Object.values(value)[0]; + if (typeof param == 'string') params[key] = param.replaceAll('%', ''); + } + } + return params; + } + + return { + params, + orders, + }; +} diff --git a/src/pages/Account/AccountList.vue b/src/pages/Account/AccountList.vue index 4b8e8fb2836..a0e2a3842ab 100644 --- a/src/pages/Account/AccountList.vue +++ b/src/pages/Account/AccountList.vue @@ -1,11 +1,13 @@ <script setup> import { useI18n } from 'vue-i18n'; -import { ref, computed } from 'vue'; +import { ref, computed, onBeforeMount } from 'vue'; import VnTable from 'components/VnTable/VnTable.vue'; import VnSearchbar from 'components/ui/VnSearchbar.vue'; import AccountSummary from './Card/AccountSummary.vue'; import { useSummaryDialog } from 'src/composables/useSummaryDialog'; import VnCardMain from 'src/components/common/VnCardMain.vue'; +import VnTableFilter from 'src/components/VnTable/VnTableFilter.vue'; +import { useArrayData } from 'src/composables/useArrayData'; const { t } = useI18n(); const { viewSummary } = useSummaryDialog(); @@ -84,7 +86,17 @@ const columns = computed(() => [ ], }, ]); -const exprBuilder = (param, value) => { + +onBeforeMount(() => { + useArrayData(dataKey, { + url, + userFilter: filter, + order: 'id DESC', + exprBuilder, + searchUrl: 'table', + }); +}); +function exprBuilder(param, value) { switch (param) { case 'search': return /^\d+$/.test(value) @@ -101,7 +113,7 @@ const exprBuilder = (param, value) => { case 'roleFk': return { [param]: value }; } -}; +} </script> <template> @@ -109,28 +121,24 @@ const exprBuilder = (param, value) => { <template #searchbar> <VnSearchbar :data-key="dataKey" - :expr-builder="exprBuilder" :label="t('account.search')" :info="t('account.searchInfo')" - :filter="filter" - :url="url" /> </template> <template #body> <VnTable ref="tableRef" :data-key="dataKey" - :url="url" - :filter="filter" - order="id DESC" :columns="columns" default-mode="table" redirect="account" :use-model="true" - :right-search="true" - :expr-builder="exprBuilder" + :right-search="false" /> </template> + <template #rightPanel> + <VnTableFilter :data-key="dataKey" :columns="columns" /> + </template> </VnCardMain> </template> diff --git a/src/utils/getUserParams.js b/src/utils/getUserParams.js new file mode 100644 index 00000000000..e69de29bb2d From 2ae0d90e32738bdfc816b3a6a5e64ca94a45e658 Mon Sep 17 00:00:00 2001 From: alexm <alexm@verdnatura.es> Date: Mon, 9 Dec 2024 14:15:33 +0100 Subject: [PATCH 06/34] chore: refs #8197 replace name --- src/components/common/VnCardMain.vue | 1 - src/router/modules/index.js | 2 +- src/router/modules/{Supplier.js => supplier.js} | 0 src/router/routes.js | 2 +- 4 files changed, 2 insertions(+), 3 deletions(-) rename src/router/modules/{Supplier.js => supplier.js} (100%) diff --git a/src/components/common/VnCardMain.vue b/src/components/common/VnCardMain.vue index 3ebfcfb8bdc..7a56aa5cf16 100644 --- a/src/components/common/VnCardMain.vue +++ b/src/components/common/VnCardMain.vue @@ -12,7 +12,6 @@ defineProps({ </script> <template> <slot name="searchbar" /> - {{ stateStore.isHeaderMounted() }} <Teleport to="#left-panel" v-if="stateStore.isHeaderMounted()"> <LeftMenu v-if="section == $route.name" /> </Teleport> diff --git a/src/router/modules/index.js b/src/router/modules/index.js index fb1bdc46667..77076d04a20 100644 --- a/src/router/modules/index.js +++ b/src/router/modules/index.js @@ -8,7 +8,7 @@ import Worker from './worker'; import Shelving from './shelving'; import Wagon from './wagon'; import Route from './route'; -import Supplier from './Supplier'; +import Supplier from './supplier'; import Travel from './travel'; import Order from './order'; import Department from './department'; diff --git a/src/router/modules/Supplier.js b/src/router/modules/supplier.js similarity index 100% rename from src/router/modules/Supplier.js rename to src/router/modules/supplier.js diff --git a/src/router/routes.js b/src/router/routes.js index d332be94194..131021c9a23 100644 --- a/src/router/routes.js +++ b/src/router/routes.js @@ -7,7 +7,7 @@ import worker from './modules/worker'; import invoiceOut from './modules/invoiceOut'; import invoiceIn from './modules/invoiceIn'; import wagon from './modules/wagon'; -import supplier from './modules/Supplier'; +import supplier from './modules/supplier'; import travel from './modules/travel'; import department from './modules/department'; import ItemType from './modules/itemType'; From 3984327b51272ef78f9f64282aebdd8fec250286 Mon Sep 17 00:00:00 2001 From: alexm <alexm@verdnatura.es> Date: Thu, 12 Dec 2024 11:03:28 +0100 Subject: [PATCH 07/34] feat: refs #8197 working rightMenu --- src/components/VnTable/VnTableFilter.vue | 85 ++++++++++-------------- src/components/common/VnCardMain.vue | 7 +- src/pages/Account/AccountList.vue | 2 +- src/pages/Account/Role/AccountRoles.vue | 1 + 4 files changed, 44 insertions(+), 51 deletions(-) diff --git a/src/components/VnTable/VnTableFilter.vue b/src/components/VnTable/VnTableFilter.vue index 2d1758786e7..f23c657cf12 100644 --- a/src/components/VnTable/VnTableFilter.vue +++ b/src/components/VnTable/VnTableFilter.vue @@ -1,7 +1,6 @@ <script setup> import { ref } from 'vue'; import { useI18n } from 'vue-i18n'; -import { useStateStore } from 'stores/useStateStore'; import VnFilterPanel from 'components/ui/VnFilterPanel.vue'; import VnFilter from 'components/VnTable/VnFilter.vue'; @@ -22,7 +21,6 @@ defineProps({ }, }); const { t } = useI18n(); -const stateStore = useStateStore(); const tableFilterRef = ref([]); @@ -34,52 +32,41 @@ function columnName(col) { } </script> <template> - <QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above> - <QScrollArea class="fit"> - <VnFilterPanel - v-bind="$attrs" - :search-button="true" - :disable-submit-event="true" + <VnFilterPanel v-bind="$attrs" :search-button="true" :disable-submit-event="true"> + <template #body="{ params, orders }"> + <div + class="row no-wrap flex-center" + v-for="col of columns.filter((c) => c.columnFilter ?? true)" + :key="col.id" > - <template #body="{ params, orders }"> - <div - class="row no-wrap flex-center" - v-for="col of columns.filter((c) => c.columnFilter ?? true)" - :key="col.id" - > - <VnFilter - ref="tableFilterRef" - :column="col" - :data-key="$attrs['data-key']" - v-model="params[columnName(col)]" - :search-url="searchUrl" - /> - <VnTableOrder - v-if=" - col?.columnFilter !== false && - col?.name !== 'tableActions' - " - v-model="orders[col.orderBy ?? col.name]" - :name="col.orderBy ?? col.name" - :data-key="$attrs['data-key']" - :search-url="searchUrl" - :vertical="true" - /> - </div> - <slot - name="moreFilterPanel" - :params="params" - :orders="orders" - :columns="columns" - /> - </template> - <template #tags="{ tag, formatFn }" v-if="chipLocale"> - <div class="q-gutter-x-xs"> - <strong>{{ t(`${chipLocale}.${tag.label}`) }}: </strong> - <span>{{ formatFn(tag.value) }}</span> - </div> - </template> - </VnFilterPanel> - </QScrollArea> - </QDrawer> + <VnFilter + ref="tableFilterRef" + :column="col" + :data-key="$attrs['data-key']" + v-model="params[columnName(col)]" + :search-url="searchUrl" + /> + <VnTableOrder + v-if="col?.columnFilter !== false && col?.name !== 'tableActions'" + v-model="orders[col.orderBy ?? col.name]" + :name="col.orderBy ?? col.name" + :data-key="$attrs['data-key']" + :search-url="searchUrl" + :vertical="true" + /> + </div> + <slot + name="moreFilterPanel" + :params="params" + :orders="orders" + :columns="columns" + /> + </template> + <template #tags="{ tag, formatFn }" v-if="chipLocale"> + <div class="q-gutter-x-xs"> + <strong>{{ t(`${chipLocale}.${tag.label}`) }}: </strong> + <span>{{ formatFn(tag.value) }}</span> + </div> + </template> + </VnFilterPanel> </template> diff --git a/src/components/common/VnCardMain.vue b/src/components/common/VnCardMain.vue index 7a56aa5cf16..ab664917ae7 100644 --- a/src/components/common/VnCardMain.vue +++ b/src/components/common/VnCardMain.vue @@ -1,6 +1,7 @@ <script setup> import LeftMenu from '../LeftMenu.vue'; import { useStateStore } from 'stores/useStateStore'; +import RightMenu from './RightMenu.vue'; const stateStore = useStateStore(); defineProps({ @@ -17,5 +18,9 @@ defineProps({ </Teleport> <slot name="body" v-if="section == $route.name" /> <RouterView v-else /> - <slot name="rightPanel" /> + <RightMenu> + <template #right-panel v-if="$slots['rightMenu']"> + <slot name="rightMenu" /> + </template> + </RightMenu> </template> diff --git a/src/pages/Account/AccountList.vue b/src/pages/Account/AccountList.vue index a0e2a3842ab..ed2030d2964 100644 --- a/src/pages/Account/AccountList.vue +++ b/src/pages/Account/AccountList.vue @@ -136,7 +136,7 @@ function exprBuilder(param, value) { :right-search="false" /> </template> - <template #rightPanel> + <template #rightMenu> <VnTableFilter :data-key="dataKey" :columns="columns" /> </template> </VnCardMain> diff --git a/src/pages/Account/Role/AccountRoles.vue b/src/pages/Account/Role/AccountRoles.vue index 74c4ab8a558..9aebef64c5c 100644 --- a/src/pages/Account/Role/AccountRoles.vue +++ b/src/pages/Account/Role/AccountRoles.vue @@ -114,6 +114,7 @@ const exprBuilder = (param, value) => { :columns="columns" default-mode="table" redirect="account/role" + :right-search="false" /> </template> </VnCardMain> From 5d744ca456e39708124e78c866d33efe14d71f06 Mon Sep 17 00:00:00 2001 From: alexm <alexm@verdnatura.es> Date: Mon, 16 Dec 2024 09:41:50 +0100 Subject: [PATCH 08/34] feat: refs #8197 better leftMenu and VnCardMain improvements --- src/components/LeftMenu.vue | 25 ++++----- src/components/common/VnBreadcrumbs.vue | 2 +- src/components/common/VnCardMain.vue | 65 ++++++++++++++++++++--- src/pages/Account/AccountList.vue | 40 +++++--------- src/pages/Account/Role/AccountRoles.vue | 20 +++---- src/pages/Account/locale/en.yml | 2 +- src/router/modules/account.js | 10 ++-- src/router/modules/account/accountCard.js | 10 ++++ src/router/modules/account/roleCard.js | 3 ++ src/utils/getSections.js | 8 --- 10 files changed, 108 insertions(+), 77 deletions(-) delete mode 100644 src/utils/getSections.js diff --git a/src/components/LeftMenu.vue b/src/components/LeftMenu.vue index ab2931dfd29..eed2e192bed 100644 --- a/src/components/LeftMenu.vue +++ b/src/components/LeftMenu.vue @@ -92,13 +92,11 @@ function findMatches(search, item) { } function addChildren(module, route, parent) { - if (route.menus) { - const mainMenus = route.menus[props.source]; - const matches = findMatches(mainMenus, route); + if (!route?.meta?.menu) return; + const matches = findMatches(route.meta.menu, route); - for (const child of matches) { - navigation.addMenuItem(module, child, parent); - } + for (const child of matches) { + navigation.addMenuItem(module, child, parent); } } @@ -120,15 +118,14 @@ function getRoutes() { } if (props.source === 'card') { - const currentRoute = route.matched[1]; - const currentModule = toLowerCamel(currentRoute.name); - const moduleDef = routes.find( - (route) => toLowerCamel(route.name) === currentModule - ); + let menuRoute; + let index = route.matched.length - 1; - if (!moduleDef) return; - - addChildren(currentModule, moduleDef, items.value); + while (!menuRoute && index > 0) { + if (route.matched[index]?.meta?.menu) menuRoute = route.matched[index]; + index--; + } + addChildren('', menuRoute, items.value); } } diff --git a/src/components/common/VnBreadcrumbs.vue b/src/components/common/VnBreadcrumbs.vue index 02226e4975a..334ab4d2115 100644 --- a/src/components/common/VnBreadcrumbs.vue +++ b/src/components/common/VnBreadcrumbs.vue @@ -15,7 +15,7 @@ let root = ref(null); watchEffect(() => { matched.value = currentRoute.value.matched.filter( - (matched) => Object.keys(matched.meta).length + (matched) => !!matched?.meta?.title || !!matched?.meta?.icon ); breadcrumbs.value.length = 0; if (!matched.value[0]) return; diff --git a/src/components/common/VnCardMain.vue b/src/components/common/VnCardMain.vue index ab664917ae7..b222748b9f7 100644 --- a/src/components/common/VnCardMain.vue +++ b/src/components/common/VnCardMain.vue @@ -2,25 +2,78 @@ import LeftMenu from '../LeftMenu.vue'; import { useStateStore } from 'stores/useStateStore'; import RightMenu from './RightMenu.vue'; +import VnSearchbar from 'components/ui/VnSearchbar.vue'; +import VnTableFilter from '../VnTable/VnTableFilter.vue'; +import { onBeforeMount } from 'vue'; +import { useArrayData } from 'src/composables/useArrayData'; const stateStore = useStateStore(); -defineProps({ +const $props = defineProps({ section: { type: String, required: true, }, + dataKey: { + type: String, + default: null, + }, + searchBar: { + type: Boolean, + default: true, + }, + prefix: { + type: String, + default: null, + }, + rightFilter: { + type: Boolean, + default: true, + }, + columns: { + type: Array, + default: null, + }, + arrayDataProps: { + type: Object, + default: null, + }, +}); + +onBeforeMount(() => { + if ($props.dataKey) + useArrayData($props.dataKey, { + searchUrl: 'table', + ...$props.arrayDataProps, + }); }); </script> <template> - <slot name="searchbar" /> + <slot name="searchbar"> + <VnSearchbar + v-if="searchBar" + v-bind="arrayDataProps" + :data-key="dataKey" + :label="$t(`${prefix}.search`)" + :info="$t(`${prefix}.searchInfo`)" + /> + </slot> + <Teleport to="#left-panel" v-if="stateStore.isHeaderMounted()"> <LeftMenu v-if="section == $route.name" /> </Teleport> - <slot name="body" v-if="section == $route.name" /> - <RouterView v-else /> + <RightMenu> - <template #right-panel v-if="$slots['rightMenu']"> - <slot name="rightMenu" /> + <template #right-panel v-if="$slots['rightMenu'] || rightFilter"> + <slot name="rightMenu"> + <VnTableFilter + v-if="rightFilter && columns" + :data-key="dataKey" + :columns="columns" + /> + </slot> </template> </RightMenu> + + <slot name="body" v-if="section == $route.name" /> + <RouterView v-else /> </template> diff --git a/src/pages/Account/AccountList.vue b/src/pages/Account/AccountList.vue index ed2030d2964..34a653e61db 100644 --- a/src/pages/Account/AccountList.vue +++ b/src/pages/Account/AccountList.vue @@ -1,22 +1,17 @@ <script setup> import { useI18n } from 'vue-i18n'; -import { ref, computed, onBeforeMount } from 'vue'; +import { computed } from 'vue'; import VnTable from 'components/VnTable/VnTable.vue'; -import VnSearchbar from 'components/ui/VnSearchbar.vue'; import AccountSummary from './Card/AccountSummary.vue'; import { useSummaryDialog } from 'src/composables/useSummaryDialog'; import VnCardMain from 'src/components/common/VnCardMain.vue'; -import VnTableFilter from 'src/components/VnTable/VnTableFilter.vue'; -import { useArrayData } from 'src/composables/useArrayData'; const { t } = useI18n(); const { viewSummary } = useSummaryDialog(); -const tableRef = ref(); const filter = { include: { relation: 'role', scope: { fields: ['id', 'name'] } }, }; const dataKey = 'AccountList'; -const url = 'VnUsers/preview'; const columns = computed(() => [ { align: 'left', @@ -87,15 +82,6 @@ const columns = computed(() => [ }, ]); -onBeforeMount(() => { - useArrayData(dataKey, { - url, - userFilter: filter, - order: 'id DESC', - exprBuilder, - searchUrl: 'table', - }); -}); function exprBuilder(param, value) { switch (param) { case 'search': @@ -117,17 +103,20 @@ function exprBuilder(param, value) { </script> <template> - <VnCardMain :section="dataKey"> - <template #searchbar> - <VnSearchbar - :data-key="dataKey" - :label="t('account.search')" - :info="t('account.searchInfo')" - /> - </template> + <VnCardMain + :section="dataKey" + :data-key="dataKey" + :columns="columns" + prefix="account" + :array-data-props="{ + url: 'VnUsers/preview', + userFilter: filter, + order: 'id DESC', + exprBuilder, + }" + > <template #body> <VnTable - ref="tableRef" :data-key="dataKey" :columns="columns" default-mode="table" @@ -136,9 +125,6 @@ function exprBuilder(param, value) { :right-search="false" /> </template> - <template #rightMenu> - <VnTableFilter :data-key="dataKey" :columns="columns" /> - </template> </VnCardMain> </template> diff --git a/src/pages/Account/Role/AccountRoles.vue b/src/pages/Account/Role/AccountRoles.vue index 9aebef64c5c..8cc392f1bb8 100644 --- a/src/pages/Account/Role/AccountRoles.vue +++ b/src/pages/Account/Role/AccountRoles.vue @@ -3,7 +3,6 @@ import { useI18n } from 'vue-i18n'; import { computed, ref } from 'vue'; import VnTable from 'components/VnTable/VnTable.vue'; import { useRoute } from 'vue-router'; -import VnSearchbar from 'components/ui/VnSearchbar.vue'; import { useSummaryDialog } from 'src/composables/useSummaryDialog'; import RoleSummary from './Card/RoleSummary.vue'; import VnCardMain from 'src/components/common/VnCardMain.vue'; @@ -86,21 +85,17 @@ const exprBuilder = (param, value) => { </script> <template> - <VnCardMain :section="dataKey"> - <template #searchbar> - <VnSearchbar - :url="url" - :data-key="dataKey" - :expr-builder="exprBuilder" - :label="t('role.searchRoles')" - :info="t('role.searchInfo')" - /> - </template> + <VnCardMain + :section="dataKey" + :data-key="dataKey" + :columns="columns" + prefix="role" + :array-data-props="{ url, exprBuilder, order: 'id ASC' }" + > <template #body> <VnTable ref="tableRef" :data-key="dataKey" - :url="url" :create="{ urlCreate: 'VnRoles', title: t('Create rol'), @@ -109,7 +104,6 @@ const exprBuilder = (param, value) => { editorFk: entityId, }, }" - order="id ASC" :disable-option="{ card: true }" :columns="columns" default-mode="table" diff --git a/src/pages/Account/locale/en.yml b/src/pages/Account/locale/en.yml index f2f563923c3..88a6b11e990 100644 --- a/src/pages/Account/locale/en.yml +++ b/src/pages/Account/locale/en.yml @@ -66,7 +66,7 @@ account: mailInputInfo: All emails will be forwarded to the specified address. role: newRole: New role - searchRoles: Search role + search: Search role searchInfo: Search role by id or name description: Description id: Id diff --git a/src/router/modules/account.js b/src/router/modules/account.js index ece0ab2bb89..2ee7c915d9d 100644 --- a/src/router/modules/account.js +++ b/src/router/modules/account.js @@ -1,7 +1,6 @@ import { RouterView } from 'vue-router'; import accountCard from './account/accountCard'; import roleCard from './account/roleCard'; -import getSections from 'src/utils/getSections'; export default { path: '/account', @@ -11,11 +10,7 @@ export default { icon: 'face', moduleName: 'Account', keyBinding: 'u', - }, - component: RouterView, - redirect: { name: 'AccountMain' }, - menus: { - main: [ + menu: [ 'AccountList', 'AccountAliasList', 'AccountRoles', @@ -25,8 +20,9 @@ export default { 'AccountAcls', 'AccountConnections', ], - card: getSections(accountCard.children), }, + component: RouterView, + redirect: { name: 'AccountMain' }, children: [ { path: '', diff --git a/src/router/modules/account/accountCard.js b/src/router/modules/account/accountCard.js index 0d8850f10aa..3ba687adfda 100644 --- a/src/router/modules/account/accountCard.js +++ b/src/router/modules/account/accountCard.js @@ -3,6 +3,16 @@ export default { path: ':id', redirect: { name: 'AccountSummary' }, component: () => import('src/pages/Account/Card/AccountCard.vue'), + meta: { + menu: [ + 'AccountBasicData', + 'AccountInheritedRoles', + 'AccountMailForwarding', + 'AccountMailAlias', + 'AccountPrivileges', + 'AccountLog', + ], + }, children: [ { name: 'AccountSummary', diff --git a/src/router/modules/account/roleCard.js b/src/router/modules/account/roleCard.js index 2a538756873..c36ce71b9da 100644 --- a/src/router/modules/account/roleCard.js +++ b/src/router/modules/account/roleCard.js @@ -3,6 +3,9 @@ export default { path: ':id', component: () => import('src/pages/Account/Role/Card/RoleCard.vue'), redirect: { name: 'RoleSummary' }, + meta: { + menu: ['RoleBasicData', 'SubRoles', 'InheritedRoles', 'RoleLog'], + }, children: [ { name: 'RoleSummary', diff --git a/src/utils/getSections.js b/src/utils/getSections.js deleted file mode 100644 index f70daf4685c..00000000000 --- a/src/utils/getSections.js +++ /dev/null @@ -1,8 +0,0 @@ -export default (sections) => { - const names = []; - for (const section of sections) { - if (section.path == 'summary') continue; - names.push(section.name); - } - return names; -}; From 18fd41b82bf8ddf8317aad06f912115985ed9531 Mon Sep 17 00:00:00 2001 From: alexm <alexm@verdnatura.es> Date: Mon, 16 Dec 2024 09:47:46 +0100 Subject: [PATCH 09/34] refactor: refs #8197 adapt AccountAlias --- src/pages/Account/AccountAliasList.vue | 77 ++++++++++++------------- src/router/modules/account.js | 15 ++++- src/router/modules/account/aliasCard.js | 36 ++++++++++++ src/router/modules/index.js | 2 - src/router/modules/mailAlias.js | 57 ------------------ src/router/routes.js | 2 - 6 files changed, 85 insertions(+), 104 deletions(-) create mode 100644 src/router/modules/account/aliasCard.js delete mode 100644 src/router/modules/mailAlias.js diff --git a/src/pages/Account/AccountAliasList.vue b/src/pages/Account/AccountAliasList.vue index c6728329747..721a009e512 100644 --- a/src/pages/Account/AccountAliasList.vue +++ b/src/pages/Account/AccountAliasList.vue @@ -2,21 +2,12 @@ import { useI18n } from 'vue-i18n'; import { ref, computed } from 'vue'; import VnTable from 'components/VnTable/VnTable.vue'; -import VnSearchbar from 'components/ui/VnSearchbar.vue'; -import { useStateStore } from 'stores/useStateStore'; +import VnCardMain from 'src/components/common/VnCardMain.vue'; const tableRef = ref(); const { t } = useI18n(); -const stateStore = useStateStore(); +const dataKey = 'AccountAliasList'; -const exprBuilder = (param, value) => { - switch (param) { - case 'search': - return /^\d+$/.test(value) - ? { id: value } - : { alias: { like: `%${value}%` } }; - } -}; const columns = computed(() => [ { align: 'left', @@ -40,40 +31,46 @@ const columns = computed(() => [ create: true, }, ]); + +const exprBuilder = (param, value) => { + switch (param) { + case 'search': + return /^\d+$/.test(value) + ? { id: value } + : { alias: { like: `%${value}%` } }; + } +}; </script> <template> - <template v-if="stateStore.isHeaderMounted()"> - <Teleport to="#searchbar"> - <VnSearchbar - data-key="AccountAliasList" - url="MailAliases" - :expr-builder="exprBuilder" - :label="t('mailAlias.search')" - :info="t('mailAlias.searchInfo')" - /> - </Teleport> - </template> - <VnTable - ref="tableRef" - data-key="AccountAliasList" - url="MailAliases" - :create="{ - urlCreate: 'MailAliases', - title: 'Create MailAlias', - onDataSaved: ({ id }) => tableRef.redirect(id), - formInitialData: {}, - }" - order="id DESC" + <VnCardMain + :section="dataKey" + :data-key="dataKey" :columns="columns" - :disable-option="{ card: true }" - default-mode="table" - redirect="account/alias" - :is-editable="true" - :use-model="true" - /> + prefix="mailAlias" + :array-data-props="{ url: 'MailAliases', order: 'id DESC', exprBuilder }" + > + <template #body> + <VnTable + :data-key="dataKey" + ref="tableRef" + :create="{ + urlCreate: 'MailAliases', + title: 'Create MailAlias', + onDataSaved: ({ id }) => tableRef.redirect(id), + formInitialData: {}, + }" + :columns="columns" + :disable-option="{ card: true }" + default-mode="table" + redirect="account/alias" + :is-editable="true" + :use-model="true" + :right-search="false" + /> + </template> + </VnCardMain> </template> - <i18n> es: Id: Id diff --git a/src/router/modules/account.js b/src/router/modules/account.js index 2ee7c915d9d..6f5ca90f3cf 100644 --- a/src/router/modules/account.js +++ b/src/router/modules/account.js @@ -1,6 +1,7 @@ import { RouterView } from 'vue-router'; import accountCard from './account/accountCard'; import roleCard from './account/roleCard'; +import aliasCard from './account/aliasCard'; export default { path: '/account', @@ -12,8 +13,8 @@ export default { keyBinding: 'u', menu: [ 'AccountList', - 'AccountAliasList', 'AccountRoles', + 'AccountAlias', 'AccountAccounts', 'AccountLdap', 'AccountSamba', @@ -65,13 +66,21 @@ export default { ], }, { - path: 'alias-list', - name: 'AccountAliasList', + path: 'alias', + name: 'AccountAlias', + redirect: { name: 'AccountAliasList' }, meta: { title: 'alias', icon: 'email', }, component: () => import('src/pages/Account/AccountAliasList.vue'), + children: [ + { + name: 'AccountAliasList', + path: 'list', + }, + aliasCard, + ], }, { path: 'acls', diff --git a/src/router/modules/account/aliasCard.js b/src/router/modules/account/aliasCard.js new file mode 100644 index 00000000000..cbbd31e5103 --- /dev/null +++ b/src/router/modules/account/aliasCard.js @@ -0,0 +1,36 @@ +export default { + name: 'AliasCard', + path: ':id', + component: () => import('src/pages/Account/Alias/Card/AliasCard.vue'), + redirect: { name: 'AliasSummary' }, + meta: { menu: ['AliasBasicData', 'AliasUsers'] }, + children: [ + { + name: 'AliasSummary', + path: 'summary', + meta: { + title: 'summary', + icon: 'launch', + }, + component: () => import('src/pages/Account/Alias/Card/AliasSummary.vue'), + }, + { + name: 'AliasBasicData', + path: 'basic-data', + meta: { + title: 'basicData', + icon: 'vn:settings', + }, + component: () => import('src/pages/Account/Alias/Card/AliasBasicData.vue'), + }, + { + name: 'AliasUsers', + path: 'users', + meta: { + title: 'aliasUsers', + icon: 'group', + }, + component: () => import('src/pages/Account/Alias/Card/AliasUsers.vue'), + }, + ], +}; diff --git a/src/router/modules/index.js b/src/router/modules/index.js index 77076d04a20..f28fed1c2c3 100644 --- a/src/router/modules/index.js +++ b/src/router/modules/index.js @@ -20,7 +20,6 @@ import ItemType from './itemType'; import Zone from './zone'; import Account from './account'; import Monitor from './monitor'; -import MailAlias from './mailAlias'; export default [ Item, @@ -44,6 +43,5 @@ export default [ ItemType, Zone, Account, - MailAlias, Monitor, ]; diff --git a/src/router/modules/mailAlias.js b/src/router/modules/mailAlias.js deleted file mode 100644 index 8e0f8abdcb9..00000000000 --- a/src/router/modules/mailAlias.js +++ /dev/null @@ -1,57 +0,0 @@ -import { RouterView } from 'vue-router'; - -export default { - path: 'account/alias', - name: 'Alias', - meta: { - title: 'alias', - icon: 'email', - moduleName: 'Alias', - }, - component: RouterView, - redirect: { name: 'AccountAliasList' }, - menus: { - main: [], - card: ['AliasBasicData', 'AliasUsers'], - }, - children: [ - { - name: 'AliasCard', - path: ':id', - component: () => import('src/pages/Account/Alias/Card/AliasCard.vue'), - redirect: { name: 'AliasSummary' }, - children: [ - { - name: 'AliasSummary', - path: 'summary', - meta: { - title: 'summary', - icon: 'launch', - }, - component: () => - import('src/pages/Account/Alias/Card/AliasSummary.vue'), - }, - { - name: 'AliasBasicData', - path: 'basic-data', - meta: { - title: 'basicData', - icon: 'vn:settings', - }, - component: () => - import('src/pages/Account/Alias/Card/AliasBasicData.vue'), - }, - { - name: 'AliasUsers', - path: 'users', - meta: { - title: 'aliasUsers', - icon: 'group', - }, - component: () => - import('src/pages/Account/Alias/Card/AliasUsers.vue'), - }, - ], - }, - ], -}; diff --git a/src/router/routes.js b/src/router/routes.js index 131021c9a23..b9120f8c414 100644 --- a/src/router/routes.js +++ b/src/router/routes.js @@ -20,7 +20,6 @@ import agency from 'src/router/modules/agency'; import zone from 'src/router/modules/zone'; import account from './modules/account'; import monitor from 'src/router/modules/monitor'; -import mailAlias from './modules/mailAlias'; const routes = [ { @@ -94,7 +93,6 @@ const routes = [ ItemType, zone, account, - mailAlias, { path: '/:catchAll(.*)*', name: 'NotFound', From 1e76d5fd3fb45bba11060ea9ab0898e2fc4fa79d Mon Sep 17 00:00:00 2001 From: Jon <jon@verdnatura.es> Date: Mon, 16 Dec 2024 12:03:57 +0100 Subject: [PATCH 10/34] refactor: ignore params when searching by id on searchbar --- src/components/ui/VnSearchbar.vue | 19 +++++++++++++------ src/pages/Order/Card/OrderCatalog.vue | 3 ++- src/pages/Zone/Card/ZoneLocationsTree.vue | 8 +++++++- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/components/ui/VnSearchbar.vue b/src/components/ui/VnSearchbar.vue index da2d370fe09..92babfcc6fc 100644 --- a/src/components/ui/VnSearchbar.vue +++ b/src/components/ui/VnSearchbar.vue @@ -51,10 +51,6 @@ const props = defineProps({ type: Object, default: null, }, - staticParams: { - type: Array, - default: () => [], - }, exprBuilder: { type: Function, default: null, @@ -67,6 +63,10 @@ const props = defineProps({ type: Function, default: undefined, }, + searchRemoveParams: { + type: Boolean, + default: true, + }, }); const searchText = ref(); @@ -105,12 +105,18 @@ async function search() { const filter = { params: { - ...Object.fromEntries(staticParams), search: searchText.value, }, - ...{ filter: props.filter }, + filter: props.filter, }; + if (!props.searchRemoveParams || !searchText.value) { + filter.params = { + ...Object.fromEntries(staticParams), + search: searchText.value, + }; + } + if (props.whereFilter) { filter.filter = { where: props.whereFilter(searchText.value), @@ -130,6 +136,7 @@ async function search() { dense standout autofocus + data-cy="vnSearchBar" > <template #prepend> <QIcon diff --git a/src/pages/Order/Card/OrderCatalog.vue b/src/pages/Order/Card/OrderCatalog.vue index 948970cc3c3..744f87297ac 100644 --- a/src/pages/Order/Card/OrderCatalog.vue +++ b/src/pages/Order/Card/OrderCatalog.vue @@ -1,7 +1,7 @@ <script setup> import { useStateStore } from 'stores/useStateStore'; import { useRoute, useRouter } from 'vue-router'; -import { onMounted, onUnmounted, ref, computed, watch, provide, nextTick } from 'vue'; +import { onMounted, onUnmounted, ref, computed, watch, provide } from 'vue'; import axios from 'axios'; import { useI18n } from 'vue-i18n'; import VnPaginate from 'src/components/ui/VnPaginate.vue'; @@ -101,6 +101,7 @@ provide('onItemSaved', onItemSaved); url="Orders/CatalogFilter" :label="t('Search items')" :info="t('You can search items by name or id')" + :search-remove-params="false" /> <QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above> <QScrollArea class="fit text-grey-8"> diff --git a/src/pages/Zone/Card/ZoneLocationsTree.vue b/src/pages/Zone/Card/ZoneLocationsTree.vue index 650047e40c7..5c87faf999e 100644 --- a/src/pages/Zone/Card/ZoneLocationsTree.vue +++ b/src/pages/Zone/Card/ZoneLocationsTree.vue @@ -163,7 +163,13 @@ onUnmounted(() => { <QBtn color="primary" icon="search" dense flat @click="reFetch()" /> </template> </VnInput> - <VnSearchbar v-if="!showSearchBar" :data-key="datakey" :url="url" :redirect="false" /> + <VnSearchbar + v-if="!showSearchBar" + :data-key="datakey" + :url="url" + :redirect="false" + :search-remove-params="false" + /> <QTree ref="treeRef" :nodes="nodes" From 0ab12d7b5d9b964fae83e64ab84367eebd473bbf Mon Sep 17 00:00:00 2001 From: alexm <alexm@verdnatura.es> Date: Mon, 16 Dec 2024 12:26:41 +0100 Subject: [PATCH 11/34] fix: refs #8197 vnTableFilter in vnTable --- src/components/VnTable/VnTable.vue | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/components/VnTable/VnTable.vue b/src/components/VnTable/VnTable.vue index 521c19d3243..5f073fb65ec 100644 --- a/src/components/VnTable/VnTable.vue +++ b/src/components/VnTable/VnTable.vue @@ -305,11 +305,17 @@ function handleSelection({ evt, added, rows: selectedRows }, rows) { } </script> <template> - <VnTableFilter + <QDrawer v-if="$props.rightSearch" - :data-key="$attrs['data-key']" - :columns="columns" - /> + v-model="stateStore.rightDrawer" + side="right" + :width="256" + show-if-above + > + <QScrollArea class="fit"> + <VnTableFilter :data-key="$attrs['data-key']" :columns="columns" /> + </QScrollArea> + </QDrawer> <CrudModel v-bind="$attrs" :class="$attrs['class'] ?? 'q-px-md'" From 95420e96d1681f4838f584a9c60a1112cd29744f Mon Sep 17 00:00:00 2001 From: alexm <alexm@verdnatura.es> Date: Mon, 16 Dec 2024 12:27:13 +0100 Subject: [PATCH 12/34] refactor: refs #8197 adapt AccountAcls to VnCardMain --- src/pages/Account/AccountAcls.vue | 60 ++++++++++++++++--------------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/src/pages/Account/AccountAcls.vue b/src/pages/Account/AccountAcls.vue index d80f835ecc2..73771a34107 100644 --- a/src/pages/Account/AccountAcls.vue +++ b/src/pages/Account/AccountAcls.vue @@ -1,16 +1,15 @@ <script setup> import { useI18n } from 'vue-i18n'; import { ref, computed } from 'vue'; -import { useStateStore } from 'stores/useStateStore'; import axios from 'axios'; import useNotify from 'src/composables/useNotify.js'; import { useQuasar } from 'quasar'; import VnTable from 'components/VnTable/VnTable.vue'; -import VnSearchbar from 'components/ui/VnSearchbar.vue'; import VnConfirm from 'components/ui/VnConfirm.vue'; import FetchData from 'src/components/FetchData.vue'; import { useValidator } from 'src/composables/useValidator'; +import VnCardMain from 'src/components/common/VnCardMain.vue'; defineProps({ id: { @@ -21,13 +20,13 @@ defineProps({ const { notify } = useNotify(); const { t } = useI18n(); -const stateStore = useStateStore(); const quasar = useQuasar(); const tableRef = ref(); const roles = ref(); const validationsStore = useValidator(); const { models } = validationsStore; +const dataKey = 'AccountAcls'; const exprBuilder = (param, value) => { switch (param) { case 'search': @@ -134,38 +133,41 @@ const deleteAcl = async ({ id }) => { </script> <template> - <VnSearchbar - data-key="AccountAcls" - url="ACLs" - :expr-builder="exprBuilder" - :label="t('acls.search')" - :info="t('acls.searchInfo')" - /> - <QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above> - </QDrawer> <FetchData url="VnRoles?fields=['name']" auto-load @on-fetch="(data) => (roles = data)" /> - <VnTable - ref="tableRef" - data-key="AccountAcls" - :url="`ACLs`" - :create="{ - urlCreate: 'ACLs', - title: 'Create ACL', - onDataSaved: () => tableRef.reload(), - formInitialData: {}, - }" - order="id DESC" - :disable-option="{ card: true }" + <VnCardMain + :section="dataKey" + :data-key="dataKey" :columns="columns" - default-mode="table" - :right-search="true" - :is-editable="true" - :use-model="true" - /> + prefix="acls" + :array-data-props="{ + url: 'ACLs', + order: 'id DESC', + exprBuilder, + }" + > + <template #body> + <VnTable + ref="tableRef" + data-key="AccountAcls" + :create="{ + urlCreate: 'ACLs', + title: 'Create ACL', + onDataSaved: () => tableRef.reload(), + formInitialData: {}, + }" + :disable-option="{ card: true }" + :columns="columns" + default-mode="table" + :right-search="false" + :is-editable="true" + :use-model="true" + /> + </template> + </VnCardMain> </template> <i18n> From dc665d43fe2da557a056e8e1feb5e21e9fcd481c Mon Sep 17 00:00:00 2001 From: alexm <alexm@verdnatura.es> Date: Mon, 16 Dec 2024 13:33:59 +0100 Subject: [PATCH 13/34] feat: refs #8197 default leftMenu --- src/components/common/VnCardMain.vue | 7 ------- src/components/common/VnSectionMain.vue | 24 ++++++++++++++++++++++-- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/components/common/VnCardMain.vue b/src/components/common/VnCardMain.vue index b222748b9f7..5b8e6b5e87a 100644 --- a/src/components/common/VnCardMain.vue +++ b/src/components/common/VnCardMain.vue @@ -1,12 +1,9 @@ <script setup> -import LeftMenu from '../LeftMenu.vue'; -import { useStateStore } from 'stores/useStateStore'; import RightMenu from './RightMenu.vue'; import VnSearchbar from 'components/ui/VnSearchbar.vue'; import VnTableFilter from '../VnTable/VnTableFilter.vue'; import { onBeforeMount } from 'vue'; import { useArrayData } from 'src/composables/useArrayData'; -const stateStore = useStateStore(); const $props = defineProps({ section: { @@ -58,10 +55,6 @@ onBeforeMount(() => { /> </slot> - <Teleport to="#left-panel" v-if="stateStore.isHeaderMounted()"> - <LeftMenu v-if="section == $route.name" /> - </Teleport> - <RightMenu> <template #right-panel v-if="$slots['rightMenu'] || rightFilter"> <slot name="rightMenu"> diff --git a/src/components/common/VnSectionMain.vue b/src/components/common/VnSectionMain.vue index c1b9808b5cf..4e800fa8aaa 100644 --- a/src/components/common/VnSectionMain.vue +++ b/src/components/common/VnSectionMain.vue @@ -1,7 +1,8 @@ <script setup> import { useStateStore } from 'stores/useStateStore'; -import { onMounted } from 'vue'; +import { onMounted, ref } from 'vue'; import { useQuasar } from 'quasar'; +import LeftMenu from '../LeftMenu.vue'; const stateStore = useStateStore(); const $props = defineProps({ @@ -13,12 +14,31 @@ const $props = defineProps({ onMounted( () => (stateStore.leftDrawer = useQuasar().screen.gt.xs ? $props.leftDrawer : false) ); + +const targetId = 'left-panel'; +const teleportRef = ref({}); +const hasContent = ref(); +let observer; + +onMounted(() => { + if (teleportRef.value) { + const checkContent = () => { + hasContent.value = teleportRef.value.innerHTML.trim() !== ''; + }; + + observer = new MutationObserver(checkContent); + observer.observe(teleportRef.value, { childList: true, subtree: true }); + + checkContent(); + } +}); </script> <template> <QDrawer v-model="stateStore.leftDrawer" show-if-above :width="256"> <QScrollArea class="fit text-grey-8"> - <div id="left-panel"></div> + <div :id="targetId" ref="teleportRef"></div> + <LeftMenu v-if="!hasContent" /> </QScrollArea> </QDrawer> <QPageContainer> From dc83d50e96e1882b98499eb5addc1acbf55ad998 Mon Sep 17 00:00:00 2001 From: alexm <alexm@verdnatura.es> Date: Mon, 16 Dec 2024 14:30:23 +0100 Subject: [PATCH 14/34] refactor: refs #8197 backward compatible --- src/components/LeftMenu.vue | 31 ++++++++--- src/components/common/VnCard.vue | 45 +++++++++++---- src/components/common/VnCardBeta.vue | 65 ++++++++++++++++++++++ src/pages/Account/Alias/Card/AliasCard.vue | 4 +- src/pages/Account/Card/AccountCard.vue | 4 +- src/pages/Account/Role/Card/RoleCard.vue | 4 +- 6 files changed, 128 insertions(+), 25 deletions(-) create mode 100644 src/components/common/VnCardBeta.vue diff --git a/src/components/LeftMenu.vue b/src/components/LeftMenu.vue index eed2e192bed..09e126213fd 100644 --- a/src/components/LeftMenu.vue +++ b/src/components/LeftMenu.vue @@ -92,8 +92,10 @@ function findMatches(search, item) { } function addChildren(module, route, parent) { - if (!route?.meta?.menu) return; - const matches = findMatches(route.meta.menu, route); + const menus = route?.meta?.menu ?? route?.menus?.[props.source]; //backwards compatible + if (!menus) return; + + const matches = findMatches(menus, route); for (const child of matches) { navigation.addMenuItem(module, child, parent); @@ -118,17 +120,28 @@ function getRoutes() { } if (props.source === 'card') { - let menuRoute; - let index = route.matched.length - 1; + const currentRoute = route.matched[1]; + const currentModule = toLowerCamel(currentRoute.name); + let moduleDef = routes.find( + (route) => toLowerCamel(route.name) === currentModule + ); - while (!menuRoute && index > 0) { - if (route.matched[index]?.meta?.menu) menuRoute = route.matched[index]; - index--; - } - addChildren('', menuRoute, items.value); + if (!moduleDef) return; + if (!moduleDef?.menus) moduleDef = betaGetRoutes(); + addChildren(currentModule, moduleDef, items.value); } } +function betaGetRoutes() { + let menuRoute; + let index = route.matched.length - 1; + while (!menuRoute && index > 0) { + if (route.matched[index]?.meta?.menu) menuRoute = route.matched[index]; + index--; + } + return menuRoute; +} + async function togglePinned(item, event) { if (event.defaultPrevented) return; event.preventDefault(); diff --git a/src/components/common/VnCard.vue b/src/components/common/VnCard.vue index 16a077a79f1..0d80f43ce94 100644 --- a/src/components/common/VnCard.vue +++ b/src/components/common/VnCard.vue @@ -4,7 +4,10 @@ import { useRoute, useRouter, onBeforeRouteUpdate } from 'vue-router'; import { useArrayData } from 'src/composables/useArrayData'; import { useStateStore } from 'stores/useStateStore'; import useCardSize from 'src/composables/useCardSize'; +import VnSubToolbar from '../ui/VnSubToolbar.vue'; +import VnSearchbar from 'components/ui/VnSearchbar.vue'; import LeftMenu from 'components/LeftMenu.vue'; +import RightMenu from 'components/common/RightMenu.vue'; const props = defineProps({ dataKey: { type: String, required: true }, baseUrl: { type: String, default: undefined }, @@ -26,7 +29,10 @@ const url = computed(() => { } return props.customUrl; }); - +const searchRightDataKey = computed(() => { + if (!props.searchDataKey) return route.name; + return props.searchDataKey; +}); const arrayData = useArrayData(props.dataKey, { url: url.value, filter: props.filter, @@ -53,13 +59,32 @@ if (props.baseUrl) { } </script> <template> - <Teleport to="#left-panel" v-if="stateStore.isHeaderMounted()"> - <component :is="descriptor" /> - <QSeparator /> - <LeftMenu source="card" /> - </Teleport> - <VnSubToolbar /> - <div :class="[useCardSize(), $attrs.class]"> - <RouterView :key="route.path" /> - </div> + <QDrawer + v-model="stateStore.leftDrawer" + show-if-above + :width="256" + v-if="stateStore.isHeaderMounted()" + > + <QScrollArea class="fit"> + <component :is="descriptor" /> + <QSeparator /> + <LeftMenu source="card" /> + </QScrollArea> + </QDrawer> + <slot name="searchbar" v-if="props.searchDataKey"> + <VnSearchbar :data-key="props.searchDataKey" v-bind="props.searchbarProps" /> + </slot> + <RightMenu> + <template #right-panel v-if="props.filterPanel"> + <component :is="props.filterPanel" :data-key="searchRightDataKey" /> + </template> + </RightMenu> + <QPageContainer> + <QPage> + <VnSubToolbar /> + <div :class="[useCardSize(), $attrs.class]"> + <RouterView :key="route.path" /> + </div> + </QPage> + </QPageContainer> </template> diff --git a/src/components/common/VnCardBeta.vue b/src/components/common/VnCardBeta.vue new file mode 100644 index 00000000000..16a077a79f1 --- /dev/null +++ b/src/components/common/VnCardBeta.vue @@ -0,0 +1,65 @@ +<script setup> +import { onBeforeMount, computed } from 'vue'; +import { useRoute, useRouter, onBeforeRouteUpdate } from 'vue-router'; +import { useArrayData } from 'src/composables/useArrayData'; +import { useStateStore } from 'stores/useStateStore'; +import useCardSize from 'src/composables/useCardSize'; +import LeftMenu from 'components/LeftMenu.vue'; +const props = defineProps({ + dataKey: { type: String, required: true }, + baseUrl: { type: String, default: undefined }, + customUrl: { type: String, default: undefined }, + filter: { type: Object, default: () => {} }, + descriptor: { type: Object, required: true }, + filterPanel: { type: Object, default: undefined }, + searchDataKey: { type: String, default: undefined }, + searchbarProps: { type: Object, default: undefined }, + redirectOnError: { type: Boolean, default: false }, +}); + +const stateStore = useStateStore(); +const route = useRoute(); +const router = useRouter(); +const url = computed(() => { + if (props.baseUrl) { + return `${props.baseUrl}/${route.params.id}`; + } + return props.customUrl; +}); + +const arrayData = useArrayData(props.dataKey, { + url: url.value, + filter: props.filter, +}); + +onBeforeMount(async () => { + try { + if (!props.baseUrl) arrayData.store.filter.where = { id: route.params.id }; + await arrayData.fetch({ append: false, updateRouter: false }); + } catch { + const { matched: matches } = router.currentRoute.value; + const { path } = matches.at(-1); + router.push({ path: path.replace(/:id.*/, '') }); + } +}); + +if (props.baseUrl) { + onBeforeRouteUpdate(async (to, from) => { + if (to.params.id !== from.params.id) { + arrayData.store.url = `${props.baseUrl}/${to.params.id}`; + await arrayData.fetch({ append: false, updateRouter: false }); + } + }); +} +</script> +<template> + <Teleport to="#left-panel" v-if="stateStore.isHeaderMounted()"> + <component :is="descriptor" /> + <QSeparator /> + <LeftMenu source="card" /> + </Teleport> + <VnSubToolbar /> + <div :class="[useCardSize(), $attrs.class]"> + <RouterView :key="route.path" /> + </div> +</template> diff --git a/src/pages/Account/Alias/Card/AliasCard.vue b/src/pages/Account/Alias/Card/AliasCard.vue index 65951b3bf61..3a814edc051 100644 --- a/src/pages/Account/Alias/Card/AliasCard.vue +++ b/src/pages/Account/Alias/Card/AliasCard.vue @@ -1,12 +1,12 @@ <script setup> import { useI18n } from 'vue-i18n'; -import VnCard from 'components/common/VnCard.vue'; +import VnCardBeta from 'components/common/VnCardBeta.vue'; import AliasDescriptor from './AliasDescriptor.vue'; const { t } = useI18n(); </script> <template> - <VnCard + <VnCardBeta data-key="Alias" base-url="MailAliases" :descriptor="AliasDescriptor" diff --git a/src/pages/Account/Card/AccountCard.vue b/src/pages/Account/Card/AccountCard.vue index ba9040852cf..35ff7e73229 100644 --- a/src/pages/Account/Card/AccountCard.vue +++ b/src/pages/Account/Card/AccountCard.vue @@ -1,8 +1,8 @@ <script setup> -import VnCard from 'components/common/VnCard.vue'; +import VnCardBeta from 'components/common/VnCardBeta.vue'; import AccountDescriptor from './AccountDescriptor.vue'; </script> <template> - <VnCard data-key="AccountId" :descriptor="AccountDescriptor" /> + <VnCardBeta data-key="AccountId" :descriptor="AccountDescriptor" /> </template> diff --git a/src/pages/Account/Role/Card/RoleCard.vue b/src/pages/Account/Role/Card/RoleCard.vue index da6ac61d87a..7664deca8da 100644 --- a/src/pages/Account/Role/Card/RoleCard.vue +++ b/src/pages/Account/Role/Card/RoleCard.vue @@ -1,7 +1,7 @@ <script setup> -import VnCard from 'components/common/VnCard.vue'; +import VnCardBeta from 'components/common/VnCardBeta.vue'; import RoleDescriptor from './RoleDescriptor.vue'; </script> <template> - <VnCard data-key="Role" :descriptor="RoleDescriptor" /> + <VnCardBeta data-key="Role" :descriptor="RoleDescriptor" /> </template> From 14ca6d73f1a992bdbfe7134e6fac5f73cdfa9fe2 Mon Sep 17 00:00:00 2001 From: Jtubau <jtubau@verdnatura.es> Date: Tue, 17 Dec 2024 07:36:16 +0100 Subject: [PATCH 15/34] feat: refs #7074 tests for fns setData(), parseDms() and showFormDialog() --- .../components/common/VnDmsList.spec.js | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 test/vitest/__tests__/components/common/VnDmsList.spec.js diff --git a/test/vitest/__tests__/components/common/VnDmsList.spec.js b/test/vitest/__tests__/components/common/VnDmsList.spec.js new file mode 100644 index 00000000000..49228ddf8fc --- /dev/null +++ b/test/vitest/__tests__/components/common/VnDmsList.spec.js @@ -0,0 +1,93 @@ +import { createWrapper, axios } from 'app/test/vitest/helper'; +import VnDmsList from 'src/components/common/VnDmsList.vue'; +import { vi, afterEach, beforeAll, describe, expect, it } from 'vitest'; + +describe('VnDmsList', () => { + let vm; + + beforeAll(() => { + vi.spyOn(axios, 'get').mockResolvedValue({ data: [] }); + vm = createWrapper(VnDmsList, { + props: { + model: 'WorkerDms/1110/filter', + defaultDmsCode: 'hhrrData', + filter: 'wd.workerFk', + updateModel: 'Workers', + deleteModel: 'WorkerDms', + downloadModel: 'WorkerDms' + } + }).vm; + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + describe('setData()', () => { + const data = [ + { + userFk: 1, + name: 'Jessica', + lastName: 'Jones', + file: '4.jpg', + created: '2021-07-28 21:00:00' + }, + { + userFk: 2, + name: 'Bruce', + lastName: 'Banner', + created: '2022-07-28 21:00:00', + dms: { + userFk: 2, + name: 'Bruce', + lastName: 'BannerDMS', + created: '2022-07-28 21:00:00', + file: '4.jpg', + } + }, + { + userFk: 3, + name: 'Natasha', + lastName: 'Romanoff', + file: '4.jpg', + created: '2021-10-28 21:00:00' + } + ] + + it('Should replace objects that contain the "dms" property with the value of the same and sort by creation date', () => { + vm.setData(data); + expect([vm.rows][0][0].lastName).toEqual('BannerDMS'); + expect([vm.rows][0][1].lastName).toEqual('Romanoff'); + + }); + }); + + describe('parseDms()', () => { + const dms = { + userFk: 1, + name: 'DMS 1' + }; + + const resultDms = { ...dms, userId:1}; + + it('Should add properties that end with "Fk" by changing the suffix to "Id"', () => { + const parsedDms = vm.parseDms(dms); + expect(parsedDms).toEqual(resultDms); + }); + }); + + describe('showFormDialog()', () => { + const dms = { + userFk: 1, + name: 'DMS 1' + }; + + const resultDms = { ...dms, userId:1}; + + it('should call fn parseDms() and set show true if dms is defined', () => { + vm.showFormDialog(dms); + expect(vm.formDialog.show).toEqual(true); + expect(vm.formDialog.dms).toEqual(resultDms); + }); + }); +}); \ No newline at end of file From 586d5eff3e5a6e34aa9fac9de09958ebe6888bed Mon Sep 17 00:00:00 2001 From: alexm <alexm@verdnatura.es> Date: Tue, 17 Dec 2024 09:05:22 +0100 Subject: [PATCH 16/34] chore: refs #8197 unnecessary file --- src/utils/getUserParams.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/utils/getUserParams.js diff --git a/src/utils/getUserParams.js b/src/utils/getUserParams.js deleted file mode 100644 index e69de29bb2d..00000000000 From 8ab10dda1b8e6461ed5392baa4d6ceb9bf28f286 Mon Sep 17 00:00:00 2001 From: Jtubau <jtubau@verdnatura.es> Date: Wed, 18 Dec 2024 08:07:44 +0100 Subject: [PATCH 17/34] refactor: refs #7074 move dms constant to global scope --- .../__tests__/components/common/VnDmsList.spec.js | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/test/vitest/__tests__/components/common/VnDmsList.spec.js b/test/vitest/__tests__/components/common/VnDmsList.spec.js index 49228ddf8fc..9649943a237 100644 --- a/test/vitest/__tests__/components/common/VnDmsList.spec.js +++ b/test/vitest/__tests__/components/common/VnDmsList.spec.js @@ -4,6 +4,10 @@ import { vi, afterEach, beforeAll, describe, expect, it } from 'vitest'; describe('VnDmsList', () => { let vm; + const dms = { + userFk: 1, + name: 'DMS 1' + }; beforeAll(() => { vi.spyOn(axios, 'get').mockResolvedValue({ data: [] }); @@ -63,11 +67,6 @@ describe('VnDmsList', () => { }); describe('parseDms()', () => { - const dms = { - userFk: 1, - name: 'DMS 1' - }; - const resultDms = { ...dms, userId:1}; it('Should add properties that end with "Fk" by changing the suffix to "Id"', () => { @@ -77,11 +76,6 @@ describe('VnDmsList', () => { }); describe('showFormDialog()', () => { - const dms = { - userFk: 1, - name: 'DMS 1' - }; - const resultDms = { ...dms, userId:1}; it('should call fn parseDms() and set show true if dms is defined', () => { From 9783be1ff00dd2709f9e3633956e85797be49616 Mon Sep 17 00:00:00 2001 From: Jon <jon@verdnatura.es> Date: Wed, 18 Dec 2024 10:45:02 +0100 Subject: [PATCH 18/34] fix: fixed recipient param --- src/pages/Worker/Card/WorkerTimeControl.vue | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/pages/Worker/Card/WorkerTimeControl.vue b/src/pages/Worker/Card/WorkerTimeControl.vue index 491e5e1802b..96e7cd44146 100644 --- a/src/pages/Worker/Card/WorkerTimeControl.vue +++ b/src/pages/Worker/Card/WorkerTimeControl.vue @@ -100,15 +100,23 @@ const formattedWeekTotalHours = computed(() => secondsToHoursMinutes(weekTotalHours.value) ); +// const onInputChange = async (date) => { +// if (!date) return; + +// const { timestamp, outside } = date.scope; +// const { year, month, day } = timestamp; +// const _date = new Date(year, month - 1, day); +// setDate(_date); + +// if (outside) getMailStates(_date); +// }; + const onInputChange = async (date) => { if (!date) return; - const { timestamp, outside } = date.scope; - const { year, month, day } = timestamp; + const { year, month, day } = date.scope.timestamp; const _date = new Date(year, month - 1, day); setDate(_date); - - if (outside) getMailStates(_date); }; const setDate = async (date) => { @@ -381,12 +389,13 @@ const isUnsatisfied = async (reason) => { const resendEmail = async () => { const params = { - recipient: worker.value?.user?.email, + recipient: worker.value[0]?.user?.emailUser?.email, week: selectedWeekNumber.value, year: selectedDate.value.getFullYear(), workerId: Number(route.params.id), state: 'SENDED', }; + console.log('params: ', params); await axios.post('WorkerTimeControls/weekly-hour-record-email', params); await getMailStates(selectedDate.value); notify(t('Email sended'), 'positive'); From 06d3a025fcd355981a41887a4a4382ca584941af Mon Sep 17 00:00:00 2001 From: Jon <jon@verdnatura.es> Date: Wed, 18 Dec 2024 10:45:42 +0100 Subject: [PATCH 19/34] fix: deleted code --- src/pages/Worker/Card/WorkerTimeControl.vue | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/Worker/Card/WorkerTimeControl.vue b/src/pages/Worker/Card/WorkerTimeControl.vue index 96e7cd44146..f4c81102f7e 100644 --- a/src/pages/Worker/Card/WorkerTimeControl.vue +++ b/src/pages/Worker/Card/WorkerTimeControl.vue @@ -395,7 +395,6 @@ const resendEmail = async () => { workerId: Number(route.params.id), state: 'SENDED', }; - console.log('params: ', params); await axios.post('WorkerTimeControls/weekly-hour-record-email', params); await getMailStates(selectedDate.value); notify(t('Email sended'), 'positive'); From 76788fe8892e2744698283ce749787f4549d46a0 Mon Sep 17 00:00:00 2001 From: Jon <jon@verdnatura.es> Date: Wed, 18 Dec 2024 10:46:50 +0100 Subject: [PATCH 20/34] fix: changes --- src/pages/Worker/Card/WorkerTimeControl.vue | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/pages/Worker/Card/WorkerTimeControl.vue b/src/pages/Worker/Card/WorkerTimeControl.vue index f4c81102f7e..c480d5bd89c 100644 --- a/src/pages/Worker/Card/WorkerTimeControl.vue +++ b/src/pages/Worker/Card/WorkerTimeControl.vue @@ -100,23 +100,15 @@ const formattedWeekTotalHours = computed(() => secondsToHoursMinutes(weekTotalHours.value) ); -// const onInputChange = async (date) => { -// if (!date) return; - -// const { timestamp, outside } = date.scope; -// const { year, month, day } = timestamp; -// const _date = new Date(year, month - 1, day); -// setDate(_date); - -// if (outside) getMailStates(_date); -// }; - const onInputChange = async (date) => { if (!date) return; - const { year, month, day } = date.scope.timestamp; + const { timestamp, outside } = date.scope; + const { year, month, day } = timestamp; const _date = new Date(year, month - 1, day); setDate(_date); + + if (outside) getMailStates(_date); }; const setDate = async (date) => { From b54f39f1a066dde5a59a8b3e4911e1636c2daebe Mon Sep 17 00:00:00 2001 From: alexm <alexm@verdnatura.es> Date: Wed, 18 Dec 2024 13:04:00 +0100 Subject: [PATCH 21/34] feat: refs #8197 default sectionName --- src/components/common/VnCardMain.vue | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/common/VnCardMain.vue b/src/components/common/VnCardMain.vue index 5b8e6b5e87a..e6afea4b6a3 100644 --- a/src/components/common/VnCardMain.vue +++ b/src/components/common/VnCardMain.vue @@ -2,7 +2,7 @@ import RightMenu from './RightMenu.vue'; import VnSearchbar from 'components/ui/VnSearchbar.vue'; import VnTableFilter from '../VnTable/VnTableFilter.vue'; -import { onBeforeMount } from 'vue'; +import { onBeforeMount, computed } from 'vue'; import { useArrayData } from 'src/composables/useArrayData'; const $props = defineProps({ @@ -36,6 +36,8 @@ const $props = defineProps({ }, }); +const sectionValue = computed(() => $props.section ?? $props.dataKey); + onBeforeMount(() => { if ($props.dataKey) useArrayData($props.dataKey, { @@ -67,6 +69,6 @@ onBeforeMount(() => { </template> </RightMenu> - <slot name="body" v-if="section == $route.name" /> + <slot name="body" v-if="sectionValue == $route.name" /> <RouterView v-else /> </template> From 76b73cc6167e27f88d004625bcccb3f0c159dfd1 Mon Sep 17 00:00:00 2001 From: alexm <alexm@verdnatura.es> Date: Wed, 18 Dec 2024 13:04:10 +0100 Subject: [PATCH 22/34] perf: refs #8197 perf --- src/components/common/VnSectionMain.vue | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/common/VnSectionMain.vue b/src/components/common/VnSectionMain.vue index 4e800fa8aaa..505b3a8b56e 100644 --- a/src/components/common/VnSectionMain.vue +++ b/src/components/common/VnSectionMain.vue @@ -15,7 +15,6 @@ onMounted( () => (stateStore.leftDrawer = useQuasar().screen.gt.xs ? $props.leftDrawer : false) ); -const targetId = 'left-panel'; const teleportRef = ref({}); const hasContent = ref(); let observer; @@ -37,7 +36,7 @@ onMounted(() => { <template> <QDrawer v-model="stateStore.leftDrawer" show-if-above :width="256"> <QScrollArea class="fit text-grey-8"> - <div :id="targetId" ref="teleportRef"></div> + <div id="left-panel" ref="teleportRef"></div> <LeftMenu v-if="!hasContent" /> </QScrollArea> </QDrawer> From 32fd07dd14e401d6fed3608cc274dcf92094c342 Mon Sep 17 00:00:00 2001 From: alexm <alexm@verdnatura.es> Date: Wed, 18 Dec 2024 13:04:31 +0100 Subject: [PATCH 23/34] feat: refs #8197 default sectionName --- src/pages/Account/AccountAcls.vue | 1 - src/pages/Account/AccountAliasList.vue | 1 - src/pages/Account/AccountList.vue | 1 - src/pages/Account/Role/AccountRoles.vue | 1 - 4 files changed, 4 deletions(-) diff --git a/src/pages/Account/AccountAcls.vue b/src/pages/Account/AccountAcls.vue index 73771a34107..b457bb7f034 100644 --- a/src/pages/Account/AccountAcls.vue +++ b/src/pages/Account/AccountAcls.vue @@ -139,7 +139,6 @@ const deleteAcl = async ({ id }) => { @on-fetch="(data) => (roles = data)" /> <VnCardMain - :section="dataKey" :data-key="dataKey" :columns="columns" prefix="acls" diff --git a/src/pages/Account/AccountAliasList.vue b/src/pages/Account/AccountAliasList.vue index 721a009e512..9631c96399d 100644 --- a/src/pages/Account/AccountAliasList.vue +++ b/src/pages/Account/AccountAliasList.vue @@ -44,7 +44,6 @@ const exprBuilder = (param, value) => { <template> <VnCardMain - :section="dataKey" :data-key="dataKey" :columns="columns" prefix="mailAlias" diff --git a/src/pages/Account/AccountList.vue b/src/pages/Account/AccountList.vue index 34a653e61db..5296cc1d668 100644 --- a/src/pages/Account/AccountList.vue +++ b/src/pages/Account/AccountList.vue @@ -104,7 +104,6 @@ function exprBuilder(param, value) { <template> <VnCardMain - :section="dataKey" :data-key="dataKey" :columns="columns" prefix="account" diff --git a/src/pages/Account/Role/AccountRoles.vue b/src/pages/Account/Role/AccountRoles.vue index 8cc392f1bb8..4e67a691bef 100644 --- a/src/pages/Account/Role/AccountRoles.vue +++ b/src/pages/Account/Role/AccountRoles.vue @@ -86,7 +86,6 @@ const exprBuilder = (param, value) => { <template> <VnCardMain - :section="dataKey" :data-key="dataKey" :columns="columns" prefix="role" From 8c3c318099bc7d76696d5e3d43c758b65011b396 Mon Sep 17 00:00:00 2001 From: alexm <alexm@verdnatura.es> Date: Wed, 18 Dec 2024 13:16:45 +0100 Subject: [PATCH 24/34] test(VnTable): refs #8197 mock useFilterParams --- test/vitest/__tests__/components/VnTable.spec.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/test/vitest/__tests__/components/VnTable.spec.js b/test/vitest/__tests__/components/VnTable.spec.js index 162df727dd6..74ba0698765 100644 --- a/test/vitest/__tests__/components/VnTable.spec.js +++ b/test/vitest/__tests__/components/VnTable.spec.js @@ -1,4 +1,4 @@ -import { describe, expect, it, beforeAll, beforeEach } from 'vitest'; +import { describe, expect, it, beforeAll, beforeEach, vi } from 'vitest'; import { createWrapper } from 'app/test/vitest/helper'; import VnTable from 'src/components/VnTable/VnTable.vue'; @@ -13,6 +13,15 @@ describe('VnTable', () => { }, }); vm = wrapper.vm; + + vi.mock('src/composables/useFilterParams', () => { + return { + useFilterParams: vi.fn(() => ({ + params: {}, + orders: {}, + })), + }; + }); }); beforeEach(() => (vm.selected = [])); From fccca9ea477c74237adfffcc5566a0c592560e05 Mon Sep 17 00:00:00 2001 From: alexm <alexm@verdnatura.es> Date: Wed, 18 Dec 2024 13:42:57 +0100 Subject: [PATCH 25/34] refactor: refs #8197 rename VnSectionMain to VnModule and VnCardMain to VnSection --- src/components/common/VnCardBeta.vue | 2 + .../{VnSectionMain.vue => VnModule.vue} | 0 .../common/{VnCardMain.vue => VnSection.vue} | 0 src/pages/Account/AccountAcls.vue | 6 +- src/pages/Account/AccountAliasList.vue | 6 +- src/pages/Account/AccountList.vue | 6 +- src/pages/Account/Role/AccountRoles.vue | 6 +- src/pages/Ticket/Card/TicketBoxing.vue | 136 +++++++++--------- src/router/modules/account.js | 2 +- src/router/modules/claim.js | 2 +- src/router/modules/customer.js | 2 +- src/router/modules/entry.js | 2 +- src/router/modules/invoiceIn.js | 2 +- src/router/modules/invoiceOut.js | 2 +- src/router/modules/item.js | 2 +- src/router/modules/monitor.js | 2 +- src/router/modules/order.js | 2 +- src/router/modules/route.js | 2 +- src/router/modules/shelving.js | 2 +- src/router/modules/supplier.js | 2 +- src/router/modules/travel.js | 2 +- src/router/modules/wagon.js | 4 +- src/router/modules/worker.js | 2 +- src/router/modules/zone.js | 2 +- 24 files changed, 98 insertions(+), 98 deletions(-) rename src/components/common/{VnSectionMain.vue => VnModule.vue} (100%) rename src/components/common/{VnCardMain.vue => VnSection.vue} (100%) diff --git a/src/components/common/VnCardBeta.vue b/src/components/common/VnCardBeta.vue index 16a077a79f1..349956be9f7 100644 --- a/src/components/common/VnCardBeta.vue +++ b/src/components/common/VnCardBeta.vue @@ -5,6 +5,8 @@ import { useArrayData } from 'src/composables/useArrayData'; import { useStateStore } from 'stores/useStateStore'; import useCardSize from 'src/composables/useCardSize'; import LeftMenu from 'components/LeftMenu.vue'; +import VnSubToolbar from '../ui/VnSubToolbar.vue'; + const props = defineProps({ dataKey: { type: String, required: true }, baseUrl: { type: String, default: undefined }, diff --git a/src/components/common/VnSectionMain.vue b/src/components/common/VnModule.vue similarity index 100% rename from src/components/common/VnSectionMain.vue rename to src/components/common/VnModule.vue diff --git a/src/components/common/VnCardMain.vue b/src/components/common/VnSection.vue similarity index 100% rename from src/components/common/VnCardMain.vue rename to src/components/common/VnSection.vue diff --git a/src/pages/Account/AccountAcls.vue b/src/pages/Account/AccountAcls.vue index b457bb7f034..b4eeb0648d4 100644 --- a/src/pages/Account/AccountAcls.vue +++ b/src/pages/Account/AccountAcls.vue @@ -9,7 +9,7 @@ import VnTable from 'components/VnTable/VnTable.vue'; import VnConfirm from 'components/ui/VnConfirm.vue'; import FetchData from 'src/components/FetchData.vue'; import { useValidator } from 'src/composables/useValidator'; -import VnCardMain from 'src/components/common/VnCardMain.vue'; +import VnSection from 'src/components/common/VnSection.vue'; defineProps({ id: { @@ -138,7 +138,7 @@ const deleteAcl = async ({ id }) => { auto-load @on-fetch="(data) => (roles = data)" /> - <VnCardMain + <VnSection :data-key="dataKey" :columns="columns" prefix="acls" @@ -166,7 +166,7 @@ const deleteAcl = async ({ id }) => { :use-model="true" /> </template> - </VnCardMain> + </VnSection> </template> <i18n> diff --git a/src/pages/Account/AccountAliasList.vue b/src/pages/Account/AccountAliasList.vue index 9631c96399d..f6016fb6c18 100644 --- a/src/pages/Account/AccountAliasList.vue +++ b/src/pages/Account/AccountAliasList.vue @@ -2,7 +2,7 @@ import { useI18n } from 'vue-i18n'; import { ref, computed } from 'vue'; import VnTable from 'components/VnTable/VnTable.vue'; -import VnCardMain from 'src/components/common/VnCardMain.vue'; +import VnSection from 'src/components/common/VnSection.vue'; const tableRef = ref(); const { t } = useI18n(); @@ -43,7 +43,7 @@ const exprBuilder = (param, value) => { </script> <template> - <VnCardMain + <VnSection :data-key="dataKey" :columns="columns" prefix="mailAlias" @@ -68,7 +68,7 @@ const exprBuilder = (param, value) => { :right-search="false" /> </template> - </VnCardMain> + </VnSection> </template> <i18n> es: diff --git a/src/pages/Account/AccountList.vue b/src/pages/Account/AccountList.vue index 5296cc1d668..997e3104142 100644 --- a/src/pages/Account/AccountList.vue +++ b/src/pages/Account/AccountList.vue @@ -4,7 +4,7 @@ import { computed } from 'vue'; import VnTable from 'components/VnTable/VnTable.vue'; import AccountSummary from './Card/AccountSummary.vue'; import { useSummaryDialog } from 'src/composables/useSummaryDialog'; -import VnCardMain from 'src/components/common/VnCardMain.vue'; +import VnSection from 'src/components/common/VnSection.vue'; const { t } = useI18n(); const { viewSummary } = useSummaryDialog(); @@ -103,7 +103,7 @@ function exprBuilder(param, value) { </script> <template> - <VnCardMain + <VnSection :data-key="dataKey" :columns="columns" prefix="account" @@ -124,7 +124,7 @@ function exprBuilder(param, value) { :right-search="false" /> </template> - </VnCardMain> + </VnSection> </template> <i18n> diff --git a/src/pages/Account/Role/AccountRoles.vue b/src/pages/Account/Role/AccountRoles.vue index 4e67a691bef..3c3d6b243fc 100644 --- a/src/pages/Account/Role/AccountRoles.vue +++ b/src/pages/Account/Role/AccountRoles.vue @@ -5,7 +5,7 @@ import VnTable from 'components/VnTable/VnTable.vue'; import { useRoute } from 'vue-router'; import { useSummaryDialog } from 'src/composables/useSummaryDialog'; import RoleSummary from './Card/RoleSummary.vue'; -import VnCardMain from 'src/components/common/VnCardMain.vue'; +import VnSection from 'src/components/common/VnSection.vue'; const route = useRoute(); const { t } = useI18n(); @@ -85,7 +85,7 @@ const exprBuilder = (param, value) => { </script> <template> - <VnCardMain + <VnSection :data-key="dataKey" :columns="columns" prefix="role" @@ -110,7 +110,7 @@ const exprBuilder = (param, value) => { :right-search="false" /> </template> - </VnCardMain> + </VnSection> </template> <i18n> diff --git a/src/pages/Ticket/Card/TicketBoxing.vue b/src/pages/Ticket/Card/TicketBoxing.vue index 1a728739645..5675fe1b30f 100644 --- a/src/pages/Ticket/Card/TicketBoxing.vue +++ b/src/pages/Ticket/Card/TicketBoxing.vue @@ -1,15 +1,18 @@ <script setup> import axios from 'axios'; import { date, useQuasar } from 'quasar'; -import { computed, onMounted, reactive, ref } from 'vue'; +import { computed, onMounted, onUnmounted, reactive, ref } from 'vue'; import { useI18n } from 'vue-i18n'; import { useRouter } from 'vue-router'; +import { useStateStore } from 'stores/useStateStore'; const router = useRouter(); +const stateStore = useStateStore(); const { t } = useI18n(); const quasar = useQuasar(); onMounted(async () => { + stateStore.rightDrawer = true; await fetch(); }); @@ -84,74 +87,69 @@ async function getVideoList(expeditionId, timed) { </script> <template> - <QDrawer show-if-above side="right"> - <QScrollArea class="fit"> - <QList bordered separator style="max-width: 318px"> - <QItem v-if="lastExpedition && videoList.length"> - <QItemSection> - <QItemLabel class="text-h6"> - {{ t('ticket.boxing.selectTime') }} ({{ time.min }}-{{ - time.max - }}) - </QItemLabel> - <QRange - v-model="time" - @change="getVideoList(lastExpedition, time)" - :min="0" - :max="24" - :step="1" - :left-label-value="time.min + ':00'" - :right-label-value="time.max + ':00'" - label - markers - snap - color="primary" - /> - </QItemSection> - </QItem> - <QItem v-if="lastExpedition && videoList.length"> - <QItemSection> - <QSelect - color="primary" - v-model="slide" - :options="videoList" - :label="t('ticket.boxing.selectVideo')" - emit-value - map-options - > - <template #prepend> - <QIcon name="schedule" /> - </template> - </QSelect> - </QItemSection> - </QItem> - <QItem - v-for="expedition in expeditions" - :key="expedition.id" - @click="getVideoList(expedition.id)" - clickable - v-ripple - > - <QItemSection> - <QItemLabel class="text-h6">#{{ expedition.id }}</QItemLabel> - </QItemSection> - <QItemSection> - <QItemLabel caption>{{ t('globals.created') }}</QItemLabel> - <QItemLabel> - {{ - date.formatDate(expedition.created, 'YYYY-MM-DD HH:mm:ss') - }} - </QItemLabel> - <QItemLabel caption>{{ t('globals.item') }}</QItemLabel> - <QItemLabel>{{ expedition.packagingItemFk }}</QItemLabel> - <QItemLabel caption>{{ t('ticket.boxing.worker') }}</QItemLabel> - <QItemLabel>{{ expedition.userName }}</QItemLabel> - </QItemSection> - </QItem> - </QList> - </QScrollArea> - </QDrawer> - + <Teleport to="#right-panel" v-if="stateStore.isHeaderMounted()"> + <QList bordered separator style="max-width: 318px"> + <QItem v-if="lastExpedition && videoList.length"> + <QItemSection> + <QItemLabel class="text-h6"> + {{ t('ticket.boxing.selectTime') }} ({{ time.min }}-{{ + time.max + }}) + </QItemLabel> + <QRange + v-model="time" + @change="getVideoList(lastExpedition, time)" + :min="0" + :max="24" + :step="1" + :left-label-value="time.min + ':00'" + :right-label-value="time.max + ':00'" + label + markers + snap + color="primary" + /> + </QItemSection> + </QItem> + <QItem v-if="lastExpedition && videoList.length"> + <QItemSection> + <QSelect + color="primary" + v-model="slide" + :options="videoList" + :label="t('ticket.boxing.selectVideo')" + emit-value + map-options + > + <template #prepend> + <QIcon name="schedule" /> + </template> + </QSelect> + </QItemSection> + </QItem> + <QItem + v-for="expedition in expeditions" + :key="expedition.id" + @click="getVideoList(expedition.id)" + clickable + v-ripple + > + <QItemSection> + <QItemLabel class="text-h6">#{{ expedition.id }}</QItemLabel> + </QItemSection> + <QItemSection> + <QItemLabel caption>{{ t('globals.created') }}</QItemLabel> + <QItemLabel> + {{ date.formatDate(expedition.created, 'YYYY-MM-DD HH:mm:ss') }} + </QItemLabel> + <QItemLabel caption>{{ t('globals.item') }}</QItemLabel> + <QItemLabel>{{ expedition.packagingItemFk }}</QItemLabel> + <QItemLabel caption>{{ t('ticket.boxing.worker') }}</QItemLabel> + <QItemLabel>{{ expedition.userName }}</QItemLabel> + </QItemSection> + </QItem> + </QList> + </Teleport> <QCard> <QCarousel animated v-model="slide" height="max-content"> <QCarouselSlide diff --git a/src/router/modules/account.js b/src/router/modules/account.js index 6f5ca90f3cf..466db953945 100644 --- a/src/router/modules/account.js +++ b/src/router/modules/account.js @@ -28,7 +28,7 @@ export default { { path: '', name: 'AccountMain', - component: () => import('src/components/common/VnSectionMain.vue'), + component: () => import('src/components/common/VnModule.vue'), redirect: { name: 'AccountIndexMain' }, children: [ { diff --git a/src/router/modules/claim.js b/src/router/modules/claim.js index b58a58e8dad..8b0a7089667 100644 --- a/src/router/modules/claim.js +++ b/src/router/modules/claim.js @@ -27,7 +27,7 @@ export default { { name: 'ClaimMain', path: '', - component: () => import('src/components/common/VnSectionMain.vue'), + component: () => import('src/components/common/VnModule.vue'), redirect: { name: 'ClaimList' }, children: [ { diff --git a/src/router/modules/customer.js b/src/router/modules/customer.js index 1b707f1a238..9e7f6fe703b 100644 --- a/src/router/modules/customer.js +++ b/src/router/modules/customer.js @@ -39,7 +39,7 @@ export default { { path: '', name: 'CustomerMain', - component: () => import('src/components/common/VnSectionMain.vue'), + component: () => import('src/components/common/VnModule.vue'), redirect: { name: 'CustomerList' }, children: [ { diff --git a/src/router/modules/entry.js b/src/router/modules/entry.js index 3add239df63..26ce773c5d1 100644 --- a/src/router/modules/entry.js +++ b/src/router/modules/entry.js @@ -25,7 +25,7 @@ export default { { path: '', name: 'EntryMain', - component: () => import('src/components/common/VnSectionMain.vue'), + component: () => import('src/components/common/VnModule.vue'), redirect: { name: 'EntryList' }, children: [ { diff --git a/src/router/modules/invoiceIn.js b/src/router/modules/invoiceIn.js index 168d64f373a..788b27d37d6 100644 --- a/src/router/modules/invoiceIn.js +++ b/src/router/modules/invoiceIn.js @@ -25,7 +25,7 @@ export default { { path: '', name: 'InvoiceInMain', - component: () => import('src/components/common/VnSectionMain.vue'), + component: () => import('src/components/common/VnModule.vue'), redirect: { name: 'InvoiceInList' }, children: [ { diff --git a/src/router/modules/invoiceOut.js b/src/router/modules/invoiceOut.js index 5e83b0859a3..53d27d0e8ee 100644 --- a/src/router/modules/invoiceOut.js +++ b/src/router/modules/invoiceOut.js @@ -18,7 +18,7 @@ export default { { path: '', name: 'InvoiceOutMain', - component: () => import('src/components/common/VnSectionMain.vue'), + component: () => import('src/components/common/VnModule.vue'), redirect: { name: 'InvoiceOutList' }, children: [ { diff --git a/src/router/modules/item.js b/src/router/modules/item.js index 0f810434c09..e2afd6c7bb7 100644 --- a/src/router/modules/item.js +++ b/src/router/modules/item.js @@ -36,7 +36,7 @@ export default { { path: '', name: 'ItemMain', - component: () => import('src/components/common/VnSectionMain.vue'), + component: () => import('src/components/common/VnModule.vue'), redirect: { name: 'ItemList' }, children: [ { diff --git a/src/router/modules/monitor.js b/src/router/modules/monitor.js index 2af60c09cd0..89ba4078f99 100644 --- a/src/router/modules/monitor.js +++ b/src/router/modules/monitor.js @@ -19,7 +19,7 @@ export default { { path: '', name: 'MonitorMain', - component: () => import('src/components/common/VnSectionMain.vue'), + component: () => import('src/components/common/VnModule.vue'), props: (route) => ({ leftDrawer: route.name === 'MonitorClientsActions' }), redirect: { name: 'MonitorTickets' }, children: [ diff --git a/src/router/modules/order.js b/src/router/modules/order.js index bfa37fce50c..77af812cf79 100644 --- a/src/router/modules/order.js +++ b/src/router/modules/order.js @@ -19,7 +19,7 @@ export default { { path: '', name: 'OrderMain', - component: () => import('src/components/common/VnSectionMain.vue'), + component: () => import('src/components/common/VnModule.vue'), redirect: { name: 'OrderList' }, children: [ { diff --git a/src/router/modules/route.js b/src/router/modules/route.js index 9a7b16df3a2..a6c4f30a223 100644 --- a/src/router/modules/route.js +++ b/src/router/modules/route.js @@ -25,7 +25,7 @@ export default { { path: '/route', name: 'RouteMain', - component: () => import('src/components/common/VnSectionMain.vue'), + component: () => import('src/components/common/VnModule.vue'), redirect: { name: 'RouteList' }, children: [ { diff --git a/src/router/modules/shelving.js b/src/router/modules/shelving.js index b7f50a3b606..dd254db6946 100644 --- a/src/router/modules/shelving.js +++ b/src/router/modules/shelving.js @@ -18,7 +18,7 @@ export default { { path: '', name: 'ShelvingMain', - component: () => import('src/components/common/VnSectionMain.vue'), + component: () => import('src/components/common/VnModule.vue'), redirect: { name: 'ShelvingList' }, children: [ { diff --git a/src/router/modules/supplier.js b/src/router/modules/supplier.js index c08fb596114..647f4bdd33e 100644 --- a/src/router/modules/supplier.js +++ b/src/router/modules/supplier.js @@ -30,7 +30,7 @@ export default { { path: '', name: 'SupplierMain', - component: () => import('src/components/common/VnSectionMain.vue'), + component: () => import('src/components/common/VnModule.vue'), redirect: { name: 'SupplierList' }, children: [ { diff --git a/src/router/modules/travel.js b/src/router/modules/travel.js index dff693d2fae..49272be1e0a 100644 --- a/src/router/modules/travel.js +++ b/src/router/modules/travel.js @@ -18,7 +18,7 @@ export default { { path: '', name: 'TravelMain', - component: () => import('src/components/common/VnSectionMain.vue'), + component: () => import('src/components/common/VnModule.vue'), redirect: { name: 'TravelList' }, children: [ { diff --git a/src/router/modules/wagon.js b/src/router/modules/wagon.js index e25e585eb57..5c7e881c2cb 100644 --- a/src/router/modules/wagon.js +++ b/src/router/modules/wagon.js @@ -18,7 +18,7 @@ export default { { path: '/wagon', name: 'WagonMain', - component: () => import('src/components/common/VnSectionMain.vue'), + component: () => import('src/components/common/VnModule.vue'), redirect: { name: 'WagonList' }, children: [ { @@ -62,7 +62,7 @@ export default { { path: '/wagon/type', name: 'WagonTypeMain', - component: () => import('src/components/common/VnSectionMain.vue'), + component: () => import('src/components/common/VnModule.vue'), redirect: { name: 'WagonTypeList' }, children: [ { diff --git a/src/router/modules/worker.js b/src/router/modules/worker.js index 9250197342d..c732664ecbe 100644 --- a/src/router/modules/worker.js +++ b/src/router/modules/worker.js @@ -35,7 +35,7 @@ export default { { path: '', name: 'WorkerMain', - component: () => import('src/components/common/VnSectionMain.vue'), + component: () => import('src/components/common/VnModule.vue'), redirect: { name: 'WorkerList' }, children: [ { diff --git a/src/router/modules/zone.js b/src/router/modules/zone.js index c5ebe762ee9..334ba2b51ad 100644 --- a/src/router/modules/zone.js +++ b/src/router/modules/zone.js @@ -30,7 +30,7 @@ export default { { path: '/zone', name: 'ZoneMain', - component: () => import('src/components/common/VnSectionMain.vue'), + component: () => import('src/components/common/VnModule.vue'), redirect: { name: 'ZoneList' }, children: [ { From 84ac4dd2102a825e0732128d22c1a7b3906744d1 Mon Sep 17 00:00:00 2001 From: alexm <alexm@verdnatura.es> Date: Wed, 18 Dec 2024 14:21:59 +0100 Subject: [PATCH 26/34] fix: refs #8197 vnPaginate onFetch emit --- src/components/ui/VnPaginate.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/ui/VnPaginate.vue b/src/components/ui/VnPaginate.vue index 90089593e05..42f558f89b8 100644 --- a/src/components/ui/VnPaginate.vue +++ b/src/components/ui/VnPaginate.vue @@ -106,6 +106,7 @@ const store = arrayData.store; onMounted(async () => { if (props.autoLoad && !store.data?.length) await fetch(); + else emit('onFetch', store.data); mounted.value = true; }); From a940eb9861b9193d659cf0cc69e43f3b81fba3c0 Mon Sep 17 00:00:00 2001 From: alexm <alexm@verdnatura.es> Date: Wed, 18 Dec 2024 14:22:19 +0100 Subject: [PATCH 27/34] refactor: refs #8197 adapt Ticket to VnCardMain --- src/pages/Ticket/Card/TicketCard.vue | 20 +- src/pages/Ticket/TicketList.vue | 431 ++++++++++++++------------- src/router/modules/ticket.js | 379 +++++++++++------------ 3 files changed, 413 insertions(+), 417 deletions(-) diff --git a/src/pages/Ticket/Card/TicketCard.vue b/src/pages/Ticket/Card/TicketCard.vue index 73b6f5543c2..6886a8e577a 100644 --- a/src/pages/Ticket/Card/TicketCard.vue +++ b/src/pages/Ticket/Card/TicketCard.vue @@ -1,23 +1,7 @@ <script setup> -import { useI18n } from 'vue-i18n'; - -import VnCard from 'components/common/VnCard.vue'; +import VnCardBeta from 'components/common/VnCardBeta.vue'; import TicketDescriptor from './TicketDescriptor.vue'; -import TicketFilter from '../TicketFilter.vue'; - -const { t } = useI18n(); </script> <template> - <VnCard - data-key="Ticket" - base-url="Tickets" - :filter-panel="TicketFilter" - :descriptor="TicketDescriptor" - search-data-key="TicketList" - :searchbar-props="{ - url: 'Tickets/filter', - label: t('card.search'), - info: t('card.searchInfo'), - }" - /> + <VnCardBeta data-key="Ticket" base-url="Tickets" :descriptor="TicketDescriptor" /> </template> diff --git a/src/pages/Ticket/TicketList.vue b/src/pages/Ticket/TicketList.vue index eb03a492721..823f74fc51a 100644 --- a/src/pages/Ticket/TicketList.vue +++ b/src/pages/Ticket/TicketList.vue @@ -8,13 +8,11 @@ import { useQuasar } from 'quasar'; import { toDate, toCurrency, dashIfEmpty } from 'src/filters/index'; import useNotify from 'src/composables/useNotify'; import TicketSummary from './Card/TicketSummary.vue'; -import VnSearchbar from 'src/components/ui/VnSearchbar.vue'; import { useSummaryDialog } from 'src/composables/useSummaryDialog'; import VnTable from 'src/components/VnTable/VnTable.vue'; import VnSelect from 'src/components/common/VnSelect.vue'; import VnInputDate from 'src/components/common/VnInputDate.vue'; import VnRow from 'src/components/ui/VnRow.vue'; -import RightMenu from 'src/components/common/RightMenu.vue'; import TicketFilter from './TicketFilter.vue'; import VnInput from 'src/components/common/VnInput.vue'; import FetchData from 'src/components/FetchData.vue'; @@ -23,6 +21,7 @@ import ZoneDescriptorProxy from 'src/pages/Zone/Card/ZoneDescriptorProxy.vue'; import { toTimeFormat } from 'src/filters/date'; import InvoiceOutDescriptorProxy from 'src/pages/InvoiceOut/Card/InvoiceOutDescriptorProxy.vue'; import TicketProblems from 'src/components/TicketProblems.vue'; +import VnSection from 'src/components/common/VnSection.vue'; const route = useRoute(); const router = useRouter(); @@ -66,6 +65,7 @@ const dialogData = ref(); const companiesOptions = ref([]); const accountingOptions = ref([]); const amountToReturn = ref(); +const dataKey = 'TicketList'; const columns = computed(() => [ { @@ -452,223 +452,228 @@ function setReference(data) { @on-fetch="(data) => (accountingOptions = data)" auto-load /> - <VnSearchbar - data-key="TicketList" - :label="t('Search ticket')" - :info="t('You can search by ticket id or alias')" - data-cy="ticketListSearchBar" - /> - <RightMenu> - <template #right-panel> + <VnSection + :data-key="dataKey" + :columns="columns" + prefix="card" + :array-data-props="{ + url: 'Tickets/filter', + order: ['shippedDate DESC', 'shippedHour ASC', 'zoneLanding ASC', 'id'], + exprBuilder, + }" + > + <template #rightMenu> <TicketFilter data-key="TicketList" /> </template> - </RightMenu> - <VnTable - ref="tableRef" - data-key="TicketList" - url="Tickets/filter" - :create="{ - urlCreate: 'Tickets/new', - title: t('ticketList.createTicket'), - onDataSaved: ({ id }) => tableRef.redirect(id), - formInitialData: { clientId: null }, - }" - default-mode="table" - :order="['shippedDate DESC', 'shippedHour ASC', 'zoneLanding ASC', 'id']" - :columns="columns" - :user-params="userParams" - :right-search="false" - redirect="ticket" - v-model:selected="selectedRows" - :table="{ - 'row-key': 'id', - selection: 'multiple', - }" - data-cy="ticketListTable" - > - <template #column-statusIcons="{ row }"> - <TicketProblems :row="row" /> - </template> - <template #column-salesPersonFk="{ row }"> - <span class="link" @click.stop> - {{ dashIfEmpty(row.userName) }} - <CustomerDescriptorProxy :id="row.salesPersonFk" /> - </span> - </template> - <template #column-shippedDate="{ row }"> - <span v-if="getDateColor(row.shipped)"> - <QChip :class="getDateColor(row.shipped)" dense square> - {{ toDate(row.shippedDate) }} - </QChip> - </span> - </template> - <template #column-nickname="{ row }"> - <span class="link" @click.stop> - {{ row.nickname }} - <CustomerDescriptorProxy :id="row.clientFk" /> - </span> - </template> - <template #column-addressNickname="{ row }"> - <span class="link" @click.stop> - {{ row.addressNickname }} - <CustomerDescriptorProxy :id="row.clientFk" /> - </span> - </template> - <template #column-stateFk="{ row }"> - <span v-if="row.refFk"> - <span class="link" @click.stop> - {{ row.refFk }} - <InvoiceOutDescriptorProxy :id="row.invoiceOutId" /> - </span> - </span> - <span v-else-if="getColor(row)"> - <QChip :class="getColor(row)" dense square> - {{ row.state }} - </QChip> - </span> - <span v-else> - {{ row.state }} - </span> - </template> - <template #column-zoneFk="{ row }"> - <span class="link" @click.stop> - {{ dashIfEmpty(row.zoneName) }} - <ZoneDescriptorProxy :id="row.zoneFk" /> - </span> - </template> - <template #column-totalWithVat="{ row }"> - <QChip - v-if="row.totalWithVat > 0 && row.totalWithVat < 50" - class="bg-warning" - dense - square + <template #body> + <VnTable + ref="tableRef" + :data-key="dataKey" + :create="{ + urlCreate: 'Tickets/new', + title: t('ticketList.createTicket'), + onDataSaved: ({ id }) => tableRef.redirect(id), + formInitialData: { clientId: null }, + }" + default-mode="table" + :columns="columns" + :user-params="userParams" + :right-search="false" + redirect="ticket" + v-model:selected="selectedRows" + :table="{ + 'row-key': 'id', + selection: 'multiple', + }" + data-cy="ticketListTable" > - {{ row.totalWithVat }} - </QChip> - </template> - <template #more-create-dialog="{ data }"> - <VnRow> - <VnSelect - url="Clients" - :fields="['id', 'name']" - :label="t('ticketList.client')" - v-model="data.clientId" - :options="clientsOptions" - option-value="id" - option-label="name" - hide-selected - required - @update:model-value="(client) => onClientSelected(data)" - :sort-by="'id ASC'" - > - <template #option="scope"> - <QItem v-bind="scope.itemProps"> - <QItemSection> - <QItemLabel> - {{ scope.opt.name }} - </QItemLabel> - <QItemLabel caption> - {{ `#${scope.opt.id}` }} - </QItemLabel> - </QItemSection> - </QItem> - </template> - </VnSelect> - </VnRow> - <VnRow> - <VnSelect - :label="t('basicData.address')" - v-model="data.addressId" - :options="addressesOptions" - option-value="id" - option-label="nickname" - hide-selected - map-options - required - :disable="!data.clientId" - :sort-by="'isActive DESC'" - @update:model-value="() => fetchAvailableAgencies(data)" - > - <template #option="scope"> - <QItem - v-bind="scope.itemProps" - :class="{ disabled: !scope.opt.isActive }" + <template #column-statusIcons="{ row }"> + <TicketProblems :row="row" /> + </template> + <template #column-salesPersonFk="{ row }"> + <span class="link" @click.stop> + {{ dashIfEmpty(row.userName) }} + <CustomerDescriptorProxy :id="row.salesPersonFk" /> + </span> + </template> + <template #column-shippedDate="{ row }"> + <span v-if="getDateColor(row.shipped)"> + <QChip :class="getDateColor(row.shipped)" dense square> + {{ toDate(row.shippedDate) }} + </QChip> + </span> + </template> + <template #column-nickname="{ row }"> + <span class="link" @click.stop> + {{ row.nickname }} + <CustomerDescriptorProxy :id="row.clientFk" /> + </span> + </template> + <template #column-addressNickname="{ row }"> + <span class="link" @click.stop> + {{ row.addressNickname }} + <CustomerDescriptorProxy :id="row.clientFk" /> + </span> + </template> + <template #column-stateFk="{ row }"> + <span v-if="row.refFk"> + <span class="link" @click.stop> + {{ row.refFk }} + <InvoiceOutDescriptorProxy :id="row.invoiceOutId" /> + </span> + </span> + <span v-else-if="getColor(row)"> + <QChip :class="getColor(row)" dense square> + {{ row.state }} + </QChip> + </span> + <span v-else> + {{ row.state }} + </span> + </template> + <template #column-zoneFk="{ row }"> + <span class="link" @click.stop> + {{ dashIfEmpty(row.zoneName) }} + <ZoneDescriptorProxy :id="row.zoneFk" /> + </span> + </template> + <template #column-totalWithVat="{ row }"> + <QChip + v-if="row.totalWithVat > 0 && row.totalWithVat < 50" + class="bg-warning" + dense + square + > + {{ row.totalWithVat }} + </QChip> + </template> + <template #more-create-dialog="{ data }"> + <VnRow> + <VnSelect + url="Clients" + :fields="['id', 'name']" + :label="t('ticketList.client')" + v-model="data.clientId" + :options="clientsOptions" + option-value="id" + option-label="name" + hide-selected + required + @update:model-value="(client) => onClientSelected(data)" + :sort-by="'id ASC'" > - <QItemSection style="min-width: min-content" avatar> - <QIcon - v-if=" - scope.opt.isActive && - selectedClient?.defaultAddressFk === scope.opt.id - " - size="sm" - color="grey" - name="star" - class="fill-icon" - /> - </QItemSection> - <QItemSection> - <QItemLabel - :class="{ - 'color-vn-label': !scope.opt?.isActive, - }" + <template #option="scope"> + <QItem v-bind="scope.itemProps"> + <QItemSection> + <QItemLabel> + {{ scope.opt.name }} + </QItemLabel> + <QItemLabel caption> + {{ `#${scope.opt.id}` }} + </QItemLabel> + </QItemSection> + </QItem> + </template> + </VnSelect> + </VnRow> + <VnRow> + <VnSelect + :label="t('basicData.address')" + v-model="data.addressId" + :options="addressesOptions" + option-value="id" + option-label="nickname" + hide-selected + map-options + required + :disable="!data.clientId" + :sort-by="'isActive DESC'" + @update:model-value="() => fetchAvailableAgencies(data)" + > + <template #option="scope"> + <QItem + v-bind="scope.itemProps" + :class="{ disabled: !scope.opt.isActive }" > - {{ - `${ - !scope.opt?.isActive - ? t('basicData.inactive') - : '' - } ` - }} - <span> - {{ scope.opt?.nickname }}: - {{ scope.opt?.street }}, {{ scope.opt?.city }} - </span> - </QItemLabel> - </QItemSection> - </QItem> - </template> - </VnSelect> - </VnRow> - <VnRow> - <div class="col"> - <VnInputDate - placeholder="dd-mm-aaa" - :label="t('globals.landed')" - v-model="data.landed" - @update:model-value="() => fetchAvailableAgencies(data)" - /> - </div> - </VnRow> - <VnRow> - <div class="col"> - <VnSelect - url="Warehouses" - :sort-by="['name']" - :label="t('globals.warehouse')" - v-model="data.warehouseId" - :options="warehousesOptions" - option-value="id" - option-label="name" - hide-selected - required - @update:model-value="() => fetchAvailableAgencies(data)" - /> - </div> - </VnRow> - <VnRow> - <div class="col"> - <VnSelect - :label="t('globals.agency')" - v-model="data.agencyModeId" - :options="agenciesOptions" - option-value="agencyModeFk" - option-label="agencyMode" - hide-selected - /> - </div> - </VnRow> + <QItemSection style="min-width: min-content" avatar> + <QIcon + v-if=" + scope.opt.isActive && + selectedClient?.defaultAddressFk === + scope.opt.id + " + size="sm" + color="grey" + name="star" + class="fill-icon" + /> + </QItemSection> + <QItemSection> + <QItemLabel + :class="{ + 'color-vn-label': !scope.opt?.isActive, + }" + > + {{ + `${ + !scope.opt?.isActive + ? t('basicData.inactive') + : '' + } ` + }} + <span> + {{ scope.opt?.nickname }}: + {{ scope.opt?.street }}, + {{ scope.opt?.city }} + </span> + </QItemLabel> + </QItemSection> + </QItem> + </template> + </VnSelect> + </VnRow> + <VnRow> + <div class="col"> + <VnInputDate + placeholder="dd-mm-aaa" + :label="t('globals.landed')" + v-model="data.landed" + @update:model-value="() => fetchAvailableAgencies(data)" + /> + </div> + </VnRow> + <VnRow> + <div class="col"> + <VnSelect + url="Warehouses" + :sort-by="['name']" + :label="t('globals.warehouse')" + v-model="data.warehouseId" + :options="warehousesOptions" + option-value="id" + option-label="name" + hide-selected + required + @update:model-value="() => fetchAvailableAgencies(data)" + /> + </div> + </VnRow> + <VnRow> + <div class="col"> + <VnSelect + :label="t('globals.agency')" + v-model="data.agencyModeId" + :options="agenciesOptions" + option-value="agencyModeFk" + option-label="agencyMode" + hide-selected + /> + </div> + </VnRow> + </template> + </VnTable> </template> - </VnTable> + </VnSection> <QPageSticky :offset="[20, 80]" style="z-index: 2"> <QBtn v-if="hasSelectedRows" diff --git a/src/router/modules/ticket.js b/src/router/modules/ticket.js index 6e407b88b57..600b64c1454 100644 --- a/src/router/modules/ticket.js +++ b/src/router/modules/ticket.js @@ -1,19 +1,12 @@ import { RouterView } from 'vue-router'; -export default { - name: 'Ticket', - path: '/ticket', +const ticketCard = { + name: 'TicketCard', + path: ':id', + component: () => import('src/pages/Ticket/Card/TicketCard.vue'), + redirect: { name: 'TicketSummary' }, meta: { - title: 'tickets', - icon: 'vn:ticket', - moduleName: 'Ticket', - keyBinding: 't', - }, - component: RouterView, - redirect: { name: 'TicketMain' }, - menus: { - main: ['TicketList', 'TicketAdvance', 'TicketWeekly', 'TicketFuture'], - card: [ + menu: [ 'TicketBasicData', 'TicketSale', 'TicketLog', @@ -32,21 +25,200 @@ export default { 'TicketSms', ], }, + children: [ + { + path: 'summary', + name: 'TicketSummary', + meta: { + title: 'summary', + icon: 'launch', + }, + component: () => import('src/pages/Ticket/Card/TicketSummary.vue'), + }, + { + path: 'basic-data', + name: 'TicketBasicData', + meta: { + title: 'basicData', + icon: 'vn:settings', + }, + component: () => + import('src/pages/Ticket/Card/BasicData/TicketBasicDataView.vue'), + }, + { + path: 'sale', + name: 'TicketSale', + meta: { + title: 'sale', + icon: 'vn:lines', + }, + component: () => import('src/pages/Ticket/Card/TicketSale.vue'), + }, + { + path: 'request', + name: 'TicketPurchaseRequest', + meta: { + title: 'purchaseRequest', + icon: 'vn:buyrequest', + }, + component: () => import('src/pages/Ticket/Card/TicketPurchaseRequest.vue'), + }, + { + path: 'tracking', + name: 'TicketTracking', + meta: { + title: 'tracking', + icon: 'vn:eye', + }, + component: () => import('src/pages/Ticket/Card/TicketTracking.vue'), + }, + { + path: 'log', + name: 'TicketLog', + meta: { + title: 'log', + icon: 'history', + }, + component: () => import('src/pages/Ticket/Card/TicketLog.vue'), + }, + { + path: 'observation', + name: 'TicketNotes', + meta: { + title: 'notes', + icon: 'vn:notes', + }, + component: () => import('src/pages/Ticket/Card/TicketNotes.vue'), + }, + { + path: 'picture', + name: 'TicketPicture', + meta: { + title: 'pictures', + icon: 'vn:photo', + }, + component: () => import('src/pages/Ticket/Card/TicketPicture.vue'), + }, + { + path: 'volume', + name: 'TicketVolume', + meta: { + title: 'volume', + icon: 'vn:volume', + }, + component: () => import('src/pages/Ticket/Card/TicketVolume.vue'), + }, + { + path: 'expedition', + name: 'TicketExpedition', + meta: { + title: 'expedition', + icon: 'vn:package', + }, + component: () => import('src/pages/Ticket/Card/TicketExpedition.vue'), + }, + { + path: 'service', + name: 'TicketService', + meta: { + title: 'services', + icon: 'vn:services', + }, + component: () => import('src/pages/Ticket/Card/TicketService.vue'), + }, + { + path: 'package', + name: 'TicketPackage', + meta: { + title: 'packages', + icon: 'vn:bucket', + }, + component: () => import('src/pages/Ticket/Card/TicketPackage.vue'), + }, + { + path: 'components', + name: 'TicketComponents', + meta: { + title: 'components', + icon: 'vn:components', + }, + component: () => import('src/pages/Ticket/Card/TicketComponents.vue'), + }, + + { + path: 'sale-tracking', + name: 'TicketSaleTracking', + meta: { + title: 'saleTracking', + icon: 'assignment', + }, + component: () => import('src/pages/Ticket/Card/TicketSaleTracking.vue'), + }, + { + path: 'dms', + name: 'TicketDms', + meta: { + title: 'dms', + icon: 'cloud_upload', + }, + component: () => import('src/pages/Ticket/Card/TicketDms.vue'), + }, + { + path: 'boxing', + name: 'TicketBoxing', + meta: { + title: 'boxing', + icon: 'science', + }, + component: () => import('src/pages/Ticket/Card/TicketBoxing.vue'), + }, + { + path: 'sms', + name: 'TicketSms', + meta: { + title: 'sms', + icon: 'sms', + }, + component: () => import('src/pages/Ticket/Card/TicketSms.vue'), + }, + ], +}; + +export default { + name: 'Ticket', + path: '/ticket', + meta: { + title: 'tickets', + icon: 'vn:ticket', + moduleName: 'Ticket', + keyBinding: 't', + menu: ['TicketList', 'TicketAdvance', 'TicketWeekly', 'TicketFuture'], + }, + component: RouterView, + redirect: { name: 'TicketMain' }, children: [ { name: 'TicketMain', path: '', - component: () => import('src/components/common/VnSectionMain.vue'), - redirect: { name: 'TicketList' }, + component: () => import('src/components/common/VnModule.vue'), + redirect: { name: 'TicketIndexMain' }, children: [ { - path: 'list', - name: 'TicketList', - meta: { - title: 'list', - icon: 'view_list', - }, + path: '', + name: 'TicketIndexMain', + redirect: { name: 'TicketList' }, component: () => import('src/pages/Ticket/TicketList.vue'), + children: [ + { + name: 'TicketList', + path: 'list', + meta: { + title: 'list', + icon: 'view_list', + }, + }, + ticketCard, + ], }, { path: 'create', @@ -86,170 +258,5 @@ export default { }, ], }, - { - name: 'TicketCard', - path: ':id', - component: () => import('src/pages/Ticket/Card/TicketCard.vue'), - redirect: { name: 'TicketSummary' }, - children: [ - { - path: 'summary', - name: 'TicketSummary', - meta: { - title: 'summary', - icon: 'launch', - }, - component: () => import('src/pages/Ticket/Card/TicketSummary.vue'), - }, - { - path: 'basic-data', - name: 'TicketBasicData', - meta: { - title: 'basicData', - icon: 'vn:settings', - }, - component: () => - import('src/pages/Ticket/Card/BasicData/TicketBasicDataView.vue'), - }, - { - path: 'sale', - name: 'TicketSale', - meta: { - title: 'sale', - icon: 'vn:lines', - }, - component: () => import('src/pages/Ticket/Card/TicketSale.vue'), - }, - { - path: 'request', - name: 'TicketPurchaseRequest', - meta: { - title: 'purchaseRequest', - icon: 'vn:buyrequest', - }, - component: () => - import('src/pages/Ticket/Card/TicketPurchaseRequest.vue'), - }, - { - path: 'tracking', - name: 'TicketTracking', - meta: { - title: 'tracking', - icon: 'vn:eye', - }, - component: () => import('src/pages/Ticket/Card/TicketTracking.vue'), - }, - { - path: 'log', - name: 'TicketLog', - meta: { - title: 'log', - icon: 'history', - }, - component: () => import('src/pages/Ticket/Card/TicketLog.vue'), - }, - { - path: 'observation', - name: 'TicketNotes', - meta: { - title: 'notes', - icon: 'vn:notes', - }, - component: () => import('src/pages/Ticket/Card/TicketNotes.vue'), - }, - { - path: 'picture', - name: 'TicketPicture', - meta: { - title: 'pictures', - icon: 'vn:photo', - }, - component: () => import('src/pages/Ticket/Card/TicketPicture.vue'), - }, - { - path: 'volume', - name: 'TicketVolume', - meta: { - title: 'volume', - icon: 'vn:volume', - }, - component: () => import('src/pages/Ticket/Card/TicketVolume.vue'), - }, - { - path: 'expedition', - name: 'TicketExpedition', - meta: { - title: 'expedition', - icon: 'vn:package', - }, - component: () => import('src/pages/Ticket/Card/TicketExpedition.vue'), - }, - { - path: 'service', - name: 'TicketService', - meta: { - title: 'services', - icon: 'vn:services', - }, - component: () => import('src/pages/Ticket/Card/TicketService.vue'), - }, - { - path: 'package', - name: 'TicketPackage', - meta: { - title: 'packages', - icon: 'vn:bucket', - }, - component: () => import('src/pages/Ticket/Card/TicketPackage.vue'), - }, - { - path: 'components', - name: 'TicketComponents', - meta: { - title: 'components', - icon: 'vn:components', - }, - component: () => import('src/pages/Ticket/Card/TicketComponents.vue'), - }, - - { - path: 'sale-tracking', - name: 'TicketSaleTracking', - meta: { - title: 'saleTracking', - icon: 'assignment', - }, - component: () => - import('src/pages/Ticket/Card/TicketSaleTracking.vue'), - }, - { - path: 'dms', - name: 'TicketDms', - meta: { - title: 'dms', - icon: 'cloud_upload', - }, - component: () => import('src/pages/Ticket/Card/TicketDms.vue'), - }, - { - path: 'boxing', - name: 'TicketBoxing', - meta: { - title: 'boxing', - icon: 'science', - }, - component: () => import('src/pages/Ticket/Card/TicketBoxing.vue'), - }, - { - path: 'sms', - name: 'TicketSms', - meta: { - title: 'sms', - icon: 'sms', - }, - component: () => import('src/pages/Ticket/Card/TicketSms.vue'), - }, - ], - }, ], }; From d29ecd0a2b570cc38d02afa52f3ff5bf64485f13 Mon Sep 17 00:00:00 2001 From: Javier Segarra <jsegarra@verdnatura.es> Date: Mon, 23 Dec 2024 09:47:00 +0000 Subject: [PATCH 28/34] fix: use value intead computedRef --- src/pages/Customer/components/CustomerSamplesCreate.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Customer/components/CustomerSamplesCreate.vue b/src/pages/Customer/components/CustomerSamplesCreate.vue index a75dfa1b2a7..665e136e41c 100644 --- a/src/pages/Customer/components/CustomerSamplesCreate.vue +++ b/src/pages/Customer/components/CustomerSamplesCreate.vue @@ -107,7 +107,7 @@ const setParams = (params) => { const getPreview = async () => { const params = { - recipientId: entityId, + recipientId: entityId.value, }; const validationMessage = validateMessage(); if (validationMessage) return notify(t(validationMessage), 'negative'); From bf41f338b70158f40045a0caf976589d8e5424e8 Mon Sep 17 00:00:00 2001 From: provira <provira@verdnatura.es> Date: Mon, 23 Dec 2024 12:13:58 +0100 Subject: [PATCH 29/34] feat: refs #7079 created VnLocation front test --- src/components/common/VnLocation.vue | 2 +- .../common/__tests__/VnLocation.spec.js | 100 ++++++++++++++++++ 2 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 src/components/common/__tests__/VnLocation.spec.js diff --git a/src/components/common/VnLocation.vue b/src/components/common/VnLocation.vue index a8840f24327..f5822218727 100644 --- a/src/components/common/VnLocation.vue +++ b/src/components/common/VnLocation.vue @@ -26,7 +26,7 @@ const locationProperties = [ (obj) => obj.country?.name, ]; -const formatLocation = (obj, properties) => { +const formatLocation = (obj, properties = locationProperties) => { const parts = properties.map((prop) => { if (typeof prop === 'string') { return obj[prop]; diff --git a/src/components/common/__tests__/VnLocation.spec.js b/src/components/common/__tests__/VnLocation.spec.js new file mode 100644 index 00000000000..920afced861 --- /dev/null +++ b/src/components/common/__tests__/VnLocation.spec.js @@ -0,0 +1,100 @@ +import { createWrapper } from 'app/test/vitest/helper'; +import VnLocation from 'components/common/VnLocation.vue'; +import { vi, afterEach, expect, it, beforeEach, describe } from 'vitest'; + +function buildComponent(data) { + return createWrapper(VnLocation, { + global: { + stubs: [''], + props: { + location: data + } + }, + }).vm; +} + +afterEach(() => { + vi.clearAllMocks(); +}); + +describe('formatLocation', () => { + let locationBase; + + beforeEach(() => { + locationBase = { + postcode: '46680', + city: 'Algemesi', + province: { name: 'Valencia' }, + country: { name: 'Spain' } + }; + }); + + it('should return the postcode, city, province and country', () => { + const location = { ...locationBase }; + const vm = buildComponent(location); + const parts = vm.formatLocation(location); + expect(parts).toEqual('46680, Algemesi(Valencia), Spain'); + }); + + it('should return the postcode and country', () => { + const location = { ...locationBase, city: undefined }; + const vm = buildComponent(location); + const parts = vm.formatLocation(location); + expect(parts).toEqual('46680, Spain'); + }); + + it('should return the city, province and country', () => { + const location = { ...locationBase, postcode: undefined }; + const vm = buildComponent(location); + const parts = vm.formatLocation(location); + expect(parts).toEqual('Algemesi(Valencia), Spain'); + }); + + it('should return the country', () => { + const location = { ...locationBase, postcode: undefined, city: undefined, province: undefined }; + const vm = buildComponent(location); + const parts = vm.formatLocation(location); + expect(parts).toEqual('Spain'); + }); +}); + +describe('showLabel', () => { + let locationBase; + + beforeEach(() => { + locationBase = { + code: '46680', + town: 'Algemesi', + province: 'Valencia', + country: 'Spain' + }; + }); + + it('should show the label with postcode, city, province and country', () => { + const location = { ...locationBase }; + const vm = buildComponent(location); + const label = vm.showLabel(location); + expect(label).toEqual('46680, Algemesi(Valencia), Spain'); + }); + + it('should show the label with postcode and country', () => { + const location = { ...locationBase, town: undefined }; + const vm = buildComponent(location); + const label = vm.showLabel(location); + expect(label).toEqual('46680, Spain'); + }); + + it('should show the label with city, province and country', () => { + const location = { ...locationBase, code: undefined }; + const vm = buildComponent(location); + const label = vm.showLabel(location); + expect(label).toEqual('Algemesi(Valencia), Spain'); + }); + + it('should show the label with country', () => { + const location = { ...locationBase, code: undefined, town: undefined, province: undefined }; + const vm = buildComponent(location); + const label = vm.showLabel(location); + expect(label).toEqual('Spain'); + }); +}); \ No newline at end of file From dd36b35bf7f6f8e64b95323efe7c71bd9b487a89 Mon Sep 17 00:00:00 2001 From: alexm <alexm@verdnatura.es> Date: Mon, 23 Dec 2024 17:56:09 +0100 Subject: [PATCH 30/34] fix: refs #8197 staticParams and redirect --- src/components/NavBar.vue | 1 + src/components/common/VnSection.vue | 11 ++++++++--- src/components/ui/VnPaginate.vue | 5 ++--- src/components/ui/VnSearchbar.vue | 6 ++++-- src/composables/useArrayData.js | 1 + src/pages/Account/AccountList.vue | 7 +++++-- src/pages/Customer/CustomerList.vue | 1 - .../integration/vnComponent/VnSearchBar.spec.js | 5 ++--- 8 files changed, 23 insertions(+), 14 deletions(-) diff --git a/src/components/NavBar.vue b/src/components/NavBar.vue index 9b0393489cb..08c2410f1e0 100644 --- a/src/components/NavBar.vue +++ b/src/components/NavBar.vue @@ -59,6 +59,7 @@ const pinnedModulesRef = ref(); 'no-visible': !stateQuery.isLoading().value, }" size="xs" + data-cy="loading-spinner" /> <QSpace /> <div id="searchbar" class="searchbar"></div> diff --git a/src/components/common/VnSection.vue b/src/components/common/VnSection.vue index e6afea4b6a3..e69e586b5df 100644 --- a/src/components/common/VnSection.vue +++ b/src/components/common/VnSection.vue @@ -34,15 +34,20 @@ const $props = defineProps({ type: Object, default: null, }, + redirect: { + type: Boolean, + default: true, + }, }); const sectionValue = computed(() => $props.section ?? $props.dataKey); - +let arrayData; onBeforeMount(() => { if ($props.dataKey) - useArrayData($props.dataKey, { + arrayData = useArrayData($props.dataKey, { searchUrl: 'table', ...$props.arrayDataProps, + navigate: $props.redirect, }); }); </script> @@ -63,12 +68,12 @@ onBeforeMount(() => { <VnTableFilter v-if="rightFilter && columns" :data-key="dataKey" + :array-data="arrayData" :columns="columns" /> </slot> </template> </RightMenu> - <slot name="body" v-if="sectionValue == $route.name" /> <RouterView v-else /> </template> diff --git a/src/components/ui/VnPaginate.vue b/src/components/ui/VnPaginate.vue index 42f558f89b8..a2ccd5d92d1 100644 --- a/src/components/ui/VnPaginate.vue +++ b/src/components/ui/VnPaginate.vue @@ -112,7 +112,6 @@ onMounted(async () => { onBeforeUnmount(() => { arrayData.resetPagination(); - arrayData.reset(['currentFilter', 'userParams', 'userFilter']); }); watch( @@ -142,7 +141,7 @@ const addFilter = async (filter, params) => { async function fetch(params) { useArrayData(props.dataKey, params); arrayData.resetPagination(); - await arrayData.fetch({ append: false, updateRouter: mounted.value }); + await arrayData.fetch({ append: false }); return emitStoreData(); } @@ -217,7 +216,7 @@ defineExpose({ <template> <div class="full-width"> <div - v-if="!props.autoLoad && !store.data && !isLoading" + v-if="!store.data && !store.data?.length && !isLoading" class="info-row q-pa-md text-center" > <h5> diff --git a/src/components/ui/VnSearchbar.vue b/src/components/ui/VnSearchbar.vue index 6a9de44cba7..4e284d8e45b 100644 --- a/src/components/ui/VnSearchbar.vue +++ b/src/components/ui/VnSearchbar.vue @@ -100,7 +100,9 @@ onMounted(() => { }); async function search() { - const staticParams = Object.entries(store.userParams); + const staticParams = Object.keys(store.userParams ?? {}).length + ? store.userParams + : store.defaultParams; arrayData.resetPagination(); const filter = { @@ -112,7 +114,7 @@ async function search() { if (!props.searchRemoveParams || !searchText.value) { filter.params = { - ...Object.fromEntries(staticParams), + ...staticParams, search: searchText.value, }; } diff --git a/src/composables/useArrayData.js b/src/composables/useArrayData.js index f4b30438af4..1a91cc50b50 100644 --- a/src/composables/useArrayData.js +++ b/src/composables/useArrayData.js @@ -64,6 +64,7 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) { store[option] = userOptions.keepOpts?.includes(option) ? Object.assign(defaultOpts, store[option]) : defaultOpts; + if (option === 'userParams') store.defaultParams = store[option]; } } } diff --git a/src/pages/Account/AccountList.vue b/src/pages/Account/AccountList.vue index 997e3104142..7004abcf161 100644 --- a/src/pages/Account/AccountList.vue +++ b/src/pages/Account/AccountList.vue @@ -1,10 +1,11 @@ <script setup> import { useI18n } from 'vue-i18n'; -import { computed } from 'vue'; +import { computed, ref } from 'vue'; import VnTable from 'components/VnTable/VnTable.vue'; import AccountSummary from './Card/AccountSummary.vue'; import { useSummaryDialog } from 'src/composables/useSummaryDialog'; import VnSection from 'src/components/common/VnSection.vue'; +import FetchData from 'src/components/FetchData.vue'; const { t } = useI18n(); const { viewSummary } = useSummaryDialog(); @@ -12,6 +13,7 @@ const filter = { include: { relation: 'role', scope: { fields: ['id', 'name'] } }, }; const dataKey = 'AccountList'; +const roles = ref([]); const columns = computed(() => [ { align: 'left', @@ -29,7 +31,7 @@ const columns = computed(() => [ component: 'select', name: 'roleFk', attrs: { - url: 'VnRoles', + options: roles, optionValue: 'id', optionLabel: 'name', }, @@ -103,6 +105,7 @@ function exprBuilder(param, value) { </script> <template> + <FetchData url="VnRoles" @on-fetch="(data) => (roles = data)" auto-load /> <VnSection :data-key="dataKey" :columns="columns" diff --git a/src/pages/Customer/CustomerList.vue b/src/pages/Customer/CustomerList.vue index 865287aeb38..b9b63208523 100644 --- a/src/pages/Customer/CustomerList.vue +++ b/src/pages/Customer/CustomerList.vue @@ -419,7 +419,6 @@ function handleLocation(data, location) { :columns="columns" redirect="customer" :right-search="false" - auto-load > <template #more-create-dialog="{ data }"> <VnSelect diff --git a/test/cypress/integration/vnComponent/VnSearchBar.spec.js b/test/cypress/integration/vnComponent/VnSearchBar.spec.js index b8621118cfc..885e5d6b3b0 100644 --- a/test/cypress/integration/vnComponent/VnSearchBar.spec.js +++ b/test/cypress/integration/vnComponent/VnSearchBar.spec.js @@ -7,10 +7,10 @@ describe('VnSearchBar', () => { beforeEach(() => { cy.viewport(1280, 720); cy.login('developer'); - cy.visit('#/customer/list'); + cy.visit('#/account/list'); }); - it('should redirect to customer summary page', () => { + it('should redirect to account summary page', () => { searchAndCheck('1', employeeId); searchAndCheck('salesPerson', salesPersonId); }); @@ -20,7 +20,6 @@ describe('VnSearchBar', () => { checkTableLength(2); cy.clearSearchbar(); - cy.writeSearchbar('0{enter}'); checkTableLength(0); }); From cfb35d23b436b37b0e71275d5fc25a435b40d19a Mon Sep 17 00:00:00 2001 From: alexm <alexm@verdnatura.es> Date: Mon, 23 Dec 2024 18:16:50 +0100 Subject: [PATCH 31/34] perf: refs #8197 perf --- src/pages/Account/AccountAcls.vue | 2 +- src/stores/useArrayDataStore.js | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/pages/Account/AccountAcls.vue b/src/pages/Account/AccountAcls.vue index b4eeb0648d4..6d357166121 100644 --- a/src/pages/Account/AccountAcls.vue +++ b/src/pages/Account/AccountAcls.vue @@ -151,7 +151,7 @@ const deleteAcl = async ({ id }) => { <template #body> <VnTable ref="tableRef" - data-key="AccountAcls" + :data-key="dataKey" :create="{ urlCreate: 'ACLs', title: 'Create ACL', diff --git a/src/stores/useArrayDataStore.js b/src/stores/useArrayDataStore.js index 47bc06fd6fe..e0d8b792932 100644 --- a/src/stores/useArrayDataStore.js +++ b/src/stores/useArrayDataStore.js @@ -56,7 +56,6 @@ export const useArrayDataStore = defineStore('arrayDataStore', () => { } return { - state, get, set, clear, From 17b178d9f13aa6ea2c588438d137a325e58a8ab9 Mon Sep 17 00:00:00 2001 From: Javier Segarra <jsegarra@verdnatura.es> Date: Tue, 24 Dec 2024 09:06:17 +0000 Subject: [PATCH 32/34] fix: orderBy priority --- src/pages/Claim/ClaimList.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Claim/ClaimList.vue b/src/pages/Claim/ClaimList.vue index d561a69f7f6..6b9fa77a094 100644 --- a/src/pages/Claim/ClaimList.vue +++ b/src/pages/Claim/ClaimList.vue @@ -131,7 +131,7 @@ const STATE_COLOR = { <VnTable data-key="ClaimList" url="Claims/filter" - :order="['priority ASC', 'created ASC']" + :order="['t.priority ASC', 'created ASC']" :columns="columns" redirect="claim" :right-search="false" From 9dabe11cd005711818adeaa0185100ca20d7af83 Mon Sep 17 00:00:00 2001 From: alexm <alexm@verdnatura.es> Date: Tue, 24 Dec 2024 12:02:58 +0100 Subject: [PATCH 33/34] fix(AccountList): use $refs --- src/pages/Account/AccountList.vue | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/Account/AccountList.vue b/src/pages/Account/AccountList.vue index 0a4d6df4ed8..c1c75fcee6a 100644 --- a/src/pages/Account/AccountList.vue +++ b/src/pages/Account/AccountList.vue @@ -152,12 +152,13 @@ function exprBuilder(param, value) { > <template #body> <VnTable + ref="tableRef" :data-key="dataKey" :columns="columns" :create="{ urlCreate: 'VnUsers', title: t('Create user'), - onDataSaved: ({ id }) => tableRef.redirect(id), + onDataSaved: ({ id }) => $refs.tableRef.redirect(id), formInitialData: {}, }" default-mode="table" From 89acb338a9ebbaf521e93ca39828c65af53cba1a Mon Sep 17 00:00:00 2001 From: provira <provira@verdnatura.es> Date: Tue, 24 Dec 2024 12:18:36 +0100 Subject: [PATCH 34/34] refactor: refs #7079 removed useless code --- .../common/__tests__/VnLocation.spec.js | 25 ++++++------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/src/components/common/__tests__/VnLocation.spec.js b/src/components/common/__tests__/VnLocation.spec.js index 920afced861..65fdae96099 100644 --- a/src/components/common/__tests__/VnLocation.spec.js +++ b/src/components/common/__tests__/VnLocation.spec.js @@ -5,7 +5,6 @@ import { vi, afterEach, expect, it, beforeEach, describe } from 'vitest'; function buildComponent(data) { return createWrapper(VnLocation, { global: { - stubs: [''], props: { location: data } @@ -32,29 +31,25 @@ describe('formatLocation', () => { it('should return the postcode, city, province and country', () => { const location = { ...locationBase }; const vm = buildComponent(location); - const parts = vm.formatLocation(location); - expect(parts).toEqual('46680, Algemesi(Valencia), Spain'); + expect(vm.formatLocation(location)).toEqual('46680, Algemesi(Valencia), Spain'); }); it('should return the postcode and country', () => { const location = { ...locationBase, city: undefined }; const vm = buildComponent(location); - const parts = vm.formatLocation(location); - expect(parts).toEqual('46680, Spain'); + expect(vm.formatLocation(location)).toEqual('46680, Spain'); }); it('should return the city, province and country', () => { const location = { ...locationBase, postcode: undefined }; const vm = buildComponent(location); - const parts = vm.formatLocation(location); - expect(parts).toEqual('Algemesi(Valencia), Spain'); + expect(vm.formatLocation(location)).toEqual('Algemesi(Valencia), Spain'); }); it('should return the country', () => { const location = { ...locationBase, postcode: undefined, city: undefined, province: undefined }; const vm = buildComponent(location); - const parts = vm.formatLocation(location); - expect(parts).toEqual('Spain'); + expect(vm.formatLocation(location)).toEqual('Spain'); }); }); @@ -73,28 +68,24 @@ describe('showLabel', () => { it('should show the label with postcode, city, province and country', () => { const location = { ...locationBase }; const vm = buildComponent(location); - const label = vm.showLabel(location); - expect(label).toEqual('46680, Algemesi(Valencia), Spain'); + expect(vm.showLabel(location)).toEqual('46680, Algemesi(Valencia), Spain'); }); it('should show the label with postcode and country', () => { const location = { ...locationBase, town: undefined }; const vm = buildComponent(location); - const label = vm.showLabel(location); - expect(label).toEqual('46680, Spain'); + expect(vm.showLabel(location)).toEqual('46680, Spain'); }); it('should show the label with city, province and country', () => { const location = { ...locationBase, code: undefined }; const vm = buildComponent(location); - const label = vm.showLabel(location); - expect(label).toEqual('Algemesi(Valencia), Spain'); + expect(vm.showLabel(location)).toEqual('Algemesi(Valencia), Spain'); }); it('should show the label with country', () => { const location = { ...locationBase, code: undefined, town: undefined, province: undefined }; const vm = buildComponent(location); - const label = vm.showLabel(location); - expect(label).toEqual('Spain'); + expect(vm.showLabel(location)).toEqual('Spain'); }); }); \ No newline at end of file