7658-devToTest_2428 #508
|
@ -203,6 +203,8 @@ async function save() {
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
notify('errors.writeRequest', 'negative');
|
notify('errors.writeRequest', 'negative');
|
||||||
|
hasChanges.value = false;
|
||||||
|
isLoading.value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,9 +22,13 @@ const $props = defineProps({
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
searchUrl: {
|
||||||
|
type: String,
|
||||||
|
default: 'params',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
const model = defineModel();
|
const model = defineModel();
|
||||||
const arrayData = useArrayData($props.dataKey);
|
const arrayData = useArrayData($props.dataKey, { searchUrl: $props.searchUrl });
|
||||||
const columnFilter = computed(() => $props.column?.columnFilter);
|
const columnFilter = computed(() => $props.column?.columnFilter);
|
||||||
|
|
||||||
const updateEvent = { 'update:modelValue': addFilter };
|
const updateEvent = { 'update:modelValue': addFilter };
|
||||||
|
@ -99,6 +103,7 @@ const components = {
|
||||||
};
|
};
|
||||||
|
|
||||||
async function addFilter(value) {
|
async function addFilter(value) {
|
||||||
|
value ||= undefined;
|
||||||
if (value && typeof value === 'object') value = model.value;
|
if (value && typeof value === 'object') value = model.value;
|
||||||
value = value === '' ? undefined : value;
|
value = value === '' ? undefined : value;
|
||||||
let field = columnFilter.value?.name ?? $props.column.name;
|
let field = columnFilter.value?.name ?? $props.column.name;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, computed, watch } from 'vue';
|
import { ref, onMounted, computed, watch, useAttrs } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
import { useQuasar } from 'quasar';
|
import { useQuasar } from 'quasar';
|
||||||
|
|
||||||
import { useStateStore } from 'stores/useStateStore';
|
import { useStateStore } from 'stores/useStateStore';
|
||||||
|
@ -43,15 +43,26 @@ const $props = defineProps({
|
||||||
type: Object,
|
type: Object,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
|
cardClass: {
|
||||||
|
type: String,
|
||||||
|
default: 'flex-one',
|
||||||
|
},
|
||||||
|
searchUrl: {
|
||||||
|
type: String,
|
||||||
|
default: 'table',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
const attrs = useAttrs();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const stateStore = useStateStore();
|
const stateStore = useStateStore();
|
||||||
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const quasar = useQuasar();
|
const quasar = useQuasar();
|
||||||
|
|
||||||
const mode = ref('card');
|
const mode = ref('card');
|
||||||
const selected = ref([]);
|
const selected = ref([]);
|
||||||
const params = ref({});
|
const routeQuery = JSON.parse(route?.query[$props.searchUrl] ?? '{}');
|
||||||
|
const params = ref({ ...routeQuery, ...routeQuery.filter?.where });
|
||||||
const VnPaginateRef = ref({});
|
const VnPaginateRef = ref({});
|
||||||
const showForm = ref(false);
|
const showForm = ref(false);
|
||||||
const splittedColumns = ref({ columns: [] });
|
const splittedColumns = ref({ columns: [] });
|
||||||
|
@ -72,6 +83,7 @@ const tableModes = [
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
mode.value = quasar.platform.is.mobile ? 'card' : $props.defaultMode;
|
mode.value = quasar.platform.is.mobile ? 'card' : $props.defaultMode;
|
||||||
stateStore.rightDrawer = true;
|
stateStore.rightDrawer = true;
|
||||||
|
setUserParams(route.query[$props.searchUrl]);
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
|
@ -80,6 +92,27 @@ watch(
|
||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// try without
|
||||||
|
watch(
|
||||||
|
() => route.params.id,
|
||||||
|
() => reload(attrs)
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => route.query[$props.searchUrl],
|
||||||
|
(val) => setUserParams(val)
|
||||||
|
);
|
||||||
|
|
||||||
|
function setUserParams(watchedParams) {
|
||||||
|
if (!watchedParams) return;
|
||||||
|
|
||||||
|
if (typeof watchedParams == 'string') watchedParams = JSON.parse(watchedParams);
|
||||||
|
const where = JSON.parse(watchedParams?.filter)?.where;
|
||||||
|
watchedParams = { ...watchedParams, ...where };
|
||||||
|
delete watchedParams.filter;
|
||||||
|
params.value = { ...params.value, ...watchedParams };
|
||||||
|
}
|
||||||
|
|
||||||
function splitColumns(columns) {
|
function splitColumns(columns) {
|
||||||
splittedColumns.value = {
|
splittedColumns.value = {
|
||||||
columns: [],
|
columns: [],
|
||||||
|
@ -126,12 +159,12 @@ function stopEventPropagation(event) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
}
|
}
|
||||||
|
|
||||||
function reload() {
|
function reload(params) {
|
||||||
VnPaginateRef.value.fetch();
|
VnPaginateRef.value.fetch(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
function columnName(col) {
|
function columnName(col) {
|
||||||
const column = Object.assign({}, col, col.columnFilter);
|
const column = { ...col, ...col.columnFilter };
|
||||||
let name = column.name;
|
let name = column.name;
|
||||||
if (column.alias) name = column.alias + '.' + name;
|
if (column.alias) name = column.alias + '.' + name;
|
||||||
return name;
|
return name;
|
||||||
|
@ -156,6 +189,7 @@ defineExpose({
|
||||||
:search-button="true"
|
:search-button="true"
|
||||||
v-model="params"
|
v-model="params"
|
||||||
:disable-submit-event="true"
|
:disable-submit-event="true"
|
||||||
|
:search-url="searchUrl"
|
||||||
>
|
>
|
||||||
<template #body>
|
<template #body>
|
||||||
<VnTableFilter
|
<VnTableFilter
|
||||||
|
@ -179,6 +213,7 @@ defineExpose({
|
||||||
class="q-px-md"
|
class="q-px-md"
|
||||||
:limit="20"
|
:limit="20"
|
||||||
ref="VnPaginateRef"
|
ref="VnPaginateRef"
|
||||||
|
:search-url="searchUrl"
|
||||||
:disable-infinite-scroll="mode == 'table'"
|
:disable-infinite-scroll="mode == 'table'"
|
||||||
>
|
>
|
||||||
<template #body="{ rows }">
|
<template #body="{ rows }">
|
||||||
|
@ -233,6 +268,7 @@ defineExpose({
|
||||||
:show-title="true"
|
:show-title="true"
|
||||||
:data-key="$attrs['data-key']"
|
:data-key="$attrs['data-key']"
|
||||||
v-model="params[columnName(col)]"
|
v-model="params[columnName(col)]"
|
||||||
|
:search-url="searchUrl"
|
||||||
/>
|
/>
|
||||||
</QTh>
|
</QTh>
|
||||||
</template>
|
</template>
|
||||||
|
@ -300,7 +336,11 @@ defineExpose({
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<QCardSection vertical class="no-margin no-padding w-80">
|
<QCardSection
|
||||||
|
vertical
|
||||||
|
class="no-margin no-padding"
|
||||||
|
:class="colsMap.tableActions ? 'w-80' : 'fit'"
|
||||||
|
>
|
||||||
<!-- Chips -->
|
<!-- Chips -->
|
||||||
<QCardSection
|
<QCardSection
|
||||||
v-if="splittedColumns.chips.length"
|
v-if="splittedColumns.chips.length"
|
||||||
|
@ -324,17 +364,21 @@ defineExpose({
|
||||||
:title="row[splittedColumns.title.name]"
|
:title="row[splittedColumns.title.name]"
|
||||||
@click="stopEventPropagation($event)"
|
@click="stopEventPropagation($event)"
|
||||||
class="cursor-text"
|
class="cursor-text"
|
||||||
>{{ row[splittedColumns.title.name] }}</span
|
|
||||||
>
|
>
|
||||||
|
{{ row[splittedColumns.title.name] }}
|
||||||
|
</span>
|
||||||
</QCardSection>
|
</QCardSection>
|
||||||
<!-- Fields -->
|
<!-- Fields -->
|
||||||
<QCardSection class="q-pl-sm q-pr-lg q-py-xs flex-one">
|
<QCardSection
|
||||||
|
class="q-pl-sm q-pr-lg q-py-xs"
|
||||||
|
:class="$props.cardClass"
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
v-for="col of splittedColumns.visible"
|
v-for="col of splittedColumns.visible"
|
||||||
:key="col.name"
|
:key="col.name"
|
||||||
class="fields"
|
class="fields"
|
||||||
>
|
>
|
||||||
<VnLv :label="`${col.label}:`">
|
<VnLv :label="col.label && `${col.label}:`">
|
||||||
<template #value>
|
<template #value>
|
||||||
<span
|
<span
|
||||||
@click="stopEventPropagation($event)"
|
@click="stopEventPropagation($event)"
|
||||||
|
@ -545,6 +589,16 @@ defineExpose({
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.grid-two {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(150px, max-content));
|
||||||
|
max-width: 100%;
|
||||||
|
margin: 0 auto;
|
||||||
|
overflow: scroll;
|
||||||
|
white-space: wrap;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.w-80 {
|
.w-80 {
|
||||||
width: 80%;
|
width: 80%;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { onBeforeMount, computed, watchEffect } from 'vue';
|
import { onBeforeMount, computed, watchEffect } from 'vue';
|
||||||
import { useRoute, onBeforeRouteUpdate } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import { useArrayData } from 'src/composables/useArrayData';
|
import { useArrayData } from 'src/composables/useArrayData';
|
||||||
import { useStateStore } from 'stores/useStateStore';
|
import { useStateStore } from 'stores/useStateStore';
|
||||||
import useCardSize from 'src/composables/useCardSize';
|
import useCardSize from 'src/composables/useCardSize';
|
||||||
|
@ -41,15 +41,6 @@ onBeforeMount(async () => {
|
||||||
await arrayData.fetch({ append: false });
|
await arrayData.fetch({ append: false });
|
||||||
});
|
});
|
||||||
|
|
||||||
if (props.baseUrl) {
|
|
||||||
onBeforeRouteUpdate(async (to, from) => {
|
|
||||||
if (to.params.id !== from.params.id) {
|
|
||||||
arrayData.store.url = `${props.baseUrl}/${to.params.id}`;
|
|
||||||
await arrayData.fetch({ append: false });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
if (Array.isArray(arrayData.store.data))
|
if (Array.isArray(arrayData.store.data))
|
||||||
arrayData.store.data = arrayData.store.data[0];
|
arrayData.store.data = arrayData.store.data[0];
|
||||||
|
|
|
@ -180,6 +180,7 @@ watch(modelValue, (newValue) => {
|
||||||
>
|
>
|
||||||
<template v-if="isClearable" #append>
|
<template v-if="isClearable" #append>
|
||||||
<QIcon
|
<QIcon
|
||||||
|
v-show="value"
|
||||||
name="close"
|
name="close"
|
||||||
@click.stop="value = null"
|
@click.stop="value = null"
|
||||||
class="cursor-pointer"
|
class="cursor-pointer"
|
||||||
|
|
|
@ -4,12 +4,11 @@ import { useI18n } from 'vue-i18n';
|
||||||
import { useArrayData } from 'composables/useArrayData';
|
import { useArrayData } from 'composables/useArrayData';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import toDate from 'filters/toDate';
|
import toDate from 'filters/toDate';
|
||||||
import useRedirect from 'src/composables/useRedirect';
|
|
||||||
import VnFilterPanelChip from 'components/ui/VnFilterPanelChip.vue';
|
import VnFilterPanelChip from 'components/ui/VnFilterPanelChip.vue';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const params = defineModel();
|
const params = defineModel({ default: {}, required: true, type: Object });
|
||||||
const props = defineProps({
|
const $props = defineProps({
|
||||||
dataKey: {
|
dataKey: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
|
@ -36,7 +35,7 @@ const props = defineProps({
|
||||||
},
|
},
|
||||||
hiddenTags: {
|
hiddenTags: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => [],
|
default: () => ['filter'],
|
||||||
},
|
},
|
||||||
customTags: {
|
customTags: {
|
||||||
type: Array,
|
type: Array,
|
||||||
|
@ -46,82 +45,72 @@ const props = defineProps({
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
searchUrl: {
|
||||||
|
type: String,
|
||||||
|
default: 'params',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits(['refresh', 'clear', 'search', 'init', 'remove']);
|
const emit = defineEmits(['refresh', 'clear', 'search', 'init', 'remove']);
|
||||||
|
|
||||||
const arrayData = useArrayData(props.dataKey, {
|
const arrayData = useArrayData($props.dataKey, {
|
||||||
exprBuilder: props.exprBuilder,
|
exprBuilder: $props.exprBuilder,
|
||||||
|
searchUrl: $props.searchUrl,
|
||||||
|
navigate: {},
|
||||||
});
|
});
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const store = arrayData.store;
|
const store = arrayData.store;
|
||||||
const userParams = ref({});
|
|
||||||
const { navigate } = useRedirect();
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (params.value) userParams.value = JSON.parse(JSON.stringify(params.value));
|
emit('init', { params: params.value });
|
||||||
if (Object.keys(store.userParams).length > 0) {
|
|
||||||
userParams.value = JSON.parse(JSON.stringify(store.userParams));
|
|
||||||
params.value = {
|
|
||||||
...params.value,
|
|
||||||
...userParams.value,
|
|
||||||
...userParams.value?.filter?.where,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
emit('init', { params: userParams.value });
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function setUserParams(params) {
|
function setUserParams(watchedParams) {
|
||||||
if (!params) {
|
if (!watchedParams) return;
|
||||||
userParams.value = {};
|
|
||||||
} else {
|
if (typeof watchedParams == 'string') watchedParams = JSON.parse(watchedParams);
|
||||||
userParams.value = typeof params == 'string' ? JSON.parse(params) : params;
|
watchedParams = { ...watchedParams, ...watchedParams.filter?.where };
|
||||||
}
|
delete watchedParams.filter;
|
||||||
|
params.value = { ...params.value, ...watchedParams };
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => route.query.params,
|
() => route.query[$props.searchUrl],
|
||||||
(val) => {
|
(val) => setUserParams(val)
|
||||||
setUserParams(val);
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => arrayData.store.userParams,
|
() => arrayData.store.userParams,
|
||||||
(val) => {
|
(val) => setUserParams(val)
|
||||||
setUserParams(val);
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const isLoading = ref(false);
|
const isLoading = ref(false);
|
||||||
async function search(evt) {
|
async function search(evt) {
|
||||||
if (evt && props.disableSubmitEvent) return;
|
if (evt && $props.disableSubmitEvent) return;
|
||||||
|
|
||||||
store.filter.where = {};
|
store.filter.where = {};
|
||||||
isLoading.value = true;
|
isLoading.value = true;
|
||||||
const filter = { ...userParams.value, ...params.value };
|
const filter = { ...params.value };
|
||||||
store.userParamsChanged = true;
|
store.userParamsChanged = true;
|
||||||
store.filter.skip = 0;
|
store.filter.skip = 0;
|
||||||
store.skip = 0;
|
store.skip = 0;
|
||||||
const { params: newParams } = await arrayData.addFilter({ params: filter });
|
const { params: newParams } = await arrayData.addFilter({ params: params.value });
|
||||||
userParams.value = newParams;
|
params.value = newParams;
|
||||||
|
|
||||||
if (!props.showAll && !Object.values(filter).length) store.data = [];
|
if (!$props.showAll && !Object.values(filter).length) store.data = [];
|
||||||
|
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
emit('search');
|
emit('search');
|
||||||
navigate(store.data, {});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function reload() {
|
async function reload() {
|
||||||
isLoading.value = true;
|
isLoading.value = true;
|
||||||
const params = Object.values(userParams.value).filter((param) => param);
|
const params = Object.values(params.value).filter((param) => param);
|
||||||
|
|
||||||
await arrayData.fetch({ append: false });
|
await arrayData.fetch({ append: false });
|
||||||
if (!props.showAll && !params.length) store.data = [];
|
if (!$props.showAll && !params.length) store.data = [];
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
emit('refresh');
|
emit('refresh');
|
||||||
navigate(store.data, {});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function clearFilters() {
|
async function clearFilters() {
|
||||||
|
@ -130,19 +119,19 @@ async function clearFilters() {
|
||||||
store.filter.skip = 0;
|
store.filter.skip = 0;
|
||||||
store.skip = 0;
|
store.skip = 0;
|
||||||
// Filtrar los params no removibles
|
// Filtrar los params no removibles
|
||||||
const removableFilters = Object.keys(userParams.value).filter((param) =>
|
const removableFilters = Object.keys(params.value).filter((param) =>
|
||||||
props.unremovableParams.includes(param)
|
$props.unremovableParams.includes(param)
|
||||||
);
|
);
|
||||||
const newParams = {};
|
const newParams = {};
|
||||||
// Conservar solo los params que no son removibles
|
// Conservar solo los params que no son removibles
|
||||||
for (const key of removableFilters) {
|
for (const key of removableFilters) {
|
||||||
newParams[key] = userParams.value[key];
|
newParams[key] = params.value[key];
|
||||||
}
|
}
|
||||||
params.value = {};
|
params.value = {};
|
||||||
userParams.value = { ...newParams }; // Actualizar los params con los removibles
|
params.value = { ...newParams }; // Actualizar los params con los removibles
|
||||||
await arrayData.applyFilter({ params: userParams.value });
|
await arrayData.applyFilter({ params: params.value });
|
||||||
|
|
||||||
if (!props.showAll) {
|
if (!$props.showAll) {
|
||||||
store.data = [];
|
store.data = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,34 +141,26 @@ async function clearFilters() {
|
||||||
|
|
||||||
const tagsList = computed(() => {
|
const tagsList = computed(() => {
|
||||||
const tagList = [];
|
const tagList = [];
|
||||||
const params = {
|
for (const key of Object.keys(params.value)) {
|
||||||
...userParams.value,
|
const value = params.value[key];
|
||||||
};
|
if (!value || ($props.hiddenTags || []).includes(key)) continue;
|
||||||
const where = params?.filter?.where;
|
tagList.push({ label: key, value });
|
||||||
if (where) {
|
|
||||||
Object.assign(params, where);
|
|
||||||
}
|
|
||||||
delete params.filter;
|
|
||||||
for (const key of Object.keys(params)) {
|
|
||||||
const value = params[key];
|
|
||||||
if (!value || (props.hiddenTags || []).includes(key)) continue;
|
|
||||||
tagList.push({ key, value });
|
|
||||||
}
|
}
|
||||||
return tagList;
|
return tagList;
|
||||||
});
|
});
|
||||||
|
|
||||||
const tags = computed(() =>
|
const tags = computed(() => {
|
||||||
tagsList.value.filter((tag) => !(props.customTags || []).includes(tag.key))
|
return tagsList.value.filter((tag) => !($props.customTags || []).includes(tag.key));
|
||||||
);
|
});
|
||||||
const customTags = computed(() =>
|
const customTags = computed(() =>
|
||||||
tagsList.value.filter((tag) => (props.customTags || []).includes(tag.key))
|
tagsList.value.filter((tag) => ($props.customTags || []).includes(tag.key))
|
||||||
);
|
);
|
||||||
|
|
||||||
async function remove(key) {
|
async function remove(key) {
|
||||||
delete userParams.value[key];
|
delete params.value[key];
|
||||||
delete userParams.value.filter.where[key];
|
delete params.value.filter?.where?.[key];
|
||||||
params.value[key] = undefined;
|
params.value[key] = undefined;
|
||||||
await arrayData.applyFilter({ params: userParams.value });
|
await arrayData.applyFilter({ params: params.value });
|
||||||
emit('remove', key);
|
emit('remove', key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -244,13 +225,13 @@ function formatValue(value) {
|
||||||
<div>
|
<div>
|
||||||
<VnFilterPanelChip
|
<VnFilterPanelChip
|
||||||
v-for="chip of tags"
|
v-for="chip of tags"
|
||||||
:key="chip.key"
|
:key="chip.label"
|
||||||
:removable="!unremovableParams.includes(chip.key)"
|
:removable="!unremovableParams.includes(chip.label)"
|
||||||
@remove="remove(chip.key)"
|
@remove="remove(chip.label)"
|
||||||
>
|
>
|
||||||
<slot name="tags" :tag="chip" :format-fn="formatValue">
|
<slot name="tags" :tag="chip" :format-fn="formatValue">
|
||||||
<div class="q-gutter-x-xs">
|
<div class="q-gutter-x-xs">
|
||||||
<strong>{{ chip.key }}:</strong>
|
<strong>{{ chip.label }}:</strong>
|
||||||
<span>"{{ chip.value }}"</span>
|
<span>"{{ chip.value }}"</span>
|
||||||
</div>
|
</div>
|
||||||
</slot>
|
</slot>
|
||||||
|
@ -258,7 +239,7 @@ function formatValue(value) {
|
||||||
<slot
|
<slot
|
||||||
v-if="$slots.customTags"
|
v-if="$slots.customTags"
|
||||||
name="customTags"
|
name="customTags"
|
||||||
:params="userParams"
|
:params="params"
|
||||||
:tags="customTags"
|
:tags="customTags"
|
||||||
:format-fn="formatValue"
|
:format-fn="formatValue"
|
||||||
:search-fn="search"
|
:search-fn="search"
|
||||||
|
@ -268,9 +249,9 @@ function formatValue(value) {
|
||||||
<QSeparator />
|
<QSeparator />
|
||||||
</QList>
|
</QList>
|
||||||
<QList dense class="list q-gutter-y-sm q-mt-sm">
|
<QList dense class="list q-gutter-y-sm q-mt-sm">
|
||||||
<slot name="body" :params="userParams" :search-fn="search"></slot>
|
<slot name="body" :params="params" :search-fn="search"></slot>
|
||||||
</QList>
|
</QList>
|
||||||
<template v-if="props.searchButton">
|
<template v-if="$props.searchButton">
|
||||||
<QItem>
|
<QItem>
|
||||||
<QItemSection class="q-py-sm">
|
<QItemSection class="q-py-sm">
|
||||||
<QBtn
|
<QBtn
|
||||||
|
@ -282,7 +263,6 @@ function formatValue(value) {
|
||||||
rounded
|
rounded
|
||||||
:type="disableSubmitEvent ? 'button' : 'submit'"
|
:type="disableSubmitEvent ? 'button' : 'submit'"
|
||||||
unelevated
|
unelevated
|
||||||
@click="search()"
|
|
||||||
/>
|
/>
|
||||||
</QItemSection>
|
</QItemSection>
|
||||||
</QItem>
|
</QItem>
|
||||||
|
@ -295,7 +275,6 @@ function formatValue(value) {
|
||||||
color="primary"
|
color="primary"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.list {
|
.list {
|
||||||
width: 256px;
|
width: 256px;
|
||||||
|
|
|
@ -62,6 +62,10 @@ const props = defineProps({
|
||||||
type: Function,
|
type: Function,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
|
searchUrl: {
|
||||||
|
type: String,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
disableInfiniteScroll: {
|
disableInfiniteScroll: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
|
@ -86,6 +90,7 @@ const arrayData = useArrayData(props.dataKey, {
|
||||||
userParams: props.userParams,
|
userParams: props.userParams,
|
||||||
exprBuilder: props.exprBuilder,
|
exprBuilder: props.exprBuilder,
|
||||||
keepOpts: props.keepOpts,
|
keepOpts: props.keepOpts,
|
||||||
|
searchUrl: props.searchUrl,
|
||||||
});
|
});
|
||||||
const store = arrayData.store;
|
const store = arrayData.store;
|
||||||
|
|
||||||
|
@ -104,7 +109,8 @@ const addFilter = async (filter, params) => {
|
||||||
await arrayData.addFilter({ filter, params });
|
await arrayData.addFilter({ filter, params });
|
||||||
};
|
};
|
||||||
|
|
||||||
async function fetch() {
|
async function fetch(params) {
|
||||||
|
useArrayData(props.dataKey, params);
|
||||||
store.filter.skip = 0;
|
store.filter.skip = 0;
|
||||||
store.skip = 0;
|
store.skip = 0;
|
||||||
await arrayData.fetch({ append: false });
|
await arrayData.fetch({ append: false });
|
||||||
|
|
|
@ -3,7 +3,6 @@ import { onMounted, ref, watch } from 'vue';
|
||||||
import { useQuasar } from 'quasar';
|
import { useQuasar } from 'quasar';
|
||||||
import { useArrayData } from 'composables/useArrayData';
|
import { useArrayData } from 'composables/useArrayData';
|
||||||
import VnInput from 'src/components/common/VnInput.vue';
|
import VnInput from 'src/components/common/VnInput.vue';
|
||||||
import useRedirect from 'src/composables/useRedirect';
|
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
const quasar = useQuasar();
|
const quasar = useQuasar();
|
||||||
|
@ -16,17 +15,14 @@ const props = defineProps({
|
||||||
},
|
},
|
||||||
label: {
|
label: {
|
||||||
type: String,
|
type: String,
|
||||||
required: false,
|
|
||||||
default: 'Search',
|
default: 'Search',
|
||||||
},
|
},
|
||||||
info: {
|
info: {
|
||||||
type: String,
|
type: String,
|
||||||
required: false,
|
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
redirect: {
|
redirect: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
url: {
|
url: {
|
||||||
|
@ -67,10 +63,20 @@ const props = defineProps({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
let arrayData = useArrayData(props.dataKey, { ...props });
|
|
||||||
let store = arrayData.store;
|
|
||||||
const searchText = ref('');
|
const searchText = ref('');
|
||||||
const { navigate } = useRedirect();
|
let arrayDataProps = { ...props };
|
||||||
|
if (props.redirect)
|
||||||
|
arrayDataProps = {
|
||||||
|
...props,
|
||||||
|
...{
|
||||||
|
navigate: {
|
||||||
|
customRouteRedirectName: props.customRouteRedirectName,
|
||||||
|
searchText: searchText.value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let arrayData = useArrayData(props.dataKey, arrayDataProps);
|
||||||
|
let store = arrayData.store;
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.dataKey,
|
() => props.dataKey,
|
||||||
|
@ -98,13 +104,6 @@ async function search() {
|
||||||
search: searchText.value,
|
search: searchText.value,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!props.redirect) return;
|
|
||||||
|
|
||||||
navigate(store.data, {
|
|
||||||
customRouteRedirectName: props.customRouteRedirectName,
|
|
||||||
searchText: searchText.value,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -23,8 +23,13 @@ export function useArrayData(key, userOptions) {
|
||||||
store.skip = 0;
|
store.skip = 0;
|
||||||
|
|
||||||
const query = route.query;
|
const query = route.query;
|
||||||
if (query.params) {
|
const searchUrl = store.searchUrl;
|
||||||
store.userParams = JSON.parse(query.params);
|
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',
|
'userParams',
|
||||||
'userFilter',
|
'userFilter',
|
||||||
'exprBuilder',
|
'exprBuilder',
|
||||||
|
'searchUrl',
|
||||||
|
'navigate',
|
||||||
];
|
];
|
||||||
if (typeof userOptions === 'object') {
|
if (typeof userOptions === 'object') {
|
||||||
for (const option in userOptions) {
|
for (const option in userOptions) {
|
||||||
const isEmpty = userOptions[option] == null || userOptions[option] === '';
|
const isEmpty = userOptions[option] == null || userOptions[option] === '';
|
||||||
if (isEmpty || !allowedOptions.includes(option)) continue;
|
if (isEmpty || !allowedOptions.includes(option)) continue;
|
||||||
|
|
||||||
if (Object.prototype.hasOwnProperty.call(store, option)) {
|
if (Object.hasOwn(store, option)) {
|
||||||
const defaultOpts = userOptions[option];
|
const defaultOpts = userOptions[option];
|
||||||
store[option] = userOptions.keepOpts?.includes(option)
|
store[option] = userOptions.keepOpts?.includes(option)
|
||||||
? Object.assign(defaultOpts, store[option])
|
? Object.assign(defaultOpts, store[option])
|
||||||
|
@ -88,8 +95,8 @@ export function useArrayData(key, userOptions) {
|
||||||
|
|
||||||
Object.assign(params, userParams);
|
Object.assign(params, userParams);
|
||||||
|
|
||||||
store.isLoading = true;
|
|
||||||
store.currentFilter = params;
|
store.currentFilter = params;
|
||||||
|
store.isLoading = true;
|
||||||
const response = await axios.get(store.url, {
|
const response = await axios.get(store.url, {
|
||||||
signal: canceller.signal,
|
signal: canceller.signal,
|
||||||
params,
|
params,
|
||||||
|
@ -119,6 +126,10 @@ export function useArrayData(key, userOptions) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function deleteOption(option) {
|
||||||
|
delete store[option];
|
||||||
|
}
|
||||||
|
|
||||||
function cancelRequest() {
|
function cancelRequest() {
|
||||||
if (canceller) {
|
if (canceller) {
|
||||||
canceller.abort();
|
canceller.abort();
|
||||||
|
@ -129,7 +140,7 @@ export function useArrayData(key, userOptions) {
|
||||||
async function applyFilter({ filter, params }) {
|
async function applyFilter({ filter, params }) {
|
||||||
if (filter) store.userFilter = filter;
|
if (filter) store.userFilter = filter;
|
||||||
store.filter = {};
|
store.filter = {};
|
||||||
if (params) store.userParams = Object.assign({}, params);
|
if (params) store.userParams = { ...params };
|
||||||
|
|
||||||
const response = await fetch({ append: false });
|
const response = await fetch({ append: false });
|
||||||
return response;
|
return response;
|
||||||
|
@ -138,7 +149,7 @@ export function useArrayData(key, userOptions) {
|
||||||
async function addFilter({ filter, params }) {
|
async function addFilter({ filter, params }) {
|
||||||
if (filter) store.userFilter = Object.assign(store.userFilter, filter);
|
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);
|
userParams = sanitizerParams(userParams, store?.exprBuilder);
|
||||||
|
|
||||||
store.userParams = userParams;
|
store.userParams = userParams;
|
||||||
|
@ -163,9 +174,7 @@ export function useArrayData(key, userOptions) {
|
||||||
delete store.userParams[param];
|
delete store.userParams[param];
|
||||||
delete params[param];
|
delete params[param];
|
||||||
if (store.filter?.where) {
|
if (store.filter?.where) {
|
||||||
const key = Object.keys(
|
const key = Object.keys(exprBuilder ? exprBuilder(param) : param);
|
||||||
exprBuilder && exprBuilder(param) ? exprBuilder(param) : param
|
|
||||||
);
|
|
||||||
if (key[0]) delete store.filter.where[key[0]];
|
if (key[0]) delete store.filter.where[key[0]];
|
||||||
if (Object.keys(store.filter.where).length === 0) {
|
if (Object.keys(store.filter.where).length === 0) {
|
||||||
delete store.filter.where;
|
delete store.filter.where;
|
||||||
|
@ -190,22 +199,32 @@ export function useArrayData(key, userOptions) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateStateParams() {
|
function updateStateParams() {
|
||||||
const query = {};
|
const newUrl = { path: route.path, query: { ...(route.query ?? {}) } };
|
||||||
if (store.order) query.order = store.order;
|
newUrl.query[store.searchUrl] = JSON.stringify(store.currentFilter);
|
||||||
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);
|
|
||||||
|
|
||||||
router.replace({
|
if (store.navigate) {
|
||||||
path: route.path,
|
const { customRouteRedirectName, searchText } = store.navigate;
|
||||||
query,
|
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);
|
const totalRows = computed(() => (store.data && store.data.length) || 0);
|
||||||
|
@ -223,5 +242,6 @@ export function useArrayData(key, userOptions) {
|
||||||
totalRows,
|
totalRows,
|
||||||
updateStateParams,
|
updateStateParams,
|
||||||
isLoading,
|
isLoading,
|
||||||
|
deleteOption,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 };
|
|
||||||
}
|
|
|
@ -21,6 +21,8 @@ export const useArrayDataStore = defineStore('arrayDataStore', () => {
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
userParamsChanged: false,
|
userParamsChanged: false,
|
||||||
exprBuilder: null,
|
exprBuilder: null,
|
||||||
|
searchUrl: 'params',
|
||||||
|
navigate: 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' });
|
|
||||||
});
|
|
||||||
});
|
|
Loading…
Reference in New Issue