From bbf847e1a0650ab67e59f93ddd79050e12f4c739 Mon Sep 17 00:00:00 2001 From: alexm Date: Mon, 10 Jun 2024 09:19:42 +0200 Subject: [PATCH] feat(url): sepate filters --- src/components/VnTable/VnFilter.vue | 7 +- src/components/VnTable/VnTable.vue | 44 +++++- src/components/common/VnCard.vue | 11 +- src/components/common/VnSelect.vue | 1 + src/components/ui/VnFilterPanel.vue | 126 ++++++++---------- src/components/ui/VnPaginate.vue | 8 +- src/components/ui/VnSearchbar.vue | 27 ++-- src/composables/useArrayData.js | 68 ++++++---- src/composables/useRedirect.js | 25 ---- src/stores/useArrayDataStore.js | 2 + .../__tests__/composables/useRedirect.spec.js | 52 -------- 11 files changed, 165 insertions(+), 206 deletions(-) delete mode 100644 src/composables/useRedirect.js delete mode 100644 test/vitest/__tests__/composables/useRedirect.spec.js diff --git a/src/components/VnTable/VnFilter.vue b/src/components/VnTable/VnFilter.vue index d572b8be0..1d4ec6c1a 100644 --- a/src/components/VnTable/VnFilter.vue +++ b/src/components/VnTable/VnFilter.vue @@ -22,9 +22,13 @@ const $props = defineProps({ type: String, required: true, }, + searchUrl: { + type: String, + default: 'params', + }, }); const model = defineModel(); -const arrayData = useArrayData($props.dataKey); +const arrayData = useArrayData($props.dataKey, { searchUrl: $props.searchUrl }); const columnFilter = computed(() => $props.column?.columnFilter); const updateEvent = { 'update:modelValue': addFilter }; @@ -99,6 +103,7 @@ const components = { }; async function addFilter(value) { + value ||= undefined; if (value && typeof value === 'object') value = model.value; value = value === '' ? undefined : value; let field = columnFilter.value?.name ?? $props.column.name; diff --git a/src/components/VnTable/VnTable.vue b/src/components/VnTable/VnTable.vue index 93bcfdddb..35fb7a395 100644 --- a/src/components/VnTable/VnTable.vue +++ b/src/components/VnTable/VnTable.vue @@ -1,7 +1,7 @@ diff --git a/src/composables/useArrayData.js b/src/composables/useArrayData.js index 81a2bf01a..aabbfbc5b 100644 --- a/src/composables/useArrayData.js +++ b/src/composables/useArrayData.js @@ -23,8 +23,13 @@ export function useArrayData(key, userOptions) { store.skip = 0; const query = route.query; - if (query.params) { - store.userParams = JSON.parse(query.params); + const searchUrl = store.searchUrl; + if (query[searchUrl]) { + const params = JSON.parse(query[searchUrl]); + const filter = params?.filter; + delete params.filter; + store.userParams = { ...params, ...store.userParams }; + store.userFilter = { ...JSON.parse(filter), ...store.userFilter }; } }); @@ -41,13 +46,15 @@ export function useArrayData(key, userOptions) { 'userParams', 'userFilter', 'exprBuilder', + 'searchUrl', + 'navigate', ]; if (typeof userOptions === 'object') { for (const option in userOptions) { const isEmpty = userOptions[option] == null || userOptions[option] === ''; if (isEmpty || !allowedOptions.includes(option)) continue; - if (Object.prototype.hasOwnProperty.call(store, option)) { + if (Object.hasOwn(store, option)) { const defaultOpts = userOptions[option]; store[option] = userOptions.keepOpts?.includes(option) ? Object.assign(defaultOpts, store[option]) @@ -88,8 +95,8 @@ export function useArrayData(key, userOptions) { Object.assign(params, userParams); - store.isLoading = true; store.currentFilter = params; + store.isLoading = true; const response = await axios.get(store.url, { signal: canceller.signal, params, @@ -119,6 +126,10 @@ export function useArrayData(key, userOptions) { } } + function deleteOption(option) { + delete store[option]; + } + function cancelRequest() { if (canceller) { canceller.abort(); @@ -129,7 +140,7 @@ export function useArrayData(key, userOptions) { async function applyFilter({ filter, params }) { if (filter) store.userFilter = filter; store.filter = {}; - if (params) store.userParams = Object.assign({}, params); + if (params) store.userParams = { ...params }; const response = await fetch({ append: false }); return response; @@ -138,7 +149,7 @@ export function useArrayData(key, userOptions) { async function addFilter({ filter, params }) { if (filter) store.userFilter = Object.assign(store.userFilter, filter); - let userParams = Object.assign({}, store.userParams, params); + let userParams = { ...store.userParams, ...params }; userParams = sanitizerParams(userParams, store?.exprBuilder); store.userParams = userParams; @@ -163,9 +174,7 @@ export function useArrayData(key, userOptions) { delete store.userParams[param]; delete params[param]; if (store.filter?.where) { - const key = Object.keys( - exprBuilder && exprBuilder(param) ? exprBuilder(param) : param - ); + const key = Object.keys(exprBuilder ? exprBuilder(param) : param); if (key[0]) delete store.filter.where[key[0]]; if (Object.keys(store.filter.where).length === 0) { delete store.filter.where; @@ -190,22 +199,32 @@ export function useArrayData(key, userOptions) { } function updateStateParams() { - const query = {}; - if (store.order) query.order = store.order; - if (store.limit) query.limit = store.limit; - if (store.skip) query.skip = store.skip; - if (store.userParams && Object.keys(store.userParams).length !== 0) - query.params = store.userParams; - if (store.userFilter && Object.keys(store.userFilter).length !== 0) { - if (!query.params) query.params = {}; - query.params.filter = store.userFilter; - } - if (query.params) query.params = JSON.stringify(query.params); + const newUrl = { path: route.path, query: { ...(route.query ?? {}) } }; + newUrl.query[store.searchUrl] = JSON.stringify(store.currentFilter); - router.replace({ - path: route.path, - query, - }); + if (store.navigate) { + const { customRouteRedirectName, searchText } = store.navigate; + if (customRouteRedirectName) + return router.push({ + name: customRouteRedirectName, + params: { id: searchText }, + }); + const { matched: matches } = router.currentRoute.value; + const { path } = matches.at(-1); + + const to = + store?.data?.length === 1 + ? path.replace(/\/(list|:id)|-list/, `/${store.data[0].id}`) + : path.replace(/:id.*/, ''); + + if (route.path != to) { + store.userParams = {}; + store.userFilter = {}; + return router.push({ path: to }); + } + } + + router.replace(newUrl); } const totalRows = computed(() => (store.data && store.data.length) || 0); @@ -223,5 +242,6 @@ export function useArrayData(key, userOptions) { totalRows, updateStateParams, isLoading, + deleteOption, }; } diff --git a/src/composables/useRedirect.js b/src/composables/useRedirect.js deleted file mode 100644 index c1470718b..000000000 --- a/src/composables/useRedirect.js +++ /dev/null @@ -1,25 +0,0 @@ -import { useRouter } from 'vue-router'; - -export default function useRedirect() { - const router = useRouter(); - - const navigate = (data, { customRouteRedirectName, searchText }) => { - if (customRouteRedirectName) - return router.push({ - name: customRouteRedirectName, - params: { id: searchText }, - }); - - const { matched: matches } = router.currentRoute.value; - const { path } = matches.at(-1); - - const to = - data.length === 1 - ? path.replace(/\/(list|:id)|-list/, `/${data[0].id}`) - : path.replace(/:id.*/, ''); - - router.push({ path: to }); - }; - - return { navigate }; -} diff --git a/src/stores/useArrayDataStore.js b/src/stores/useArrayDataStore.js index 115c161dd..ebe32f8d0 100644 --- a/src/stores/useArrayDataStore.js +++ b/src/stores/useArrayDataStore.js @@ -21,6 +21,8 @@ export const useArrayDataStore = defineStore('arrayDataStore', () => { isLoading: false, userParamsChanged: false, exprBuilder: null, + searchUrl: 'params', + navigate: null, }; } diff --git a/test/vitest/__tests__/composables/useRedirect.spec.js b/test/vitest/__tests__/composables/useRedirect.spec.js deleted file mode 100644 index ce56189b9..000000000 --- a/test/vitest/__tests__/composables/useRedirect.spec.js +++ /dev/null @@ -1,52 +0,0 @@ -import { vi, describe, expect, it, beforeEach, beforeAll } from 'vitest'; -import useRedirect from 'src/composables/useRedirect'; -import { useRouter } from 'vue-router'; - -vi.mock('vue-router'); - -describe('useRedirect', () => { - useRouter.mockReturnValue({ - push: vi.fn(), - currentRoute: { - value: { - matched: [ - { path: '/' }, - { path: '/customer' }, - { path: '/customer/:id' }, - { path: '/customer/:id/basic-data' }, - ], - }, - }, - }); - const data = []; - let navigate; - let spy; - - beforeAll(() => { - const { navigate: navigateFn } = useRedirect(); - navigate = navigateFn; - spy = useRouter().push; - }); - - beforeEach(() => { - data.length = 0; - spy.mockReset(); - }); - - it('should redirect to list page if there are several results', async () => { - data.push({ id: 1, name: 'employee' }, { id: 2, name: 'boss' }); - navigate(data, {}); - expect(spy).toHaveBeenCalledWith({ path: '/customer/' }); - }); - - it('should redirect to list page if there is no results', async () => { - navigate(data, {}); - expect(spy).toHaveBeenCalledWith({ path: '/customer/' }); - }); - - it('should redirect to basic-data page if there is only one result', async () => { - data.push({ id: 1, name: 'employee' }); - navigate(data, {}); - expect(spy).toHaveBeenCalledWith({ path: '/customer/1/basic-data' }); - }); -});