import { onMounted, computed } from 'vue'; import { useRouter, useRoute } from 'vue-router'; import axios from 'axios'; import { useArrayDataStore } from 'stores/useArrayDataStore'; import { buildFilter } from 'filters/filterPanel'; const arrayDataStore = useArrayDataStore(); export function useArrayData(key = useRoute().meta.moduleName, userOptions) { if (!key) throw new Error('ArrayData: A key is required to use this composable'); if (!arrayDataStore.get(key)) arrayDataStore.set(key); const store = arrayDataStore.get(key); const route = useRoute(); const router = useRouter(); let canceller = null; onMounted(() => { setOptions(); reset(['skip']); const query = route.query; const searchUrl = store.searchUrl; if (query[searchUrl]) { const params = JSON.parse(query[searchUrl]); const filter = params?.filter && JSON.parse(params?.filter ?? '{}'); delete params.filter; store.userParams = { ...params, ...store.userParams }; store.userFilter = { ...filter, ...store.userFilter }; if (filter?.order) store.order = filter.order; } }); if (key && userOptions) setOptions(); function setOptions() { const allowedOptions = [ 'url', 'filter', 'where', 'order', 'limit', 'skip', '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.hasOwn(store, option)) { const defaultOpts = userOptions[option]; store[option] = userOptions.keepOpts?.includes(option) ? Object.assign(defaultOpts, store[option]) : defaultOpts; } } } } async function fetch({ append = false, updateRouter = true }) { if (!store.url) return; cancelRequest(); canceller = new AbortController(); const filter = { limit: store.limit, }; let exprFilter; let userParams = { ...store.userParams }; if (store?.exprBuilder) { const where = buildFilter(userParams, (param, value) => { const res = store.exprBuilder(param, value); if (res) delete userParams[param]; return res; }); exprFilter = where ? { where } : null; } Object.assign(filter, store.userFilter, exprFilter); 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, userParams); params.filter.skip = store.skip; if (store.order && store.order.length) params.filter.order = store.order; else delete params.filter.order; params.filter = JSON.stringify(params.filter); store.currentFilter = params; store.isLoading = true; const response = await axios.get(store.url, { signal: canceller.signal, params, }); const { limit } = filter; store.hasMoreData = limit && response.data.length >= limit; if (append) { if (!store.data) store.data = []; for (const row of response.data) store.data.push(row); } else { store.data = response.data; if (!document.querySelectorAll('[role="dialog"]').length) updateRouter && updateStateParams(); } store.isLoading = false; canceller = null; return response; } function destroy() { if (arrayDataStore.get(key)) { arrayDataStore.clear(key); } } function deleteOption(option) { delete store[option]; } function reset(opts = []) { if (arrayDataStore.get(key)) arrayDataStore.reset(key, opts); } function cancelRequest() { if (canceller) { canceller.abort(); canceller = null; } } async function applyFilter({ filter, params }) { if (filter) store.userFilter = filter; store.filter = {}; if (params) store.userParams = { ...params }; const response = await fetch({}); return response; } async function addFilter({ filter, params }) { if (filter) store.filter = filter; let userParams = { ...store.userParams, ...params }; userParams = sanitizerParams(userParams, store?.exprBuilder); store.userParams = userParams; reset(['skip', 'filter.skip', 'page']); await fetch({}); return { filter, params }; } async function addFilterWhere(where) { const storedFilter = { ...store.filter }; if (!storedFilter?.where) storedFilter.where = {}; where = { ...storedFilter.where, ...where }; await addFilter({ filter: { where } }); } async function addOrder(field, direction = 'ASC') { const newOrder = field + ' ' + direction; let order = store.order || []; if (typeof order == 'string') order = [order]; let index = order.findIndex((o) => o.split(' ')[0] === field); if (index > -1) { order[index] = newOrder; } else { index = order.length; order.push(newOrder); } store.order = order; reset(['skip', 'filter.skip', 'page']); fetch({}); index++; return { index, order }; } async function deleteOrder(field) { let order = store.order ?? []; if (typeof order == 'string') order = [order]; const index = order.findIndex((o) => o.split(' ')[0] === field); if (index > -1) order.splice(index, 1); store.order = order; fetch({}); } function sanitizerParams(params, exprBuilder) { for (const param in params) { if (params[param] === '' || params[param] === null) { delete store.userParams[param]; delete params[param]; if (store.filter?.where) { let key; if (exprBuilder) { const result = exprBuilder(param); if (result !== undefined && result !== null) key = Object.keys(result); } else { if (typeof param === 'object' && param !== null) key = Object.keys(param); } if (key && key[0]) { delete store.filter.where[key[0]]; if (Object.keys(store.filter.where).length === 0) { delete store.filter.where; } } } } } return params; } async function loadMore() { if (!store.hasMoreData) return; store.skip = store.limit * store.page; store.page += 1; await fetch({ append: true }); } async function refresh() { if (Object.values(store.userParams).length) await fetch({}); } function updateStateParams() { const newUrl = { path: route.path, query: { ...(route.query ?? {}) } }; newUrl.query[store.searchUrl] = JSON.stringify(store.currentFilter); 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) { const pushUrl = { path: to }; if (to.endsWith('/list') || to.endsWith('/')) pushUrl.query = newUrl.query; destroy(); return router.push(pushUrl); } } router.replace(newUrl); } const totalRows = computed(() => (store.data && store.data.length) || 0); const isLoading = computed(() => store.isLoading || false); return { fetch, applyFilter, addFilter, addFilterWhere, addOrder, deleteOrder, refresh, destroy, loadMore, store, totalRows, updateStateParams, isLoading, deleteOption, reset, }; }