refs #6062 feat(arrayData): support exprBuilder #112

Merged
alexm merged 2 commits from 6062-exprBuilder into dev 2023-11-27 12:23:33 +00:00
9 changed files with 168 additions and 41 deletions

View File

@ -25,27 +25,17 @@ const pinnedModulesRef = ref();
Review

Esto es pq me daba toc, estaba mal alineado

Esto es pq me daba toc, estaba mal alineado
<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

@ -15,6 +15,10 @@ const $props = defineProps({
type: String,
default: '',
},
isClearable: {
type: Boolean,
default: true,
},
});
const { optionLabel, options } = toRefs($props);
const myOptions = ref([]);
@ -84,7 +88,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) {
Review

Se puede reemplazar el for por esto

Object.entries(userParams.value).filter(([k,v]) => v).reduce((acc,[label, value])=>( {...acc, [label]: value} ),{})

Se puede reemplazar el for por esto ` Object.entries(userParams.value).filter(([k,v]) => v).reduce((acc,[label, value])=>( {...acc, [label]: value} ),{}) `
Review

poco legible no?

poco legible no?
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,
alexm marked this conversation as resolved
Review

si coinciden todas las claves con el valor de props, quizas se puede hacer

const arrayData = useArrayData(props.dataKey, {...props} });

si coinciden todas las claves con el valor de props, quizas se puede hacer `const arrayData = useArrayData(props.dataKey, {...props} });`
Review

De hecho no es un allowProperties

De hecho no es un allowProperties
});
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) {
jsegarra marked this conversation as resolved
Review

Duda, esto es para eliminar los parámetros de búsqueda que tengan valor vacío o null

Duda, esto es para eliminar los parámetros de búsqueda que tengan valor vacío o null
Review

exacto

exacto
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) {
alexm marked this conversation as resolved
Review

Lo miramos, porque este método se puede simplificar

Lo miramos, porque este método se puede simplificar
Review

Este archivo es copy paste del de salix (lo hizo Juan)

Este archivo es copy paste del de salix (lo hizo Juan)
Review

Ah vale, me salía en verde.
Entonces, no lo tocamos?

Ah vale, me salía en verde. Entonces, no lo tocamos?
Review

Yo no lo tocaria

Yo no lo tocaria
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,
};
}