diff --git a/package.json b/package.json index 39d49519b..b5e62af11 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "salix-front", - "version": "24.52.0", + "version": "25.02.0", "description": "Salix frontend", "productName": "Salix", "author": "Verdnatura", diff --git a/test/vitest/__tests__/boot/axios.spec.js b/src/boot/__tests__/axios.spec.js similarity index 97% rename from test/vitest/__tests__/boot/axios.spec.js rename to src/boot/__tests__/axios.spec.js index 19d396ec5..b3b6f98c6 100644 --- a/test/vitest/__tests__/boot/axios.spec.js +++ b/src/boot/__tests__/axios.spec.js @@ -1,4 +1,3 @@ -import { Notify } from 'quasar'; import { onRequest, onResponseError } from 'src/boot/axios'; import { describe, expect, it, vi } from 'vitest'; @@ -27,6 +26,7 @@ describe('Axios boot', () => { expect(resultConfig).toEqual( expect.objectContaining({ headers: { + 'Accept-Language': 'en-US', Authorization: 'DEFAULT_TOKEN', }, }) diff --git a/src/boot/axios.js b/src/boot/axios.js index aee38e887..3f9fadee5 100644 --- a/src/boot/axios.js +++ b/src/boot/axios.js @@ -3,12 +3,12 @@ import { useSession } from 'src/composables/useSession'; import { Router } from 'src/router'; import useNotify from 'src/composables/useNotify.js'; import { useStateQueryStore } from 'src/stores/useStateQueryStore'; +import { i18n } from 'src/boot/i18n'; const session = useSession(); const { notify } = useNotify(); const stateQuery = useStateQueryStore(); const baseUrl = '/api/'; - axios.defaults.baseURL = baseUrl; const axiosNoError = axios.create({ baseURL: baseUrl }); @@ -16,6 +16,7 @@ const onRequest = (config) => { const token = session.getToken(); if (token.length && !config.headers.Authorization) { config.headers.Authorization = token; + config.headers['Accept-Language'] = i18n.global.locale.value; } stateQuery.add(config); return config; diff --git a/src/boot/i18n.js b/src/boot/i18n.js index b23b6d5fd..85d0772a3 100644 --- a/src/boot/i18n.js +++ b/src/boot/i18n.js @@ -1,9 +1,11 @@ import { boot } from 'quasar/wrappers'; import { createI18n } from 'vue-i18n'; import messages from 'src/i18n'; +import { useState } from 'src/composables/useState'; +const user = useState().getUser(); const i18n = createI18n({ - locale: navigator.language || navigator.userLanguage, + locale: user.value.lang || navigator.language || navigator.userLanguage, fallbackLocale: 'en', globalInjection: true, messages, diff --git a/src/components/LeftMenu.vue b/src/components/LeftMenu.vue index 31ad9ebed..7a882e56c 100644 --- a/src/components/LeftMenu.vue +++ b/src/components/LeftMenu.vue @@ -92,13 +92,13 @@ function findMatches(search, item) { } function addChildren(module, route, parent) { - if (route.menus) { - const mainMenus = route.menus[props.source]; - const matches = findMatches(mainMenus, route); + const menus = route?.meta?.menu ?? route?.menus?.[props.source]; //backwards compatible + if (!menus) return; - for (const child of matches) { - navigation.addMenuItem(module, child, parent); - } + const matches = findMatches(menus, route); + + for (const child of matches) { + navigation.addMenuItem(module, child, parent); } } @@ -122,16 +122,26 @@ function getRoutes() { if (props.source === 'card') { const currentRoute = route.matched[1]; const currentModule = toLowerCamel(currentRoute.name); - const moduleDef = routes.find( + let moduleDef = routes.find( (route) => toLowerCamel(route.name) === currentModule ); 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/NavBar.vue b/src/components/NavBar.vue index 9b0393489..ef5bdc6ac 100644 --- a/src/components/NavBar.vue +++ b/src/components/NavBar.vue @@ -17,12 +17,10 @@ const stateQuery = useStateQueryStore(); const state = useState(); const user = state.getUser(); const appName = 'Lilium'; +const pinnedModulesRef = ref(); onMounted(() => stateStore.setMounted()); - -const pinnedModulesRef = ref(); - - - -en: - Go to Salix: Go to Salix -es: - Go to Salix: Ir a Salix - diff --git a/src/components/UserPanel.vue b/src/components/UserPanel.vue index 810f63044..a0ef73a1f 100644 --- a/src/components/UserPanel.vue +++ b/src/components/UserPanel.vue @@ -87,10 +87,10 @@ async function saveDarkMode(value) { async function saveLanguage(value) { const query = `/VnUsers/${user.value.id}`; try { - await axios.patch(query, { - lang: value, - }); + await axios.patch(query, { lang: value }); + user.value.lang = value; + useState().setUser(user.value); onDataSaved(); } catch (error) { onDataError(); diff --git a/src/components/VnTable/VnFilter.vue b/src/components/VnTable/VnFilter.vue index 999133130..426f5c716 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 e78efa852..7886d0567 100644 --- a/src/components/VnTable/VnTable.vue +++ b/src/components/VnTable/VnTable.vue @@ -1,20 +1,21 @@ + diff --git a/test/vitest/__tests__/components/VnTable.spec.js b/src/components/VnTable/__tests__/VnTable.spec.js similarity index 82% rename from test/vitest/__tests__/components/VnTable.spec.js rename to src/components/VnTable/__tests__/VnTable.spec.js index 162df727d..74ba06987 100644 --- a/test/vitest/__tests__/components/VnTable.spec.js +++ b/src/components/VnTable/__tests__/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 = [])); diff --git a/test/vitest/__tests__/components/common/CrudModel.spec.js b/src/components/__tests__/CrudModel.spec.js similarity index 100% rename from test/vitest/__tests__/components/common/CrudModel.spec.js rename to src/components/__tests__/CrudModel.spec.js diff --git a/test/vitest/__tests__/components/Leftmenu.spec.js b/src/components/__tests__/Leftmenu.spec.js similarity index 100% rename from test/vitest/__tests__/components/Leftmenu.spec.js rename to src/components/__tests__/Leftmenu.spec.js diff --git a/src/components/common/RightMenu.vue b/src/components/common/RightMenu.vue index 3aa1891f9..32dc2874d 100644 --- a/src/components/common/RightMenu.vue +++ b/src/components/common/RightMenu.vue @@ -2,7 +2,11 @@ import { ref, onMounted, useSlots } from 'vue'; import { useI18n } from 'vue-i18n'; import { useStateStore } from 'stores/useStateStore'; +import { useQuasar } from 'quasar'; +const { t } = useI18n(); +const quasar = useQuasar(); +const stateStore = useStateStore(); const slots = useSlots(); const hasContent = ref(false); const rightPanel = ref(null); @@ -11,7 +15,6 @@ onMounted(() => { rightPanel.value = document.querySelector('#right-panel'); if (!rightPanel.value) return; - // Check if there's content to display const observer = new MutationObserver(() => { hasContent.value = rightPanel.value.childNodes.length; }); @@ -21,12 +24,9 @@ onMounted(() => { childList: true, attributes: true, }); - - if (!slots['right-panel'] && !hasContent.value) stateStore.rightDrawer = false; + if ((!slots['right-panel'] && !hasContent.value) || quasar.platform.is.mobile) + stateStore.rightDrawer = false; }); - -const { t } = useI18n(); -const stateStore = useStateStore(); - es: Open date: Abrir fecha diff --git a/src/components/common/VnInputNumber.vue b/src/components/common/VnInputNumber.vue index 1cad6c245..165cfae3d 100644 --- a/src/components/common/VnInputNumber.vue +++ b/src/components/common/VnInputNumber.vue @@ -1,13 +1,28 @@ - diff --git a/src/components/common/VnInputTime.vue b/src/components/common/VnInputTime.vue index 6724c00b5..b4b246618 100644 --- a/src/components/common/VnInputTime.vue +++ b/src/components/common/VnInputTime.vue @@ -80,7 +80,7 @@ function dateToTime(newDate) { :class="{ required: isRequired }" style="min-width: 100px" :rules="mixinRules" - @click="isPopupOpen = false" + @click="isPopupOpen = !isPopupOpen" type="time" hide-bottom-space > @@ -100,12 +100,6 @@ function dateToTime(newDate) { isPopupOpen = false; " /> - import { useStateStore } from 'stores/useStateStore'; -import LeftMenu from 'components/LeftMenu.vue'; -import { onMounted } from 'vue'; +import { onMounted, ref } from 'vue'; import { useQuasar } from 'quasar'; +import LeftMenu from '../LeftMenu.vue'; const stateStore = useStateStore(); const $props = defineProps({ @@ -14,12 +14,30 @@ const $props = defineProps({ onMounted( () => (stateStore.leftDrawer = useQuasar().screen.gt.xs ? $props.leftDrawer : false) ); + +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(); + } +}); diff --git a/test/vitest/__tests__/components/common/VnChangePassword.spec.js b/src/components/common/__tests__/VnChangePassword.spec.js similarity index 100% rename from test/vitest/__tests__/components/common/VnChangePassword.spec.js rename to src/components/common/__tests__/VnChangePassword.spec.js diff --git a/test/vitest/__tests__/components/common/VnDiscount.spec.js b/src/components/common/__tests__/VnDiscount.spec.js similarity index 100% rename from test/vitest/__tests__/components/common/VnDiscount.spec.js rename to src/components/common/__tests__/VnDiscount.spec.js diff --git a/test/vitest/__tests__/components/common/VnDmsList.spec.js b/src/components/common/__tests__/VnDmsList.spec.js similarity index 100% rename from test/vitest/__tests__/components/common/VnDmsList.spec.js rename to src/components/common/__tests__/VnDmsList.spec.js diff --git a/test/vitest/__tests__/components/common/VnLog.spec.js b/src/components/common/__tests__/VnLog.spec.js similarity index 100% rename from test/vitest/__tests__/components/common/VnLog.spec.js rename to src/components/common/__tests__/VnLog.spec.js diff --git a/test/vitest/__tests__/components/common/VnSmsDialog.spec.js b/src/components/common/__tests__/VnSmsDialog.spec.js similarity index 100% rename from test/vitest/__tests__/components/common/VnSmsDialog.spec.js rename to src/components/common/__tests__/VnSmsDialog.spec.js diff --git a/src/components/ui/CardSummary.vue b/src/components/ui/CardSummary.vue index cf8859a35..8395dfd73 100644 --- a/src/components/ui/CardSummary.vue +++ b/src/components/ui/CardSummary.vue @@ -1,10 +1,10 @@ - - diff --git a/src/components/ui/VnConfirm.vue b/src/components/ui/VnConfirm.vue index 0b1913383..a02b56bdb 100644 --- a/src/components/ui/VnConfirm.vue +++ b/src/components/ui/VnConfirm.vue @@ -98,6 +98,7 @@ function cancel() { /> -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'; +import { useRoute } from 'vue-router'; -const { t } = useI18n(); +const { t, te } = useI18n(); +const route = useRoute(); const $props = defineProps({ modelValue: { type: Object, @@ -55,6 +57,10 @@ const $props = defineProps({ type: Boolean, default: true, }, + arrayData: { + type: Object, + default: null, + }, }); const emit = defineEmits([ @@ -67,52 +73,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 +96,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'); @@ -139,7 +111,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) @@ -149,9 +121,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 = []; @@ -214,20 +185,13 @@ 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; -} +const getLocale = (label) => { + const param = label.split('.').at(-1); + const globalLocale = `globals.params.${param}`; + if (te(globalLocale)) return t(globalLocale); + else if (te(t(`params.${param}`))); + else return t(`${route.meta.moduleName}.params.${param}`); +};