refs #5835 fix conflicts
gitea/salix-front/pipeline/head This commit looks good Details

This commit is contained in:
Jorge Penadés 2023-11-28 12:08:20 +01:00
commit f75b81db71
9 changed files with 168 additions and 41 deletions

View File

@ -25,27 +25,17 @@ const pinnedModulesRef = ref();
<template>
<QHeader class="bg-dark" color="white" elevated>
<QToolbar class="q-py-sm q-px-md">
<QBtn
@click="stateStore.toggleLeftDrawer()"
icon="menu"
class="q-mr-sm"
round
dense
flat
>
<QToolbar
class="q-py-sm q-px-md"
:class="{ 'q-gutter-x-sm': !quasar.platform.is.mobile }"
>
<QBtn @click="stateStore.toggleLeftDrawer()" icon="menu" round dense flat>
<QTooltip bottom anchor="bottom right">
{{ t('globals.collapseMenu') }}
</QTooltip>
</QBtn>
<RouterLink to="/">
<QBtn
class="q-ml-xs"
color="primary"
flat
round
v-if="!quasar.platform.is.mobile"
>
<QBtn color="primary" flat round v-if="!quasar.platform.is.mobile">
<QAvatar square size="md">
<QImg
src="~/assets/salix_icon.svg"

View File

@ -19,6 +19,10 @@ const $props = defineProps({
type: Array,
default: () => [],
},
isClearable: {
type: Boolean,
default: true,
},
});
const { optionLabel, options } = toRefs($props);
const myOptions = ref([]);
@ -93,7 +97,7 @@ const value = computed({
fill-input
ref="vnSelectRef"
>
<template #append>
<template v-if="isClearable" #append>
<QIcon name="close" @click.stop="value = null" class="cursor-pointer" />
</template>
<template v-for="(_, slotName) in $slots" #[slotName]="slotData">

View File

@ -41,15 +41,11 @@ onMounted(() => {
const isLoading = ref(false);
async function search() {
for (const param in userParams.value) {
if (userParams.value[param] === '' || userParams.value[param] === null) {
delete userParams.value[param];
delete store.userParams[param];
}
}
const params = { ...userParams.value };
isLoading.value = true;
await arrayData.addFilter({ params });
const params = { ...userParams.value };
const { params: newParams } = await arrayData.addFilter({ params });
userParams.value = newParams;
if (!props.showAll && !Object.values(params).length) store.data = [];
isLoading.value = false;
@ -78,10 +74,11 @@ async function clearFilters() {
const tags = computed(() => {
const params = [];
for (const param in store.userParams) {
for (const param in userParams.value) {
if (!userParams.value[param]) continue;
params.push({
label: param,
value: store.userParams[param],
value: userParams.value[param],
});
}
@ -89,8 +86,7 @@ const tags = computed(() => {
});
async function remove(key) {
delete userParams.value[key];
delete store.userParams[key];
userParams.value[key] = null;
await search();
}
@ -200,7 +196,7 @@ function formatValue(value) {
</template>
<i18n>
es:
es:
No filters applied: No se han aplicado filtros
Applied filters: Filtros aplicados
Remove filters: Eliminar filtros

View File

@ -50,6 +50,10 @@ const props = defineProps({
type: Boolean,
default: true,
},
exprBuilder: {
type: Function,
default: null,
},
});
const emit = defineEmits(['onFetch', 'onPaginate']);
@ -68,6 +72,7 @@ const arrayData = useArrayData(props.dataKey, {
limit: props.limit,
order: props.order,
userParams: props.userParams,
exprBuilder: props.exprBuilder,
});
const store = arrayData.store;

View File

@ -2,6 +2,7 @@ import { onMounted, ref, 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();
@ -43,6 +44,7 @@ export function useArrayData(key, userOptions) {
'skip',
'userParams',
'userFilter',
'exprBuilder',
];
if (typeof userOptions === 'object') {
for (const option in userOptions) {
@ -68,16 +70,27 @@ export function useArrayData(key, userOptions) {
skip: store.skip,
};
Object.assign(filter, store.userFilter);
Object.assign(store.filter, filter);
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);
Object.assign(store.filter, filter);
const params = {
filter: JSON.stringify(store.filter),
};
Object.assign(params, store.userParams);
Object.assign(params, userParams);
store.isLoading = true;
const response = await axios.get(store.url, {
signal: canceller.signal,
params,
@ -126,9 +139,30 @@ export function useArrayData(key, userOptions) {
async function addFilter({ filter, params }) {
if (filter) store.userFilter = Object.assign(store.userFilter, filter);
if (params) store.userParams = Object.assign(store.userParams, params);
let userParams = Object.assign({}, store.userParams, params);
userParams = sanitizerParams(userParams, store?.exprBuilder);
store.userParams = userParams;
await fetch({ append: false });
return { filter, params };
}
function sanitizerParams(params) {
for (const param in params) {
if (params[param] === '' || params[param] === null) {
delete store.userParams[param];
delete params[param];
if (store.filter?.where) {
delete store.filter.where[Object.keys(store?.exprBuilder(param))[0]];
if (Object.keys(store.filter.where).length === 0) {
delete store.filter.where;
}
}
}
}
return params;
}
async function loadMore() {

View File

@ -0,0 +1,94 @@
/**
* Passes a loopback fields filter to an object.
*
* @param {Object} fields The fields object or array
* @return {Object} The fields as object
*/
function fieldsToObject(fields) {
let fieldsObj = {};
if (Array.isArray(fields)) {
for (let field of fields) fieldsObj[field] = true;
} else if (typeof fields == 'object') {
for (let field in fields) {
if (fields[field]) fieldsObj[field] = true;
}
}
return fieldsObj;
}
/**
* Merges two loopback fields filters.
*
* @param {Object|Array} src The source fields
* @param {Object|Array} dst The destination fields
* @return {Array} The merged fields as an array
*/
function mergeFields(src, dst) {
let fields = {};
Object.assign(fields, fieldsToObject(src), fieldsToObject(dst));
return Object.keys(fields);
}
/**
* Merges two loopback where filters.
*
* @param {Object|Array} src The source where
* @param {Object|Array} dst The destination where
* @return {Array} The merged wheres
*/
function mergeWhere(src, dst) {
let and = [];
if (src) and.push(src);
if (dst) and.push(dst);
return simplifyOperation(and, 'and');
}
/**
* Merges two loopback filters returning the merged filter.
*
* @param {Object} src The source filter
* @param {Object} dst The destination filter
* @return {Object} The result filter
*/
function mergeFilters(src, dst) {
let res = Object.assign({}, dst);
if (!src) return res;
if (src.fields) res.fields = mergeFields(src.fields, res.fields);
if (src.where) res.where = mergeWhere(res.where, src.where);
if (src.include) res.include = src.include;
if (src.order) res.order = src.order;
if (src.limit) res.limit = src.limit;
if (src.offset) res.offset = src.offset;
if (src.skip) res.skip = src.skip;
return res;
}
function simplifyOperation(operation, operator) {
switch (operation.length) {
case 0:
return undefined;
case 1:
return operation[0];
default:
return { [operator]: operation };
}
}
function buildFilter(params, builderFunc) {
let and = [];
for (let param in params) {
let value = params[param];
if (value == null) continue;
let expr = builderFunc(param, value);
if (expr) and.push(expr);
}
return simplifyOperation(and, 'and');
}
export { fieldsToObject, mergeFields, mergeWhere, mergeFilters, buildFilter };

View File

@ -3,6 +3,7 @@ import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import FetchData from 'components/FetchData.vue';
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
import VnSelectFilter from 'components/common/VnSelectFilter.vue';
const { t } = useI18n();
const props = defineProps({
@ -60,7 +61,7 @@ const states = ref();
<QSkeleton type="QInput" class="full-width" />
</QItemSection>
<QItemSection v-if="workers">
<QSelect
<VnSelectFilter
:label="t('Salesperson')"
v-model="params.salesPersonFk"
@update:model-value="searchFn()"
@ -79,7 +80,7 @@ const states = ref();
<QSkeleton type="QInput" class="full-width" />
</QItemSection>
<QItemSection v-if="workers">
<QSelect
<VnSelectFilter
:label="t('Attender')"
v-model="params.attenderFk"
@update:model-value="searchFn()"
@ -98,7 +99,7 @@ const states = ref();
<QSkeleton type="QInput" class="full-width" />
</QItemSection>
<QItemSection v-if="workers">
<QSelect
<VnSelectFilter
:label="t('Responsible')"
v-model="params.claimResponsibleFk"
@update:model-value="searchFn()"
@ -117,7 +118,7 @@ const states = ref();
<QSkeleton type="QInput" class="full-width" />
</QItemSection>
<QItemSection v-if="states">
<QSelect
<VnSelectFilter
:label="t('State')"
v-model="params.claimStateFk"
@update:model-value="searchFn()"

View File

@ -3,6 +3,7 @@ import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import FetchData from 'components/FetchData.vue';
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
import VnSelectFilter from 'components/common/VnSelectFilter.vue';
const { t } = useI18n();
const props = defineProps({
@ -63,7 +64,7 @@ const zones = ref();
<QSkeleton type="QInput" class="full-width" />
</QItemSection>
<QItemSection v-if="workers">
<QSelect
<VnSelectFilter
:label="t('Salesperson')"
v-model="params.salesPersonFk"
@update:model-value="searchFn()"
@ -82,7 +83,7 @@ const zones = ref();
<QSkeleton type="QInput" class="full-width" />
</QItemSection>
<QItemSection v-if="provinces">
<QSelect
<VnSelectFilter
:label="t('Province')"
v-model="params.provinceFk"
@update:model-value="searchFn()"
@ -91,6 +92,7 @@ const zones = ref();
option-label="name"
emit-value
map-options
:input-debounce="0"
/>
</QItemSection>
</QItem>
@ -124,7 +126,7 @@ const zones = ref();
<QSkeleton type="QInput" class="full-width" />
</QItemSection>
<QItemSection v-if="zones">
<QSelect
<VnSelectFilter
:label="t('Zone')"
v-model="params.zoneFk"
@update:model-value="searchFn()"

View File

@ -19,6 +19,7 @@ export const useArrayDataStore = defineStore('arrayDataStore', () => {
order: '',
data: ref(),
isLoading: false,
exprBuilder: null,
};
}