forked from verdnatura/salix-front
Reviewed-on: verdnatura/salix-front#396 Reviewed-by: Javier Segarra <jsegarra@verdnatura.es> Reviewed-by: Pablo Natek <pablone@verdnatura.es>
This commit is contained in:
commit
bcda488aae
|
@ -27,7 +27,6 @@ export default {
|
||||||
this.$el.addEventListener('keyup', function (evt) {
|
this.$el.addEventListener('keyup', function (evt) {
|
||||||
if (evt.key === 'Enter') {
|
if (evt.key === 'Enter') {
|
||||||
const input = evt.target;
|
const input = evt.target;
|
||||||
console.log('input', input);
|
|
||||||
if (input.type == 'textarea' && evt.shiftKey) {
|
if (input.type == 'textarea' && evt.shiftKey) {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
let { selectionStart, selectionEnd } = input;
|
let { selectionStart, selectionEnd } = input;
|
||||||
|
|
|
@ -67,6 +67,10 @@ const $props = defineProps({
|
||||||
default: '',
|
default: '',
|
||||||
description: 'It is used for redirect on click "save and continue"',
|
description: 'It is used for redirect on click "save and continue"',
|
||||||
},
|
},
|
||||||
|
hasSubtoolbar: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const isLoading = ref(false);
|
const isLoading = ref(false);
|
||||||
|
@ -75,6 +79,7 @@ const originalData = ref();
|
||||||
const vnPaginateRef = ref();
|
const vnPaginateRef = ref();
|
||||||
const formData = ref();
|
const formData = ref();
|
||||||
const saveButtonRef = ref(null);
|
const saveButtonRef = ref(null);
|
||||||
|
const watchChanges = ref();
|
||||||
const formUrl = computed(() => $props.url);
|
const formUrl = computed(() => $props.url);
|
||||||
|
|
||||||
const emit = defineEmits(['onFetch', 'update:selected', 'saveChanges']);
|
const emit = defineEmits(['onFetch', 'update:selected', 'saveChanges']);
|
||||||
|
@ -89,6 +94,7 @@ defineExpose({
|
||||||
saveChanges,
|
saveChanges,
|
||||||
getChanges,
|
getChanges,
|
||||||
formData,
|
formData,
|
||||||
|
vnPaginateRef,
|
||||||
});
|
});
|
||||||
|
|
||||||
async function fetch(data) {
|
async function fetch(data) {
|
||||||
|
@ -97,19 +103,26 @@ async function fetch(data) {
|
||||||
data.map((d) => (d.$index = $index++));
|
data.map((d) => (d.$index = $index++));
|
||||||
}
|
}
|
||||||
|
|
||||||
originalData.value = data && JSON.parse(JSON.stringify(data));
|
resetData(data);
|
||||||
formData.value = data && JSON.parse(JSON.stringify(data));
|
|
||||||
watch(formData, () => (hasChanges.value = true), { deep: true });
|
|
||||||
|
|
||||||
emit('onFetch', data);
|
emit('onFetch', data);
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resetData(data) {
|
||||||
|
if (!data) return;
|
||||||
|
originalData.value = JSON.parse(JSON.stringify(data));
|
||||||
|
formData.value = JSON.parse(JSON.stringify(data));
|
||||||
|
|
||||||
|
if (watchChanges.value) watchChanges.value(); //destoy watcher
|
||||||
|
watchChanges.value = watch(formData, () => (hasChanges.value = true), { deep: true });
|
||||||
|
}
|
||||||
|
|
||||||
async function reset() {
|
async function reset() {
|
||||||
await fetch(originalData.value);
|
await fetch(originalData.value);
|
||||||
hasChanges.value = false;
|
hasChanges.value = false;
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line vue/no-dupe-keys
|
|
||||||
function filter(value, update, filterOptions) {
|
function filter(value, update, filterOptions) {
|
||||||
update(
|
update(
|
||||||
() => {
|
() => {
|
||||||
|
@ -271,8 +284,9 @@ function isEmpty(obj) {
|
||||||
if (obj.length > 0) return false;
|
if (obj.length > 0) return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function reload() {
|
async function reload(params) {
|
||||||
vnPaginateRef.value.fetch();
|
const data = await vnPaginateRef.value.fetch(params);
|
||||||
|
fetch(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(formUrl, async () => {
|
watch(formUrl, async () => {
|
||||||
|
@ -284,10 +298,11 @@ watch(formUrl, async () => {
|
||||||
<VnPaginate
|
<VnPaginate
|
||||||
:url="url"
|
:url="url"
|
||||||
:limit="limit"
|
:limit="limit"
|
||||||
v-bind="$attrs"
|
|
||||||
@on-fetch="fetch"
|
@on-fetch="fetch"
|
||||||
|
@on-change="resetData"
|
||||||
:skeleton="false"
|
:skeleton="false"
|
||||||
ref="vnPaginateRef"
|
ref="vnPaginateRef"
|
||||||
|
v-bind="$attrs"
|
||||||
>
|
>
|
||||||
<template #body v-if="formData">
|
<template #body v-if="formData">
|
||||||
<slot
|
<slot
|
||||||
|
@ -298,8 +313,8 @@ watch(formUrl, async () => {
|
||||||
></slot>
|
></slot>
|
||||||
</template>
|
</template>
|
||||||
</VnPaginate>
|
</VnPaginate>
|
||||||
<SkeletonTable v-if="!formData" />
|
<SkeletonTable v-if="!formData" :columns="$attrs.columns?.length" />
|
||||||
<Teleport to="#st-actions" v-if="stateStore?.isSubToolbarShown()">
|
<Teleport to="#st-actions" v-if="stateStore?.isSubToolbarShown() && hasSubtoolbar">
|
||||||
<QBtnGroup push style="column-gap: 10px">
|
<QBtnGroup push style="column-gap: 10px">
|
||||||
<slot name="moreBeforeActions" />
|
<slot name="moreBeforeActions" />
|
||||||
<QBtn
|
<QBtn
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
<script setup>
|
||||||
|
defineProps({
|
||||||
|
columns: {
|
||||||
|
type: Array,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
row: {
|
||||||
|
type: Object,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function stopEventPropagation(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<slot name="beforeChip" :row="row"></slot>
|
||||||
|
<span
|
||||||
|
v-for="col of columns"
|
||||||
|
:key="col.name"
|
||||||
|
@click="stopEventPropagation"
|
||||||
|
class="cursor-text"
|
||||||
|
>
|
||||||
|
<QChip
|
||||||
|
v-if="col.chip.condition(row[col.name], row)"
|
||||||
|
:title="col.label"
|
||||||
|
:class="[
|
||||||
|
col.chip.color
|
||||||
|
? col.chip.color(row)
|
||||||
|
: !col.chip.icon && 'bg-chip-secondary',
|
||||||
|
col.chip.icon && 'q-px-none',
|
||||||
|
]"
|
||||||
|
dense
|
||||||
|
square
|
||||||
|
>
|
||||||
|
<span v-if="!col.chip.icon">{{ row[col.name] }}</span>
|
||||||
|
<QIcon v-else :name="col.chip.icon" color="primary-light" />
|
||||||
|
</QChip>
|
||||||
|
</span>
|
||||||
|
<slot name="afterChip" :row="row"></slot>
|
||||||
|
</template>
|
||||||
|
<style lang="scss">
|
||||||
|
.bg-chip-secondary {
|
||||||
|
background-color: var(--vn-page-color);
|
||||||
|
color: var(--vn-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cursor-text {
|
||||||
|
pointer-events: all;
|
||||||
|
cursor: text;
|
||||||
|
user-select: all;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,161 @@
|
||||||
|
<script setup>
|
||||||
|
import { markRaw, computed, defineModel } from 'vue';
|
||||||
|
import { QIcon, QCheckbox } from 'quasar';
|
||||||
|
import { dashIfEmpty } from 'src/filters';
|
||||||
|
|
||||||
|
/* basic input */
|
||||||
|
import VnSelect from 'components/common/VnSelect.vue';
|
||||||
|
import VnInput from 'components/common/VnInput.vue';
|
||||||
|
import VnInputDate from 'components/common/VnInputDate.vue';
|
||||||
|
import VnComponent from 'components/common/VnComponent.vue';
|
||||||
|
|
||||||
|
const model = defineModel(undefined, { required: true });
|
||||||
|
const $props = defineProps({
|
||||||
|
column: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
row: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
default: {
|
||||||
|
type: [Object, String],
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
componentProp: {
|
||||||
|
type: String,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
isEditable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
type: Object,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
showLabel: {
|
||||||
|
type: Boolean,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const defaultComponents = {
|
||||||
|
input: {
|
||||||
|
component: markRaw(VnInput),
|
||||||
|
attrs: {
|
||||||
|
disable: !$props.isEditable,
|
||||||
|
},
|
||||||
|
forceAttrs: {
|
||||||
|
label: $props.showLabel && $props.column.label,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
number: {
|
||||||
|
component: markRaw(VnInput),
|
||||||
|
attrs: {
|
||||||
|
disable: !$props.isEditable,
|
||||||
|
},
|
||||||
|
forceAttrs: {
|
||||||
|
label: $props.showLabel && $props.column.label,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
date: {
|
||||||
|
component: markRaw(VnInputDate),
|
||||||
|
attrs: {
|
||||||
|
readonly: true,
|
||||||
|
disable: !$props.isEditable,
|
||||||
|
style: 'min-width: 125px',
|
||||||
|
},
|
||||||
|
forceAttrs: {
|
||||||
|
label: $props.showLabel && $props.column.label,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
checkbox: {
|
||||||
|
component: markRaw(QCheckbox),
|
||||||
|
attrs: (prop) => {
|
||||||
|
const defaultAttrs = {
|
||||||
|
disable: !$props.isEditable,
|
||||||
|
'model-value': Boolean(prop),
|
||||||
|
class: 'no-padding',
|
||||||
|
};
|
||||||
|
|
||||||
|
if (typeof prop == 'number') {
|
||||||
|
defaultAttrs['true-value'] = 1;
|
||||||
|
defaultAttrs['false-value'] = 0;
|
||||||
|
}
|
||||||
|
return defaultAttrs;
|
||||||
|
},
|
||||||
|
forceAttrs: {
|
||||||
|
label: $props.showLabel && $props.column.label,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
component: markRaw(VnSelect),
|
||||||
|
attrs: {
|
||||||
|
disable: !$props.isEditable,
|
||||||
|
},
|
||||||
|
forceAttrs: {
|
||||||
|
label: $props.showLabel && $props.column.label,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
icon: {
|
||||||
|
component: markRaw(QIcon),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const value = computed(() => {
|
||||||
|
return $props.column.format
|
||||||
|
? $props.column.format($props.row, dashIfEmpty)
|
||||||
|
: dashIfEmpty($props.row[$props.column.name]);
|
||||||
|
});
|
||||||
|
|
||||||
|
const col = computed(() => {
|
||||||
|
let newColumn = { ...$props.column };
|
||||||
|
const specific = newColumn[$props.componentProp];
|
||||||
|
if (specific) {
|
||||||
|
newColumn = {
|
||||||
|
...newColumn,
|
||||||
|
...specific,
|
||||||
|
...specific.attrs,
|
||||||
|
...specific.forceAttrs,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
(newColumn.name.startsWith('is') || newColumn.name.startsWith('has')) &&
|
||||||
|
!newColumn.component
|
||||||
|
)
|
||||||
|
newColumn.component = 'checkbox';
|
||||||
|
if ($props.default && !newColumn.component) newColumn.component = $props.default;
|
||||||
|
|
||||||
|
return newColumn;
|
||||||
|
});
|
||||||
|
|
||||||
|
const components = computed(() => $props.components ?? defaultComponents);
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="row no-wrap fit">
|
||||||
|
<VnComponent
|
||||||
|
v-if="col.before"
|
||||||
|
:prop="col.before"
|
||||||
|
:components="components"
|
||||||
|
:value="model"
|
||||||
|
v-model="model"
|
||||||
|
/>
|
||||||
|
<VnComponent
|
||||||
|
v-if="col.component"
|
||||||
|
:prop="col"
|
||||||
|
:components="components"
|
||||||
|
:value="model"
|
||||||
|
v-model="model"
|
||||||
|
/>
|
||||||
|
<span :title="value" v-else>{{ value }}</span>
|
||||||
|
<VnComponent
|
||||||
|
v-if="col.after"
|
||||||
|
:prop="col.after"
|
||||||
|
:components="components"
|
||||||
|
:value="model"
|
||||||
|
v-model="model"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
|
@ -0,0 +1,146 @@
|
||||||
|
<script setup>
|
||||||
|
import { markRaw, computed, defineModel } from 'vue';
|
||||||
|
import { QCheckbox } from 'quasar';
|
||||||
|
import { useArrayData } from 'composables/useArrayData';
|
||||||
|
|
||||||
|
/* basic input */
|
||||||
|
import VnSelect from 'components/common/VnSelect.vue';
|
||||||
|
import VnInput from 'components/common/VnInput.vue';
|
||||||
|
import VnInputDate from 'components/common/VnInputDate.vue';
|
||||||
|
import VnTableColumn from 'components/VnTable/VnColumn.vue';
|
||||||
|
|
||||||
|
const $props = defineProps({
|
||||||
|
column: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
showTitle: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
dataKey: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
searchUrl: {
|
||||||
|
type: String,
|
||||||
|
default: 'params',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const model = defineModel(undefined, { required: true });
|
||||||
|
const arrayData = useArrayData($props.dataKey, { searchUrl: $props.searchUrl });
|
||||||
|
const columnFilter = computed(() => $props.column?.columnFilter);
|
||||||
|
|
||||||
|
const updateEvent = { 'update:modelValue': addFilter };
|
||||||
|
const enterEvent = {
|
||||||
|
'keyup.enter': () => addFilter(model.value),
|
||||||
|
remove: () => addFilter(null),
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultAttrs = {
|
||||||
|
filled: !$props.showTitle,
|
||||||
|
class: 'q-px-sm q-pb-xs q-pt-none',
|
||||||
|
dense: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const forceAttrs = {
|
||||||
|
label: $props.showTitle ? '' : $props.column.label,
|
||||||
|
};
|
||||||
|
|
||||||
|
const components = {
|
||||||
|
input: {
|
||||||
|
component: markRaw(VnInput),
|
||||||
|
event: enterEvent,
|
||||||
|
attrs: {
|
||||||
|
...defaultAttrs,
|
||||||
|
clearable: true,
|
||||||
|
},
|
||||||
|
forceAttrs,
|
||||||
|
},
|
||||||
|
number: {
|
||||||
|
component: markRaw(VnInput),
|
||||||
|
event: enterEvent,
|
||||||
|
attrs: {
|
||||||
|
...defaultAttrs,
|
||||||
|
clearable: true,
|
||||||
|
},
|
||||||
|
forceAttrs,
|
||||||
|
},
|
||||||
|
date: {
|
||||||
|
component: markRaw(VnInputDate),
|
||||||
|
event: updateEvent,
|
||||||
|
attrs: {
|
||||||
|
...defaultAttrs,
|
||||||
|
style: 'min-width: 150px',
|
||||||
|
},
|
||||||
|
forceAttrs,
|
||||||
|
},
|
||||||
|
checkbox: {
|
||||||
|
component: markRaw(QCheckbox),
|
||||||
|
event: updateEvent,
|
||||||
|
attrs: {
|
||||||
|
dense: true,
|
||||||
|
class: $props.showTitle ? 'q-py-sm q-mt-md' : 'q-px-md q-py-xs',
|
||||||
|
'toggle-indeterminate': true,
|
||||||
|
},
|
||||||
|
forceAttrs,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
component: markRaw(VnSelect),
|
||||||
|
event: updateEvent,
|
||||||
|
attrs: {
|
||||||
|
class: 'q-px-md q-pb-xs q-pt-none',
|
||||||
|
dense: true,
|
||||||
|
filled: !$props.showTitle,
|
||||||
|
},
|
||||||
|
forceAttrs,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
async function addFilter(value) {
|
||||||
|
value ??= undefined;
|
||||||
|
if (value && typeof value === 'object') value = model.value;
|
||||||
|
value = value === '' ? undefined : value;
|
||||||
|
let field = columnFilter.value?.name ?? $props.column.name;
|
||||||
|
|
||||||
|
if (columnFilter.value?.inWhere) {
|
||||||
|
if (columnFilter.value.alias) field = columnFilter.value.alias + '.' + field;
|
||||||
|
return await arrayData.addFilterWhere({ [field]: value });
|
||||||
|
}
|
||||||
|
await arrayData.addFilter({ params: { [field]: value } });
|
||||||
|
}
|
||||||
|
|
||||||
|
function alignRow() {
|
||||||
|
switch ($props.column.align) {
|
||||||
|
case 'left':
|
||||||
|
return 'justify-start items-start';
|
||||||
|
case 'right':
|
||||||
|
return 'justify-end items-end';
|
||||||
|
default:
|
||||||
|
return 'flex-center';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const showFilter = computed(
|
||||||
|
() => $props.column?.columnFilter !== false && $props.column.name != 'tableActions'
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
v-if="showTitle"
|
||||||
|
class="q-pt-sm q-px-sm ellipsis"
|
||||||
|
:class="`text-${column?.align ?? 'left'}`"
|
||||||
|
:style="!showFilter ? { 'min-height': 72 + 'px' } : ''"
|
||||||
|
>
|
||||||
|
{{ column?.label }}
|
||||||
|
</div>
|
||||||
|
<div v-if="showFilter" class="full-width" :class="alignRow()">
|
||||||
|
<VnTableColumn
|
||||||
|
:column="$props.column"
|
||||||
|
default="input"
|
||||||
|
v-model="model"
|
||||||
|
:components="components"
|
||||||
|
component-prop="columnFilter"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
|
@ -0,0 +1,628 @@
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted, computed, watch } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
|
import { useQuasar } from 'quasar';
|
||||||
|
|
||||||
|
import { useStateStore } from 'stores/useStateStore';
|
||||||
|
import FormModelPopup from 'components/FormModelPopup.vue';
|
||||||
|
import CrudModel from 'src/components/CrudModel.vue';
|
||||||
|
import VnFilterPanel from 'components/ui/VnFilterPanel.vue';
|
||||||
|
import VnLv from 'components/ui/VnLv.vue';
|
||||||
|
|
||||||
|
import VnTableColumn from 'components/VnTable/VnColumn.vue';
|
||||||
|
import VnTableFilter from 'components/VnTable/VnFilter.vue';
|
||||||
|
import VnTableChip from 'components/VnTable/VnChip.vue';
|
||||||
|
|
||||||
|
const $props = defineProps({
|
||||||
|
columns: {
|
||||||
|
type: Array,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
defaultMode: {
|
||||||
|
type: String,
|
||||||
|
default: 'card', // 'table', 'card'
|
||||||
|
},
|
||||||
|
columnSearch: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
rightSearch: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
rowClick: {
|
||||||
|
type: Function,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
redirect: {
|
||||||
|
type: String,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
type: Object,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
cardClass: {
|
||||||
|
type: String,
|
||||||
|
default: 'flex-one',
|
||||||
|
},
|
||||||
|
searchUrl: {
|
||||||
|
type: String,
|
||||||
|
default: 'table',
|
||||||
|
},
|
||||||
|
isEditable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
useModel: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const { t } = useI18n();
|
||||||
|
const stateStore = useStateStore();
|
||||||
|
const route = useRoute();
|
||||||
|
const router = useRouter();
|
||||||
|
const quasar = useQuasar();
|
||||||
|
|
||||||
|
const DEFAULT_MODE = 'card';
|
||||||
|
const TABLE_MODE = 'table';
|
||||||
|
const mode = ref(DEFAULT_MODE);
|
||||||
|
const selected = ref([]);
|
||||||
|
const routeQuery = JSON.parse(route?.query[$props.searchUrl] ?? '{}');
|
||||||
|
const params = ref({ ...routeQuery, ...routeQuery.filter?.where });
|
||||||
|
const CrudModelRef = ref({});
|
||||||
|
const showForm = ref(false);
|
||||||
|
const splittedColumns = ref({ columns: [] });
|
||||||
|
const tableModes = [
|
||||||
|
{
|
||||||
|
icon: 'view_column',
|
||||||
|
title: t('table view'),
|
||||||
|
value: TABLE_MODE,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'grid_view',
|
||||||
|
title: t('grid view'),
|
||||||
|
value: DEFAULT_MODE,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
mode.value = quasar.platform.is.mobile ? DEFAULT_MODE : $props.defaultMode;
|
||||||
|
stateStore.rightDrawer = true;
|
||||||
|
setUserParams(route.query[$props.searchUrl]);
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => $props.columns,
|
||||||
|
(value) => splitColumns(value),
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
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) {
|
||||||
|
splittedColumns.value = {
|
||||||
|
columns: [],
|
||||||
|
chips: [],
|
||||||
|
create: [],
|
||||||
|
visible: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const col of columns) {
|
||||||
|
if (col.name == 'tableActions') splittedColumns.value.actions = col;
|
||||||
|
if (col.chip) splittedColumns.value.chips.push(col);
|
||||||
|
if (col.isTitle) splittedColumns.value.title = col;
|
||||||
|
if (col.create) splittedColumns.value.create.push(col);
|
||||||
|
if (col.cardVisible) splittedColumns.value.visible.push(col);
|
||||||
|
if ($props.isEditable && col.disable == null) col.disable = false;
|
||||||
|
if ($props.useModel) col.columnFilter = { ...col.columnFilter, inWhere: true };
|
||||||
|
splittedColumns.value.columns.push(col);
|
||||||
|
}
|
||||||
|
// Status column
|
||||||
|
if (splittedColumns.value.chips.length) {
|
||||||
|
splittedColumns.value.columnChips = splittedColumns.value.chips.filter(
|
||||||
|
(c) => !c.isId
|
||||||
|
);
|
||||||
|
if (splittedColumns.value.columnChips.length)
|
||||||
|
splittedColumns.value.columns.unshift({
|
||||||
|
align: 'left',
|
||||||
|
label: t('status'),
|
||||||
|
name: 'tableStatus',
|
||||||
|
columnFilter: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const rowClickFunction = computed(() => {
|
||||||
|
if ($props.rowClick) return $props.rowClick;
|
||||||
|
if ($props.redirect) return ({ id }) => redirectFn(id);
|
||||||
|
return () => {};
|
||||||
|
});
|
||||||
|
|
||||||
|
function redirectFn(id) {
|
||||||
|
router.push({ path: `/${$props.redirect}/${id}` });
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopEventPropagation(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
function reload(params) {
|
||||||
|
CrudModelRef.value.reload(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
function columnName(col) {
|
||||||
|
const column = { ...col, ...col.columnFilter };
|
||||||
|
let name = column.name;
|
||||||
|
if (column.alias) name = column.alias + '.' + name;
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getColAlign(col) {
|
||||||
|
return 'text-' + (col.align ?? 'left')
|
||||||
|
}
|
||||||
|
defineExpose({
|
||||||
|
reload,
|
||||||
|
redirect: redirectFn,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<QDrawer
|
||||||
|
v-if="$props.rightSearch"
|
||||||
|
v-model="stateStore.rightDrawer"
|
||||||
|
side="right"
|
||||||
|
:width="256"
|
||||||
|
show-if-above
|
||||||
|
>
|
||||||
|
<QScrollArea class="fit">
|
||||||
|
<VnFilterPanel
|
||||||
|
:data-key="$attrs['data-key']"
|
||||||
|
:search-button="true"
|
||||||
|
v-model="params"
|
||||||
|
:disable-submit-event="true"
|
||||||
|
:search-url="searchUrl"
|
||||||
|
>
|
||||||
|
<template #body>
|
||||||
|
<VnTableFilter
|
||||||
|
:column="col"
|
||||||
|
:data-key="$attrs['data-key']"
|
||||||
|
v-for="col of splittedColumns.columns"
|
||||||
|
:key="col.id"
|
||||||
|
v-model="params[columnName(col)]"
|
||||||
|
:search-url="searchUrl"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<slot
|
||||||
|
name="moreFilterPanel"
|
||||||
|
:params="params"
|
||||||
|
:columns="splittedColumns.columns"
|
||||||
|
/>
|
||||||
|
</VnFilterPanel>
|
||||||
|
</QScrollArea>
|
||||||
|
</QDrawer>
|
||||||
|
<!-- class in div to fix warn-->
|
||||||
|
<div class="q-px-md">
|
||||||
|
<CrudModel
|
||||||
|
v-bind="$attrs"
|
||||||
|
:limit="20"
|
||||||
|
ref="CrudModelRef"
|
||||||
|
:search-url="searchUrl"
|
||||||
|
:disable-infinite-scroll="mode == TABLE_MODE"
|
||||||
|
@save-changes="reload"
|
||||||
|
:has-subtoolbar="isEditable"
|
||||||
|
>
|
||||||
|
<template #body="{ rows }">
|
||||||
|
<QTable
|
||||||
|
v-bind="$attrs['QTable']"
|
||||||
|
class="vnTable"
|
||||||
|
:columns="splittedColumns.columns"
|
||||||
|
:rows="rows"
|
||||||
|
v-model:selected="selected"
|
||||||
|
:grid="mode != TABLE_MODE"
|
||||||
|
table-header-class="bg-header"
|
||||||
|
card-container-class="grid-three"
|
||||||
|
flat
|
||||||
|
:style="mode == TABLE_MODE && 'max-height: 90vh'"
|
||||||
|
virtual-scroll
|
||||||
|
@virtual-scroll="
|
||||||
|
(event) =>
|
||||||
|
event.index > rows.length - 2 &&
|
||||||
|
CrudModelRef.vnPaginateRef.paginate()
|
||||||
|
"
|
||||||
|
@row-click="(_, row) => rowClickFunction(row)"
|
||||||
|
>
|
||||||
|
<template #top-left>
|
||||||
|
<slot name="top-left"></slot>
|
||||||
|
</template>
|
||||||
|
<template #top-right>
|
||||||
|
<!-- <QBtn
|
||||||
|
icon="visibility"
|
||||||
|
title="asd"
|
||||||
|
class="bg-vn-section-color q-mr-md"
|
||||||
|
dense
|
||||||
|
v-if="mode == 'table'"
|
||||||
|
/> -->
|
||||||
|
<QBtnToggle
|
||||||
|
v-model="mode"
|
||||||
|
toggle-color="primary"
|
||||||
|
class="bg-vn-section-color"
|
||||||
|
dense
|
||||||
|
:options="tableModes"
|
||||||
|
/>
|
||||||
|
<QBtn
|
||||||
|
icon="filter_alt"
|
||||||
|
title="asd"
|
||||||
|
class="bg-vn-section-color q-ml-md"
|
||||||
|
dense
|
||||||
|
@click="stateStore.toggleRightDrawer()"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template #header-cell="{ col }">
|
||||||
|
<QTh
|
||||||
|
auto-width
|
||||||
|
style="min-width: 100px"
|
||||||
|
v-if="$props.columnSearch"
|
||||||
|
>
|
||||||
|
<VnTableFilter
|
||||||
|
:column="col"
|
||||||
|
:show-title="true"
|
||||||
|
:data-key="$attrs['data-key']"
|
||||||
|
v-model="params[columnName(col)]"
|
||||||
|
:search-url="searchUrl"
|
||||||
|
/>
|
||||||
|
</QTh>
|
||||||
|
</template>
|
||||||
|
<template #header-cell-tableActions>
|
||||||
|
<QTh auto-width class="sticky" />
|
||||||
|
</template>
|
||||||
|
<template #body-cell-tableStatus="{ col, row }">
|
||||||
|
<QTd auto-width :class="getColAlign(col)">
|
||||||
|
<VnTableChip
|
||||||
|
:columns="splittedColumns.columnChips"
|
||||||
|
:row="row"
|
||||||
|
>
|
||||||
|
<template #afterChip>
|
||||||
|
<slot name="afterChip" :row="row"></slot>
|
||||||
|
</template>
|
||||||
|
</VnTableChip>
|
||||||
|
</QTd>
|
||||||
|
</template>
|
||||||
|
<template #body-cell="{ col, row }">
|
||||||
|
<!-- Columns -->
|
||||||
|
<QTd
|
||||||
|
auto-width
|
||||||
|
class="no-margin q-px-xs"
|
||||||
|
:class="getColAlign(col)"
|
||||||
|
>
|
||||||
|
<VnTableColumn
|
||||||
|
:column="col"
|
||||||
|
:row="row"
|
||||||
|
:is-editable="false"
|
||||||
|
v-model="row[col.name]"
|
||||||
|
component-prop="columnField"
|
||||||
|
/>
|
||||||
|
</QTd>
|
||||||
|
</template>
|
||||||
|
<template #body-cell-tableActions="{ col, row }">
|
||||||
|
<QTd
|
||||||
|
auto-width
|
||||||
|
:class="getColAlign(col)"
|
||||||
|
class="sticky no-padding"
|
||||||
|
@click="stopEventPropagation($event)"
|
||||||
|
>
|
||||||
|
<QBtn
|
||||||
|
v-for="(btn, index) of col.actions"
|
||||||
|
:key="index"
|
||||||
|
:title="btn.title"
|
||||||
|
:icon="btn.icon"
|
||||||
|
class="q-px-sm"
|
||||||
|
flat
|
||||||
|
:class="
|
||||||
|
btn.isPrimary
|
||||||
|
? 'text-primary-light'
|
||||||
|
: 'color-vn-text '
|
||||||
|
"
|
||||||
|
@click="btn.action(row)"
|
||||||
|
/>
|
||||||
|
</QTd>
|
||||||
|
</template>
|
||||||
|
<template #item="{ row, colsMap }">
|
||||||
|
<component
|
||||||
|
:is="$props.redirect ? 'router-link' : 'span'"
|
||||||
|
:to="`/${$props.redirect}/` + row.id"
|
||||||
|
>
|
||||||
|
<QCard
|
||||||
|
bordered
|
||||||
|
flat
|
||||||
|
class="row no-wrap justify-between cursor-pointer"
|
||||||
|
@click="
|
||||||
|
(_, row) => {
|
||||||
|
$props.rowClick && $props.rowClick(row);
|
||||||
|
}
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<QCardSection
|
||||||
|
vertical
|
||||||
|
class="no-margin no-padding"
|
||||||
|
:class="colsMap.tableActions ? 'w-80' : 'fit'"
|
||||||
|
>
|
||||||
|
<!-- Chips -->
|
||||||
|
<QCardSection
|
||||||
|
v-if="splittedColumns.chips.length"
|
||||||
|
class="no-margin q-px-xs q-py-none"
|
||||||
|
>
|
||||||
|
<VnTableChip
|
||||||
|
:columns="splittedColumns.chips"
|
||||||
|
:row="row"
|
||||||
|
>
|
||||||
|
<template #afterChip>
|
||||||
|
<slot name="afterChip" :row="row"></slot>
|
||||||
|
</template>
|
||||||
|
</VnTableChip>
|
||||||
|
</QCardSection>
|
||||||
|
<!-- Title -->
|
||||||
|
<QCardSection
|
||||||
|
v-if="splittedColumns.title"
|
||||||
|
class="q-pl-sm q-py-none text-primary-light text-bold text-h6 cardEllipsis"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
:title="row[splittedColumns.title.name]"
|
||||||
|
@click="stopEventPropagation($event)"
|
||||||
|
class="cursor-text"
|
||||||
|
>
|
||||||
|
{{ row[splittedColumns.title.name] }}
|
||||||
|
</span>
|
||||||
|
</QCardSection>
|
||||||
|
<!-- Fields -->
|
||||||
|
<QCardSection
|
||||||
|
class="q-pl-sm q-pr-lg q-py-xs"
|
||||||
|
:class="$props.cardClass"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="col of splittedColumns.visible"
|
||||||
|
:key="col.name"
|
||||||
|
class="fields"
|
||||||
|
>
|
||||||
|
<VnLv
|
||||||
|
:label="
|
||||||
|
!col.component &&
|
||||||
|
col.label &&
|
||||||
|
`${col.label}:`
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<template #value>
|
||||||
|
<span
|
||||||
|
@click="
|
||||||
|
stopEventPropagation($event)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<VnTableColumn
|
||||||
|
:column="col"
|
||||||
|
:row="row"
|
||||||
|
:is-editable="false"
|
||||||
|
v-model="row[col.name]"
|
||||||
|
component-prop="columnField"
|
||||||
|
:show-label="true"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</VnLv>
|
||||||
|
</div>
|
||||||
|
</QCardSection>
|
||||||
|
</QCardSection>
|
||||||
|
<!-- Actions -->
|
||||||
|
<QCardSection
|
||||||
|
v-if="colsMap.tableActions"
|
||||||
|
class="column flex-center w-10 no-margin q-pa-xs q-gutter-y-xs"
|
||||||
|
@click="stopEventPropagation($event)"
|
||||||
|
>
|
||||||
|
<QBtn
|
||||||
|
v-for="(btn, index) of splittedColumns.actions
|
||||||
|
.actions"
|
||||||
|
:key="index"
|
||||||
|
:title="btn.title"
|
||||||
|
:icon="btn.icon"
|
||||||
|
class="q-pa-xs"
|
||||||
|
flat
|
||||||
|
:class="
|
||||||
|
btn.isPrimary
|
||||||
|
? 'text-primary-light'
|
||||||
|
: 'color-vn-text '
|
||||||
|
"
|
||||||
|
@click="btn.action(row)"
|
||||||
|
/>
|
||||||
|
</QCardSection>
|
||||||
|
</QCard>
|
||||||
|
</component>
|
||||||
|
</template>
|
||||||
|
</QTable>
|
||||||
|
</template>
|
||||||
|
</CrudModel>
|
||||||
|
</div>
|
||||||
|
<QPageSticky v-if="create" :offset="[20, 20]" style="z-index: 2">
|
||||||
|
<QBtn @click="showForm = !showForm" color="primary" fab icon="add" />
|
||||||
|
<QTooltip>
|
||||||
|
{{ create.title }}
|
||||||
|
</QTooltip>
|
||||||
|
</QPageSticky>
|
||||||
|
<QDialog v-model="showForm" transition-show="scale" transition-hide="scale">
|
||||||
|
<FormModelPopup
|
||||||
|
v-bind="create"
|
||||||
|
:model="$attrs['data-key'] + 'Create'"
|
||||||
|
@on-data-saved="(_, res) => create.onDataSaved(res)"
|
||||||
|
>
|
||||||
|
<template #form-inputs="{ data }">
|
||||||
|
<div class="grid-create">
|
||||||
|
<VnTableColumn
|
||||||
|
v-for="column of splittedColumns.create"
|
||||||
|
:key="column.name"
|
||||||
|
:column="column"
|
||||||
|
:row="{}"
|
||||||
|
default="input"
|
||||||
|
v-model="data[column.name]"
|
||||||
|
:show-label="true"
|
||||||
|
/>
|
||||||
|
<slot name="more-create-dialog" :data="data" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</FormModelPopup>
|
||||||
|
</QDialog>
|
||||||
|
</template>
|
||||||
|
<i18n>
|
||||||
|
en:
|
||||||
|
status: Status
|
||||||
|
es:
|
||||||
|
status: Estados
|
||||||
|
</i18n>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.bg-chip-secondary {
|
||||||
|
background-color: var(--vn-page-color);
|
||||||
|
color: var(--vn-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-header {
|
||||||
|
background-color: #5d5d5d;
|
||||||
|
color: var(--vn-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.q-table--dark .q-table__bottom,
|
||||||
|
.q-table--dark thead,
|
||||||
|
.q-table--dark tr,
|
||||||
|
.q-table--dark th,
|
||||||
|
.q-table--dark td {
|
||||||
|
border-color: #222222;
|
||||||
|
}
|
||||||
|
|
||||||
|
.q-table__container > div:first-child {
|
||||||
|
background-color: var(--vn-page-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-three {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(400px, max-content));
|
||||||
|
max-width: 100%;
|
||||||
|
grid-gap: 20px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-create {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(150px, max-content));
|
||||||
|
max-width: 100%;
|
||||||
|
grid-gap: 20px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-one {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row wrap;
|
||||||
|
div.fields {
|
||||||
|
width: 100%;
|
||||||
|
.vn-label-value {
|
||||||
|
display: flex;
|
||||||
|
gap: 2%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.q-table th {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vnTable {
|
||||||
|
thead tr th {
|
||||||
|
position: sticky;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
thead tr:first-child th {
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
.q-table__top {
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
tbody {
|
||||||
|
.q-checkbox {
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: 9px;
|
||||||
|
& .q-checkbox__label {
|
||||||
|
margin-left: 31px;
|
||||||
|
color: var(--vn-text-color);
|
||||||
|
}
|
||||||
|
& .q-checkbox__inner {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
color: var(--vn-label-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.sticky {
|
||||||
|
position: sticky;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
td.sticky {
|
||||||
|
background-color: var(--q-dark);
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.vn-label-value {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
color: var(--vn-text-color);
|
||||||
|
.value {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
pointer-events: all;
|
||||||
|
cursor: text;
|
||||||
|
user-select: all;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cardEllipsis {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
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 {
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.w-20 {
|
||||||
|
width: 20%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cursor-text {
|
||||||
|
pointer-events: all;
|
||||||
|
cursor: text;
|
||||||
|
user-select: all;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,6 +1,6 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { onBeforeMount, computed, watchEffect } from 'vue';
|
import { onBeforeMount, computed } 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,20 +41,6 @@ onBeforeMount(async () => {
|
||||||
if (!props.baseUrl) arrayData.store.filter.where = { id: route.params.id };
|
if (!props.baseUrl) arrayData.store.filter.where = { id: route.params.id };
|
||||||
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(() => {
|
|
||||||
if (Array.isArray(arrayData.store.data))
|
|
||||||
arrayData.store.data = arrayData.store.data[0];
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<QDrawer
|
<QDrawer
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
<script setup>
|
||||||
|
import { computed, defineModel } from 'vue';
|
||||||
|
|
||||||
|
const model = defineModel(undefined, { required: true });
|
||||||
|
const $props = defineProps({
|
||||||
|
prop: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
type: [Object, Number, String],
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const componentArray = computed(() => {
|
||||||
|
if (typeof $props.prop === 'object') return [$props.prop];
|
||||||
|
return $props.prop;
|
||||||
|
});
|
||||||
|
|
||||||
|
function mix(toComponent) {
|
||||||
|
const { component, attrs, event } = toComponent;
|
||||||
|
const customComponent = $props.components[component];
|
||||||
|
return {
|
||||||
|
component: customComponent?.component ?? component,
|
||||||
|
attrs: {
|
||||||
|
...toValueAttrs(attrs),
|
||||||
|
...toValueAttrs(customComponent?.attrs),
|
||||||
|
...toComponent,
|
||||||
|
...toValueAttrs(customComponent?.forceAttrs),
|
||||||
|
},
|
||||||
|
event: event ?? customComponent?.event,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function toValueAttrs(attrs) {
|
||||||
|
if (!attrs) return;
|
||||||
|
return typeof attrs == 'function' ? attrs($props.value) : attrs;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<span
|
||||||
|
v-for="toComponent of componentArray"
|
||||||
|
:key="toComponent.name"
|
||||||
|
class="column flex-center fit"
|
||||||
|
>
|
||||||
|
<component
|
||||||
|
v-if="toComponent?.component"
|
||||||
|
:is="mix(toComponent).component"
|
||||||
|
v-bind="mix(toComponent).attrs"
|
||||||
|
v-on="mix(toComponent).event ?? {}"
|
||||||
|
v-model="model"
|
||||||
|
class="fit"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</template>
|
|
@ -2,7 +2,12 @@
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue', 'update:options', 'keyup.enter']);
|
const emit = defineEmits([
|
||||||
|
'update:modelValue',
|
||||||
|
'update:options',
|
||||||
|
'keyup.enter',
|
||||||
|
'remove',
|
||||||
|
]);
|
||||||
|
|
||||||
const $props = defineProps({
|
const $props = defineProps({
|
||||||
modelValue: {
|
modelValue: {
|
||||||
|
|
|
@ -97,7 +97,7 @@ const styleAttrs = computed(() => {
|
||||||
<QIcon
|
<QIcon
|
||||||
name="close"
|
name="close"
|
||||||
size="xs"
|
size="xs"
|
||||||
v-if="hover && value"
|
v-if="hover && value && !readonly"
|
||||||
@click="onDateUpdate(null)"
|
@click="onDateUpdate(null)"
|
||||||
></QIcon>
|
></QIcon>
|
||||||
<QIcon name="event" class="cursor-pointer">
|
<QIcon name="event" class="cursor-pointer">
|
||||||
|
|
|
@ -16,11 +16,11 @@ const $props = defineProps({
|
||||||
},
|
},
|
||||||
optionLabel: {
|
optionLabel: {
|
||||||
type: [String],
|
type: [String],
|
||||||
default: '',
|
default: 'name',
|
||||||
},
|
},
|
||||||
optionValue: {
|
optionValue: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: 'id',
|
||||||
},
|
},
|
||||||
optionFilter: {
|
optionFilter: {
|
||||||
type: String,
|
type: String,
|
||||||
|
@ -116,7 +116,9 @@ async function fetchFilter(val) {
|
||||||
if (new RegExp(/\d/g).test(val)) key = optionFilter.value ?? optionValue.value;
|
if (new RegExp(/\d/g).test(val)) key = optionFilter.value ?? optionValue.value;
|
||||||
|
|
||||||
const where = { ...{ [key]: { like: `%${val}%` } }, ...$props.where };
|
const where = { ...{ [key]: { like: `%${val}%` } }, ...$props.where };
|
||||||
return dataRef.value.fetch({ fields, where, order: sortBy, limit });
|
const fetchOptions = { where, order: sortBy, limit };
|
||||||
|
if (fields) fetchOptions.fields = fields;
|
||||||
|
return dataRef.value.fetch(fetchOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function filterHandler(val, update) {
|
async function filterHandler(val, update) {
|
||||||
|
@ -178,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"
|
||||||
|
|
|
@ -1,25 +1,38 @@
|
||||||
|
<script setup>
|
||||||
|
defineProps({
|
||||||
|
columns: {
|
||||||
|
type: Number,
|
||||||
|
default: 6,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="q-pa-md w">
|
<div class="q-pa-md q-mx-md container">
|
||||||
<div class="row q-gutter-md q-mb-md">
|
<div class="row q-gutter-md q-mb-md justify-around no-wrap">
|
||||||
<QSkeleton type="rect" square />
|
<QSkeleton type="rect" square v-for="n in columns" :key="n" class="column" />
|
||||||
<QSkeleton type="rect" square />
|
</div>
|
||||||
<QSkeleton type="rect" square />
|
<div
|
||||||
<QSkeleton type="rect" square />
|
class="row q-gutter-md q-mb-md justify-around no-wrap"
|
||||||
<QSkeleton type="rect" square />
|
v-for="n in 5"
|
||||||
<QSkeleton type="rect" square />
|
:key="n"
|
||||||
</div>
|
>
|
||||||
<div class="row q-gutter-md q-mb-md" v-for="n in 5" :key="n">
|
<QSkeleton
|
||||||
<QSkeleton type="QInput" square />
|
type="QInput"
|
||||||
<QSkeleton type="QInput" square />
|
square
|
||||||
<QSkeleton type="QInput" square />
|
v-for="m in columns"
|
||||||
<QSkeleton type="QInput" square />
|
:key="m"
|
||||||
<QSkeleton type="QInput" square />
|
class="column"
|
||||||
<QSkeleton type="QInput" square />
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.w {
|
.container {
|
||||||
width: 80vw;
|
width: 100%;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
.column {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 200px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -4,11 +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 props = defineProps({
|
const params = defineModel({ default: {}, required: true, type: Object });
|
||||||
|
const $props = defineProps({
|
||||||
dataKey: {
|
dataKey: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
|
@ -18,11 +18,6 @@ const props = defineProps({
|
||||||
required: false,
|
required: false,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
params: {
|
|
||||||
type: Object,
|
|
||||||
required: false,
|
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
showAll: {
|
showAll: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true,
|
default: true,
|
||||||
|
@ -40,12 +35,20 @@ const props = defineProps({
|
||||||
},
|
},
|
||||||
hiddenTags: {
|
hiddenTags: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => [],
|
default: () => ['filter'],
|
||||||
},
|
},
|
||||||
customTags: {
|
customTags: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => [],
|
default: () => [],
|
||||||
},
|
},
|
||||||
|
disableSubmitEvent: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
searchUrl: {
|
||||||
|
type: String,
|
||||||
|
default: 'params',
|
||||||
|
},
|
||||||
redirect: {
|
redirect: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true,
|
default: true,
|
||||||
|
@ -54,61 +57,64 @@ const props = defineProps({
|
||||||
|
|
||||||
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 (props.params) userParams.value = JSON.parse(JSON.stringify(props.params));
|
emit('init', { params: params.value });
|
||||||
if (Object.keys(store.userParams).length > 0) {
|
|
||||||
userParams.value = JSON.parse(JSON.stringify(store.userParams));
|
|
||||||
}
|
|
||||||
emit('init', { params: userParams.value });
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function setUserParams(watchedParams) {
|
||||||
|
if (!watchedParams) return;
|
||||||
|
|
||||||
|
if (typeof watchedParams == 'string') watchedParams = JSON.parse(watchedParams);
|
||||||
|
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)
|
||||||
if (!val) {
|
);
|
||||||
userParams.value = {};
|
|
||||||
} else {
|
watch(
|
||||||
const parsedParams = JSON.parse(val);
|
() => arrayData.store.userParams,
|
||||||
userParams.value = { ...parsedParams };
|
(val) => setUserParams(val)
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const isLoading = ref(false);
|
const isLoading = ref(false);
|
||||||
async function search() {
|
async function search(evt) {
|
||||||
|
if (evt && $props.disableSubmitEvent) return;
|
||||||
|
|
||||||
store.filter.where = {};
|
store.filter.where = {};
|
||||||
isLoading.value = true;
|
isLoading.value = true;
|
||||||
const params = { ...userParams.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 });
|
const { params: newParams } = await arrayData.addFilter({ params: params.value });
|
||||||
userParams.value = newParams;
|
params.value = newParams;
|
||||||
|
|
||||||
if (!props.showAll && !Object.values(params).length) store.data = [];
|
if (!$props.showAll && !Object.values(filter).length) store.data = [];
|
||||||
|
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
emit('search');
|
emit('search');
|
||||||
if (props.redirect) 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');
|
||||||
if (props.redirect) navigate(store.data, {});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function clearFilters() {
|
async function clearFilters() {
|
||||||
|
@ -117,18 +123,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];
|
||||||
}
|
}
|
||||||
userParams.value = { ...newParams }; // Actualizar los params con los removibles
|
params.value = {};
|
||||||
await arrayData.applyFilter({ params: userParams.value });
|
params.value = { ...newParams }; // Actualizar los params con los removibles
|
||||||
|
await arrayData.applyFilter({ params: params.value });
|
||||||
|
|
||||||
if (!props.showAll) {
|
if (!$props.showAll) {
|
||||||
store.data = [];
|
store.data = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,36 +143,32 @@ async function clearFilters() {
|
||||||
emit('clear');
|
emit('clear');
|
||||||
}
|
}
|
||||||
|
|
||||||
const tagsList = computed(() =>
|
const tagsList = computed(() => {
|
||||||
Object.entries(userParams.value)
|
const tagList = [];
|
||||||
.filter(([key, value]) => value && !(props.hiddenTags || []).includes(key))
|
for (const key of Object.keys(params.value)) {
|
||||||
.map(([key, value]) => ({
|
const value = params.value[key];
|
||||||
label: key,
|
if (value == null || ($props.hiddenTags || []).includes(key)) continue;
|
||||||
value: value,
|
tagList.push({ label: key, value });
|
||||||
}))
|
}
|
||||||
);
|
return tagList;
|
||||||
|
});
|
||||||
|
|
||||||
const tags = computed(() =>
|
const tags = computed(() => {
|
||||||
tagsList.value.filter((tag) => !(props.customTags || []).includes(tag.label))
|
return tagsList.value.filter((tag) => !($props.customTags || []).includes(tag.key));
|
||||||
);
|
});
|
||||||
const customTags = computed(() =>
|
const customTags = computed(() =>
|
||||||
tagsList.value.filter((tag) => (props.customTags || []).includes(tag.label))
|
tagsList.value.filter((tag) => ($props.customTags || []).includes(tag.key))
|
||||||
);
|
);
|
||||||
|
|
||||||
async function remove(key) {
|
async function remove(key) {
|
||||||
userParams.value[key] = null;
|
params.value[key] = undefined;
|
||||||
await arrayData.applyFilter({ params: userParams.value });
|
search();
|
||||||
emit('remove', key);
|
emit('remove', key);
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatValue(value) {
|
function formatValue(value) {
|
||||||
if (typeof value === 'boolean') {
|
if (typeof value === 'boolean') return value ? t('Yes') : t('No');
|
||||||
return value ? t('Yes') : t('No');
|
if (isNaN(value) && !isNaN(Date.parse(value))) return toDate(value);
|
||||||
}
|
|
||||||
|
|
||||||
if (isNaN(value) && !isNaN(Date.parse(value))) {
|
|
||||||
return toDate(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return `"${value}"`;
|
return `"${value}"`;
|
||||||
}
|
}
|
||||||
|
@ -226,14 +229,14 @@ function formatValue(value) {
|
||||||
<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.label }}:</strong>
|
<strong>{{ chip.label }}:</strong>
|
||||||
<span>"{{ chip.value }}"</span>
|
<span>"{{ formatValue(chip.value) }}"</span>
|
||||||
</div>
|
</div>
|
||||||
</slot>
|
</slot>
|
||||||
</VnFilterPanelChip>
|
</VnFilterPanelChip>
|
||||||
<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"
|
||||||
|
@ -243,9 +246,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
|
||||||
|
@ -255,7 +258,7 @@ function formatValue(value) {
|
||||||
dense
|
dense
|
||||||
icon="search"
|
icon="search"
|
||||||
rounded
|
rounded
|
||||||
type="submit"
|
:type="disableSubmitEvent ? 'button' : 'submit'"
|
||||||
unelevated
|
unelevated
|
||||||
/>
|
/>
|
||||||
</QItemSection>
|
</QItemSection>
|
||||||
|
@ -269,7 +272,6 @@ function formatValue(value) {
|
||||||
color="primary"
|
color="primary"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.list {
|
.list {
|
||||||
width: 256px;
|
width: 256px;
|
||||||
|
|
|
@ -58,14 +58,19 @@ 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,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits(['onFetch', 'onPaginate']);
|
const emit = defineEmits(['onFetch', 'onPaginate', 'onChange']);
|
||||||
const isLoading = ref(false);
|
const isLoading = ref(false);
|
||||||
|
const mounted = ref(false);
|
||||||
const pagination = ref({
|
const pagination = ref({
|
||||||
sortBy: props.order,
|
sortBy: props.order,
|
||||||
rowsPerPage: props.limit,
|
rowsPerPage: props.limit,
|
||||||
|
@ -81,11 +86,13 @@ 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;
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(async () => {
|
||||||
if (props.autoLoad) fetch();
|
if (props.autoLoad) await fetch();
|
||||||
|
mounted.value = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
|
@ -95,11 +102,22 @@ watch(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => store.data,
|
||||||
|
(data) => emit('onChange', data)
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.url,
|
||||||
|
(url) => fetch({ url })
|
||||||
|
);
|
||||||
|
|
||||||
const addFilter = async (filter, params) => {
|
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 });
|
||||||
|
@ -107,6 +125,7 @@ async function fetch() {
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
}
|
}
|
||||||
emit('onFetch', store.data);
|
emit('onFetch', store.data);
|
||||||
|
return store.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function paginate() {
|
async function paginate() {
|
||||||
|
@ -138,7 +157,7 @@ function endPagination() {
|
||||||
emit('onPaginate');
|
emit('onPaginate');
|
||||||
}
|
}
|
||||||
async function onLoad(index, done) {
|
async function onLoad(index, done) {
|
||||||
if (!store.data) return done();
|
if (!store.data || !mounted.value) return done();
|
||||||
|
|
||||||
if (store.data.length === 0 || !props.url) return done(false);
|
if (store.data.length === 0 || !props.url) return done(false);
|
||||||
|
|
||||||
|
@ -150,7 +169,7 @@ async function onLoad(index, done) {
|
||||||
done(isDone);
|
done(isDone);
|
||||||
}
|
}
|
||||||
|
|
||||||
defineExpose({ fetch, addFilter });
|
defineExpose({ fetch, addFilter, paginate });
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -199,12 +218,6 @@ defineExpose({ fetch, addFilter });
|
||||||
<QSpinner color="orange" size="md" />
|
<QSpinner color="orange" size="md" />
|
||||||
</div>
|
</div>
|
||||||
</QInfiniteScroll>
|
</QInfiniteScroll>
|
||||||
<div
|
|
||||||
v-if="!isLoading && store.hasMoreData"
|
|
||||||
class="w-full flex justify-center q-mt-md"
|
|
||||||
>
|
|
||||||
<QBtn color="primary" :label="t('Load more data')" @click="paginate()" />
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
|
@ -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';
|
||||||
import { useStateStore } from 'src/stores/useStateStore';
|
import { useStateStore } from 'src/stores/useStateStore';
|
||||||
|
|
||||||
|
@ -18,17 +17,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: {
|
||||||
|
@ -73,10 +69,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,
|
||||||
|
@ -106,13 +112,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>
|
||||||
<template>
|
<template>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { onMounted, ref, computed } from 'vue';
|
import { onMounted, ref, computed } from 'vue';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRouter, useRoute } from 'vue-router';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { useArrayDataStore } from 'stores/useArrayDataStore';
|
import { useArrayDataStore } from 'stores/useArrayDataStore';
|
||||||
import { buildFilter } from 'filters/filterPanel';
|
import { buildFilter } from 'filters/filterPanel';
|
||||||
|
@ -13,6 +13,7 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
|
||||||
|
|
||||||
const store = arrayDataStore.get(key);
|
const store = arrayDataStore.get(key);
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
const router = useRouter();
|
||||||
let canceller = null;
|
let canceller = null;
|
||||||
|
|
||||||
const page = ref(1);
|
const page = ref(1);
|
||||||
|
@ -22,8 +23,13 @@ export function useArrayData(key = useRoute().meta.moduleName, 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 };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -40,13 +46,15 @@ export function useArrayData(key = useRoute().meta.moduleName, 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])
|
||||||
|
@ -87,8 +95,8 @@ export function useArrayData(key = useRoute().meta.moduleName, 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,
|
||||||
|
@ -118,6 +126,10 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function deleteOption(option) {
|
||||||
|
delete store[option];
|
||||||
|
}
|
||||||
|
|
||||||
function cancelRequest() {
|
function cancelRequest() {
|
||||||
if (canceller) {
|
if (canceller) {
|
||||||
canceller.abort();
|
canceller.abort();
|
||||||
|
@ -128,7 +140,7 @@ export function useArrayData(key = useRoute().meta.moduleName, 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;
|
||||||
|
@ -137,7 +149,7 @@ export function useArrayData(key = useRoute().meta.moduleName, 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;
|
||||||
|
@ -149,15 +161,20 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
|
||||||
return { filter, params };
|
return { filter, params };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function addFilterWhere(where) {
|
||||||
|
const storedFilter = { ...store.userFilter };
|
||||||
|
if (!storedFilter?.where) storedFilter.where = {};
|
||||||
|
where = { ...storedFilter.where, ...where };
|
||||||
|
await addFilter({ filter: { where } });
|
||||||
|
}
|
||||||
|
|
||||||
function sanitizerParams(params, exprBuilder) {
|
function sanitizerParams(params, exprBuilder) {
|
||||||
for (const param in params) {
|
for (const param in params) {
|
||||||
if (params[param] === '' || params[param] === null) {
|
if (params[param] === '' || params[param] === null) {
|
||||||
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;
|
||||||
|
@ -182,22 +199,34 @@ export function useArrayData(key = useRoute().meta.moduleName, 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 = JSON.stringify(store.userParams);
|
|
||||||
|
|
||||||
const url = new URL(window.location.href);
|
if (store.navigate) {
|
||||||
const { hash: currentHash } = url;
|
const { customRouteRedirectName, searchText } = store.navigate;
|
||||||
const [currentRoute] = currentHash.split('?');
|
if (customRouteRedirectName)
|
||||||
|
return router.push({
|
||||||
|
name: customRouteRedirectName,
|
||||||
|
params: { id: searchText },
|
||||||
|
});
|
||||||
|
const { matched: matches } = router.currentRoute.value;
|
||||||
|
const { path } = matches.at(-1);
|
||||||
|
|
||||||
const params = new URLSearchParams();
|
const to =
|
||||||
for (const param in query) params.append(param, query[param]);
|
store?.data?.length === 1
|
||||||
|
? path.replace(/\/(list|:id)|-list/, `/${store.data[0].id}`)
|
||||||
|
: path.replace(/:id.*/, '');
|
||||||
|
|
||||||
url.hash = currentRoute + '?' + params.toString();
|
if (route.path != to) {
|
||||||
window.history.pushState({}, '', url.hash);
|
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 totalRows = computed(() => (store.data && store.data.length) || 0);
|
||||||
|
@ -207,6 +236,7 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
|
||||||
fetch,
|
fetch,
|
||||||
applyFilter,
|
applyFilter,
|
||||||
addFilter,
|
addFilter,
|
||||||
|
addFilterWhere,
|
||||||
refresh,
|
refresh,
|
||||||
destroy,
|
destroy,
|
||||||
loadMore,
|
loadMore,
|
||||||
|
@ -214,5 +244,6 @@ export function useArrayData(key = useRoute().meta.moduleName, 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 };
|
|
||||||
}
|
|
|
@ -115,6 +115,13 @@ select:-webkit-autofill {
|
||||||
background-color: var(--vn-accent-color);
|
background-color: var(--vn-accent-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.text-primary-light {
|
||||||
|
color: $primary-light !important;
|
||||||
|
}
|
||||||
|
.bg-primary-light {
|
||||||
|
background: $primary-light !important;
|
||||||
|
}
|
||||||
|
|
||||||
.fill-icon {
|
.fill-icon {
|
||||||
font-variation-settings: 'FILL' 1;
|
font-variation-settings: 'FILL' 1;
|
||||||
}
|
}
|
||||||
|
@ -189,3 +196,26 @@ input::-webkit-inner-spin-button {
|
||||||
.q-scrollarea__content {
|
.q-scrollarea__content {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ===== Scrollbar CSS ===== /
|
||||||
|
/ Firefox */
|
||||||
|
|
||||||
|
* {
|
||||||
|
scrollbar-width: auto;
|
||||||
|
scrollbar-color: var(--vn-label-color) transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Chrome, Edge, and Safari */
|
||||||
|
*::-webkit-scrollbar {
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
*::-webkit-scrollbar-thumb {
|
||||||
|
background-color: var(--vn-label-color);
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
*::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
|
@ -28,7 +28,7 @@ $color-link: #66bfff;
|
||||||
$color-spacer-light: #a3a3a31f;
|
$color-spacer-light: #a3a3a31f;
|
||||||
$color-spacer: #7979794d;
|
$color-spacer: #7979794d;
|
||||||
$border-thin-light: 1px solid $color-spacer-light;
|
$border-thin-light: 1px solid $color-spacer-light;
|
||||||
$primary-light: lighten($primary, 35%);
|
$primary-light: #f5b351;
|
||||||
$dark-shadow-color: black;
|
$dark-shadow-color: black;
|
||||||
$layout-shadow-dark: 0 0 10px 2px #00000033, 0 0px 10px #0000003d;
|
$layout-shadow-dark: 0 0 10px 2px #00000033, 0 0px 10px #0000003d;
|
||||||
$spacing-md: 16px;
|
$spacing-md: 16px;
|
||||||
|
|
|
@ -279,8 +279,8 @@ customer:
|
||||||
extendedList:
|
extendedList:
|
||||||
tableVisibleColumns:
|
tableVisibleColumns:
|
||||||
id: Identifier
|
id: Identifier
|
||||||
name: Name
|
name: Comercial name
|
||||||
socialName: Social name
|
socialName: Business name
|
||||||
fi: Tax number
|
fi: Tax number
|
||||||
salesPersonFk: Salesperson
|
salesPersonFk: Salesperson
|
||||||
credit: Credit
|
credit: Credit
|
||||||
|
|
|
@ -278,7 +278,7 @@ customer:
|
||||||
extendedList:
|
extendedList:
|
||||||
tableVisibleColumns:
|
tableVisibleColumns:
|
||||||
id: Identificador
|
id: Identificador
|
||||||
name: Nombre
|
name: Nombre Comercial
|
||||||
socialName: Razón social
|
socialName: Razón social
|
||||||
fi: NIF / CIF
|
fi: NIF / CIF
|
||||||
salesPersonFk: Comercial
|
salesPersonFk: Comercial
|
||||||
|
|
|
@ -10,7 +10,7 @@ import CustomerFilter from '../CustomerFilter.vue';
|
||||||
:descriptor="CustomerDescriptor"
|
:descriptor="CustomerDescriptor"
|
||||||
:filter-panel="CustomerFilter"
|
:filter-panel="CustomerFilter"
|
||||||
search-data-key="CustomerList"
|
search-data-key="CustomerList"
|
||||||
search-url="Clients/filter"
|
search-url="Clients/extendedListFilter"
|
||||||
searchbar-label="Search customer"
|
searchbar-label="Search customer"
|
||||||
searchbar-info="You can search by customer id or name"
|
searchbar-info="You can search by customer id or name"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -29,7 +29,7 @@ const zones = ref();
|
||||||
@on-fetch="(data) => (workers = data)"
|
@on-fetch="(data) => (workers = data)"
|
||||||
auto-load
|
auto-load
|
||||||
/>
|
/>
|
||||||
<VnFilterPanel :data-key="props.dataKey" :search-button="true">
|
<VnFilterPanel :data-key="props.dataKey" :search-button="true" search-url="table">
|
||||||
<template #tags="{ tag, formatFn }">
|
<template #tags="{ tag, formatFn }">
|
||||||
<div class="q-gutter-x-xs">
|
<div class="q-gutter-x-xs">
|
||||||
<strong>{{ t(`params.${tag.label}`) }}: </strong>
|
<strong>{{ t(`params.${tag.label}`) }}: </strong>
|
||||||
|
|
|
@ -1,94 +1,439 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import { ref, computed, markRaw } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import VnPaginate from 'src/components/ui/VnPaginate.vue';
|
import VnTable from 'components/VnTable/VnTable.vue';
|
||||||
import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
|
import VnLocation from 'src/components/common/VnLocation.vue';
|
||||||
import CustomerFilter from './CustomerFilter.vue';
|
import VnSearchbar from 'components/ui/VnSearchbar.vue';
|
||||||
import VnLv from 'src/components/ui/VnLv.vue';
|
|
||||||
import CardList from 'src/components/ui/CardList.vue';
|
|
||||||
import VnLinkPhone from 'src/components/ui/VnLinkPhone.vue';
|
|
||||||
import { useSummaryDialog } from 'src/composables/useSummaryDialog';
|
|
||||||
import CustomerSummary from './Card/CustomerSummary.vue';
|
import CustomerSummary from './Card/CustomerSummary.vue';
|
||||||
import RightMenu from 'src/components/common/RightMenu.vue';
|
import { useSummaryDialog } from 'src/composables/useSummaryDialog';
|
||||||
|
|
||||||
|
import VnLinkPhone from 'src/components/ui/VnLinkPhone.vue';
|
||||||
|
|
||||||
|
import { toDate } from 'src/filters';
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const postcodesOptions = ref([]);
|
||||||
|
const tableRef = ref();
|
||||||
|
|
||||||
|
const columns = computed(() => [
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
name: 'id',
|
||||||
|
label: t('customer.extendedList.tableVisibleColumns.id'),
|
||||||
|
chip: {
|
||||||
|
condition: () => true,
|
||||||
|
},
|
||||||
|
isId: true,
|
||||||
|
columnFilter: {
|
||||||
|
component: 'select',
|
||||||
|
name: 'search',
|
||||||
|
attrs: {
|
||||||
|
url: 'Clients',
|
||||||
|
fields: ['id', 'name'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
label: t('customer.extendedList.tableVisibleColumns.name'),
|
||||||
|
name: 'name',
|
||||||
|
isTitle: true,
|
||||||
|
create: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
name: 'socialName',
|
||||||
|
label: t('customer.extendedList.tableVisibleColumns.socialName'),
|
||||||
|
isTitle: true,
|
||||||
|
create: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
label: t('customer.extendedList.tableVisibleColumns.fi'),
|
||||||
|
name: 'fi',
|
||||||
|
create: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
label: t('customer.extendedList.tableVisibleColumns.salesPersonFk'),
|
||||||
|
name: 'salesPersonFk',
|
||||||
|
component: 'select',
|
||||||
|
attrs: {
|
||||||
|
url: 'Workers/activeWithInheritedRole',
|
||||||
|
fields: ['id', 'name'],
|
||||||
|
where: { role: 'salesPerson' },
|
||||||
|
},
|
||||||
|
create: true,
|
||||||
|
columnField: {
|
||||||
|
component: null,
|
||||||
|
},
|
||||||
|
format: (row, dashIfEmpty) => dashIfEmpty(row.salesPerson),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
label: t('customer.extendedList.tableVisibleColumns.credit'),
|
||||||
|
name: 'credit',
|
||||||
|
component: 'number',
|
||||||
|
columnFilter: {
|
||||||
|
inWhere: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
label: t('customer.extendedList.tableVisibleColumns.creditInsurance'),
|
||||||
|
name: 'creditInsurance',
|
||||||
|
component: 'number',
|
||||||
|
columnFilter: {
|
||||||
|
inWhere: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
label: t('customer.extendedList.tableVisibleColumns.phone'),
|
||||||
|
name: 'phone',
|
||||||
|
cardVisible: true,
|
||||||
|
columnFilter: {
|
||||||
|
component: 'number',
|
||||||
|
},
|
||||||
|
columnField: {
|
||||||
|
component: null,
|
||||||
|
after: {
|
||||||
|
component: markRaw(VnLinkPhone),
|
||||||
|
attrs: (prop) => {
|
||||||
|
return {
|
||||||
|
'phone-number': prop,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
label: t('customer.extendedList.tableVisibleColumns.mobile'),
|
||||||
|
name: 'mobile',
|
||||||
|
cardVisible: true,
|
||||||
|
columnFilter: {
|
||||||
|
component: 'number',
|
||||||
|
inWhere: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
label: t('customer.extendedList.tableVisibleColumns.street'),
|
||||||
|
name: 'street',
|
||||||
|
create: true,
|
||||||
|
columnFilter: {
|
||||||
|
inWhere: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
label: t('customer.extendedList.tableVisibleColumns.countryFk'),
|
||||||
|
name: 'countryFk',
|
||||||
|
columnFilter: {
|
||||||
|
component: 'select',
|
||||||
|
inWhere: true,
|
||||||
|
alias: 'c',
|
||||||
|
attrs: {
|
||||||
|
url: 'Countries',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
format: (row, dashIfEmpty) => dashIfEmpty(row.country),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
label: t('customer.extendedList.tableVisibleColumns.provinceFk'),
|
||||||
|
name: 'provinceFk',
|
||||||
|
component: 'select',
|
||||||
|
attrs: {
|
||||||
|
url: 'Provinces',
|
||||||
|
},
|
||||||
|
columnField: {
|
||||||
|
component: null,
|
||||||
|
},
|
||||||
|
format: (row, dashIfEmpty) => dashIfEmpty(row.province),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
label: t('customer.extendedList.tableVisibleColumns.city'),
|
||||||
|
name: 'city',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
label: t('customer.extendedList.tableVisibleColumns.postcode'),
|
||||||
|
name: 'postcode',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
label: t('customer.extendedList.tableVisibleColumns.email'),
|
||||||
|
name: 'email',
|
||||||
|
cardVisible: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
label: t('customer.extendedList.tableVisibleColumns.created'),
|
||||||
|
name: 'created',
|
||||||
|
format: ({ created }) => toDate(created),
|
||||||
|
component: 'date',
|
||||||
|
columnFilter: {
|
||||||
|
alias: 'c',
|
||||||
|
inWhere: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
label: t('customer.extendedList.tableVisibleColumns.businessTypeFk'),
|
||||||
|
name: 'businessTypeFk',
|
||||||
|
create: true,
|
||||||
|
component: 'select',
|
||||||
|
attrs: {
|
||||||
|
url: 'BusinessTypes',
|
||||||
|
optionLabel: 'description',
|
||||||
|
optionValue: 'code',
|
||||||
|
},
|
||||||
|
columnField: {
|
||||||
|
component: null,
|
||||||
|
},
|
||||||
|
format: (row, dashIfEmpty) => dashIfEmpty(row.businessType),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
label: t('customer.extendedList.tableVisibleColumns.payMethodFk'),
|
||||||
|
name: 'payMethodFk',
|
||||||
|
columnFilter: {
|
||||||
|
component: 'select',
|
||||||
|
attrs: {
|
||||||
|
url: 'PayMethods',
|
||||||
|
},
|
||||||
|
inWhere: true,
|
||||||
|
},
|
||||||
|
format: (row, dashIfEmpty) => dashIfEmpty(row.payMethod),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
label: t('customer.extendedList.tableVisibleColumns.sageTaxTypeFk'),
|
||||||
|
name: 'sageTaxTypeFk',
|
||||||
|
columnFilter: {
|
||||||
|
component: 'select',
|
||||||
|
attrs: {
|
||||||
|
optionLabel: 'vat',
|
||||||
|
url: 'SageTaxTypes',
|
||||||
|
},
|
||||||
|
alias: 'sti',
|
||||||
|
inWhere: true,
|
||||||
|
},
|
||||||
|
format: (row, dashIfEmpty) => dashIfEmpty(row.sageTaxType),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
label: t('customer.extendedList.tableVisibleColumns.sageTransactionTypeFk'),
|
||||||
|
name: 'sageTransactionTypeFk',
|
||||||
|
columnFilter: {
|
||||||
|
component: 'select',
|
||||||
|
attrs: {
|
||||||
|
optionLabel: 'transaction',
|
||||||
|
url: 'SageTransactionTypes',
|
||||||
|
},
|
||||||
|
alias: 'stt',
|
||||||
|
inWhere: true,
|
||||||
|
},
|
||||||
|
format: (row, dashIfEmpty) => dashIfEmpty(row.sageTransactionType),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
label: t('customer.extendedList.tableVisibleColumns.isActive'),
|
||||||
|
name: 'isActive',
|
||||||
|
chip: {
|
||||||
|
color: null,
|
||||||
|
condition: (value) => !value,
|
||||||
|
icon: 'vn:disabled',
|
||||||
|
},
|
||||||
|
columnFilter: {
|
||||||
|
inWhere: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
label: t('customer.extendedList.tableVisibleColumns.isVies'),
|
||||||
|
name: 'isVies',
|
||||||
|
columnFilter: {
|
||||||
|
inWhere: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
label: t('customer.extendedList.tableVisibleColumns.isTaxDataChecked'),
|
||||||
|
name: 'isTaxDataChecked',
|
||||||
|
columnFilter: {
|
||||||
|
inWhere: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
label: t('customer.extendedList.tableVisibleColumns.isEqualizated'),
|
||||||
|
name: 'isEqualizated',
|
||||||
|
create: true,
|
||||||
|
columnFilter: {
|
||||||
|
inWhere: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
label: t('customer.extendedList.tableVisibleColumns.isFreezed'),
|
||||||
|
name: 'isFreezed',
|
||||||
|
chip: {
|
||||||
|
color: null,
|
||||||
|
condition: (value) => value,
|
||||||
|
icon: 'vn:frozen',
|
||||||
|
},
|
||||||
|
columnFilter: {
|
||||||
|
inWhere: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
label: t('customer.extendedList.tableVisibleColumns.hasToInvoice'),
|
||||||
|
name: 'hasToInvoice',
|
||||||
|
columnFilter: {
|
||||||
|
inWhere: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
label: t('customer.extendedList.tableVisibleColumns.hasToInvoiceByAddress'),
|
||||||
|
name: 'hasToInvoiceByAddress',
|
||||||
|
columnFilter: {
|
||||||
|
inWhere: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
label: t('customer.extendedList.tableVisibleColumns.isToBeMailed'),
|
||||||
|
name: 'isToBeMailed',
|
||||||
|
columnFilter: {
|
||||||
|
inWhere: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
label: t('customer.extendedList.tableVisibleColumns.hasLcr'),
|
||||||
|
name: 'hasLcr',
|
||||||
|
columnFilter: {
|
||||||
|
inWhere: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
label: t('customer.extendedList.tableVisibleColumns.hasCoreVnl'),
|
||||||
|
name: 'hasCoreVnl',
|
||||||
|
columnFilter: {
|
||||||
|
inWhere: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
label: t('customer.extendedList.tableVisibleColumns.hasSepaVnl'),
|
||||||
|
name: 'hasSepaVnl',
|
||||||
|
columnFilter: {
|
||||||
|
inWhere: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'right',
|
||||||
|
label: '',
|
||||||
|
name: 'tableActions',
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
title: t('Client ticket list'),
|
||||||
|
icon: 'vn:ticket',
|
||||||
|
action: redirectToCreateView,
|
||||||
|
isPrimary: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('Client ticket list'),
|
||||||
|
icon: 'preview',
|
||||||
|
action: (row) => viewSummary(row.id, CustomerSummary),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
const { viewSummary } = useSummaryDialog();
|
const { viewSummary } = useSummaryDialog();
|
||||||
|
const redirectToCreateView = (row) => {
|
||||||
function navigate(id) {
|
router.push({
|
||||||
router.push({ path: `/customer/${id}` });
|
name: 'TicketList',
|
||||||
}
|
query: {
|
||||||
|
params: JSON.stringify({
|
||||||
const redirectToCreateView = () => {
|
clientFk: row.id,
|
||||||
router.push({ name: 'CustomerCreate' });
|
}),
|
||||||
|
},
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function handleLocation(data, location) {
|
||||||
|
const { town, code, provinceFk, countryFk } = location ?? {};
|
||||||
|
data.postcode = code;
|
||||||
|
data.city = town;
|
||||||
|
data.provinceFk = provinceFk;
|
||||||
|
data.countryFk = countryFk;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<VnSearchbar
|
<VnSearchbar
|
||||||
:info="t('You can search by customer id or name')"
|
:info="t('You can search by customer id or name')"
|
||||||
:label="t('Search customer')"
|
:label="t('Search customer')"
|
||||||
data-key="CustomerList"
|
data-key="Customer"
|
||||||
/>
|
/>
|
||||||
<RightMenu>
|
<VnTable
|
||||||
<template #right-panel>
|
ref="tableRef"
|
||||||
<CustomerFilter data-key="CustomerList" />
|
data-key="Customer"
|
||||||
</template>
|
url="Clients/extendedListFilter"
|
||||||
</RightMenu>
|
:create="{
|
||||||
<QPage class="column items-center q-pa-md">
|
urlCreate: 'Clients/createWithUser',
|
||||||
<div class="vn-card-list">
|
title: 'Create client',
|
||||||
<VnPaginate
|
onDataSaved: ({ id }) => tableRef.redirect(id),
|
||||||
auto-load
|
formInitialData: {
|
||||||
data-key="CustomerList"
|
active: true,
|
||||||
order="id DESC"
|
isEqualizated: false,
|
||||||
url="/Clients/filter"
|
},
|
||||||
>
|
}"
|
||||||
<template #body="{ rows }">
|
order="id DESC"
|
||||||
<CardList
|
:columns="columns"
|
||||||
:id="row.id"
|
default-mode="table"
|
||||||
:key="row.id"
|
redirect="customer"
|
||||||
:title="row.name"
|
auto-load
|
||||||
@click="navigate(row.id)"
|
>
|
||||||
v-for="row of rows"
|
<template #more-create-dialog="{ data }">
|
||||||
>
|
<VnLocation
|
||||||
<template #list-items>
|
:roles-allowed-to-create="['deliveryAssistant']"
|
||||||
<VnLv :label="t('customer.list.email')" :value="row.email" />
|
:options="postcodesOptions"
|
||||||
<VnLv :value="row.phone">
|
v-model="data.location"
|
||||||
<template #label>
|
@update:model-value="(location) => handleLocation(data, location)"
|
||||||
{{ t('customer.list.phone') }}
|
/>
|
||||||
<VnLinkPhone :phone-number="row.phone" />
|
<QInput v-model="data.userName" :label="t('Web user')" />
|
||||||
</template>
|
<QInput :label="t('Email')" clearable type="email" v-model="data.email">
|
||||||
</VnLv>
|
<template #append>
|
||||||
</template>
|
<QIcon name="info" class="cursor-info">
|
||||||
<template #actions>
|
<QTooltip max-width="400px">{{
|
||||||
<QBtn
|
t('customer.basicData.youCanSaveMultipleEmails')
|
||||||
:label="t('components.smartCard.openCard')"
|
}}</QTooltip>
|
||||||
@click.stop="navigate(row.id)"
|
</QIcon>
|
||||||
outline
|
|
||||||
/>
|
|
||||||
<QBtn
|
|
||||||
:label="t('components.smartCard.openSummary')"
|
|
||||||
@click.stop="viewSummary(row.id, CustomerSummary)"
|
|
||||||
color="primary"
|
|
||||||
style="margin-top: 15px"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</CardList>
|
|
||||||
</template>
|
</template>
|
||||||
</VnPaginate>
|
</QInput>
|
||||||
</div>
|
</template>
|
||||||
<QPageSticky :offset="[20, 20]">
|
</VnTable>
|
||||||
<QBtn @click="redirectToCreateView()" color="primary" fab icon="add" />
|
|
||||||
<QTooltip>
|
|
||||||
{{ t('New client') }}
|
|
||||||
</QTooltip>
|
|
||||||
</QPageSticky>
|
|
||||||
</QPage>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<i18n>
|
<i18n>
|
||||||
es:
|
es:
|
||||||
Search customer: Buscar cliente
|
Web user: Usuario Web
|
||||||
You can search by customer id or name: Puedes buscar por id o nombre del cliente
|
|
||||||
New client: Nuevo cliente
|
|
||||||
</i18n>
|
</i18n>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.col-content {
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 6px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -1,619 +0,0 @@
|
||||||
<script setup>
|
|
||||||
import { ref, computed, onBeforeMount, onMounted } from 'vue';
|
|
||||||
import { useI18n } from 'vue-i18n';
|
|
||||||
import { useRouter } from 'vue-router';
|
|
||||||
|
|
||||||
import { QBtn, QIcon } from 'quasar';
|
|
||||||
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
|
|
||||||
import CustomerDescriptorProxy from 'src/pages/Customer/Card/CustomerDescriptorProxy.vue';
|
|
||||||
import CustomerExtendedListActions from './CustomerExtendedListActions.vue';
|
|
||||||
import CustomerExtendedListFilter from './CustomerExtendedListFilter.vue';
|
|
||||||
import TableVisibleColumns from 'src/components/common/TableVisibleColumns.vue';
|
|
||||||
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
|
|
||||||
import VnPaginate from 'src/components/ui/VnPaginate.vue';
|
|
||||||
import { useArrayData } from 'composables/useArrayData';
|
|
||||||
import { useStateStore } from 'stores/useStateStore';
|
|
||||||
import { toDate } from 'src/filters';
|
|
||||||
import RightMenu from 'src/components/common/RightMenu.vue';
|
|
||||||
|
|
||||||
const { t } = useI18n();
|
|
||||||
const router = useRouter();
|
|
||||||
const stateStore = useStateStore();
|
|
||||||
|
|
||||||
const arrayData = ref(null);
|
|
||||||
|
|
||||||
onBeforeMount(async () => {
|
|
||||||
arrayData.value = useArrayData('CustomerExtendedList', {
|
|
||||||
url: 'Clients/extendedListFilter',
|
|
||||||
limit: 0,
|
|
||||||
});
|
|
||||||
await arrayData.value.fetch({ append: false });
|
|
||||||
stateStore.rightDrawer = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
const filteredColumns = columns.value.filter(
|
|
||||||
(col) => col.name !== 'actions' && col.name !== 'customerStatus'
|
|
||||||
);
|
|
||||||
allColumnNames.value = filteredColumns.map((col) => col.name);
|
|
||||||
});
|
|
||||||
|
|
||||||
const selectedCustomerId = ref(0);
|
|
||||||
const selectedSalesPersonId = ref(0);
|
|
||||||
const allColumnNames = ref([]);
|
|
||||||
const visibleColumns = ref([]);
|
|
||||||
|
|
||||||
const tableColumnComponents = {
|
|
||||||
customerStatus: {
|
|
||||||
component: QIcon,
|
|
||||||
props: (prop) => ({
|
|
||||||
name: !prop.row.isActive
|
|
||||||
? 'vn:disabled'
|
|
||||||
: prop.row.isActive && prop.row.isFreezed
|
|
||||||
? 'vn:frozen'
|
|
||||||
: '',
|
|
||||||
color: 'primary',
|
|
||||||
size: 'sm',
|
|
||||||
}),
|
|
||||||
event: () => {},
|
|
||||||
},
|
|
||||||
id: {
|
|
||||||
component: QBtn,
|
|
||||||
props: () => ({ flat: true, color: 'blue' }),
|
|
||||||
event: (prop) => {
|
|
||||||
selectCustomerId(prop.row.id);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
name: {
|
|
||||||
component: 'span',
|
|
||||||
props: () => {},
|
|
||||||
event: () => {},
|
|
||||||
},
|
|
||||||
socialName: {
|
|
||||||
component: 'span',
|
|
||||||
props: () => {},
|
|
||||||
event: () => {},
|
|
||||||
},
|
|
||||||
fi: {
|
|
||||||
component: 'span',
|
|
||||||
props: () => {},
|
|
||||||
event: () => {},
|
|
||||||
},
|
|
||||||
salesPersonFk: {
|
|
||||||
component: QBtn,
|
|
||||||
props: () => ({ flat: true, color: 'blue' }),
|
|
||||||
event: (prop) => selectSalesPersonId(prop.row.salesPersonFk),
|
|
||||||
},
|
|
||||||
credit: {
|
|
||||||
component: 'span',
|
|
||||||
props: () => {},
|
|
||||||
event: () => {},
|
|
||||||
},
|
|
||||||
creditInsurance: {
|
|
||||||
component: 'span',
|
|
||||||
props: () => {},
|
|
||||||
event: () => {},
|
|
||||||
},
|
|
||||||
phone: {
|
|
||||||
component: 'span',
|
|
||||||
props: () => {},
|
|
||||||
event: () => {},
|
|
||||||
},
|
|
||||||
mobile: {
|
|
||||||
component: 'span',
|
|
||||||
props: () => {},
|
|
||||||
event: () => {},
|
|
||||||
},
|
|
||||||
street: {
|
|
||||||
component: 'span',
|
|
||||||
props: () => {},
|
|
||||||
event: () => {},
|
|
||||||
},
|
|
||||||
countryFk: {
|
|
||||||
component: 'span',
|
|
||||||
props: () => {},
|
|
||||||
event: () => {},
|
|
||||||
},
|
|
||||||
provinceFk: {
|
|
||||||
component: 'span',
|
|
||||||
props: () => {},
|
|
||||||
event: () => {},
|
|
||||||
},
|
|
||||||
city: {
|
|
||||||
component: 'span',
|
|
||||||
props: () => {},
|
|
||||||
event: () => {},
|
|
||||||
},
|
|
||||||
postcode: {
|
|
||||||
component: 'span',
|
|
||||||
props: () => {},
|
|
||||||
event: () => {},
|
|
||||||
},
|
|
||||||
email: {
|
|
||||||
component: 'span',
|
|
||||||
props: () => {},
|
|
||||||
event: () => {},
|
|
||||||
},
|
|
||||||
created: {
|
|
||||||
component: 'span',
|
|
||||||
props: () => {},
|
|
||||||
event: () => {},
|
|
||||||
},
|
|
||||||
businessTypeFk: {
|
|
||||||
component: 'span',
|
|
||||||
props: () => {},
|
|
||||||
event: () => {},
|
|
||||||
},
|
|
||||||
payMethodFk: {
|
|
||||||
component: 'span',
|
|
||||||
props: () => {},
|
|
||||||
event: () => {},
|
|
||||||
},
|
|
||||||
sageTaxTypeFk: {
|
|
||||||
component: 'span',
|
|
||||||
props: () => {},
|
|
||||||
event: () => {},
|
|
||||||
},
|
|
||||||
sageTransactionTypeFk: {
|
|
||||||
component: 'span',
|
|
||||||
props: () => {},
|
|
||||||
event: () => {},
|
|
||||||
},
|
|
||||||
isActive: {
|
|
||||||
component: QIcon,
|
|
||||||
props: (prop) => ({
|
|
||||||
name: prop.row.isActive ? 'check' : 'close',
|
|
||||||
color: prop.row.isActive ? 'positive' : 'negative',
|
|
||||||
size: 'sm',
|
|
||||||
}),
|
|
||||||
event: () => {},
|
|
||||||
},
|
|
||||||
isVies: {
|
|
||||||
component: QIcon,
|
|
||||||
props: (prop) => ({
|
|
||||||
name: prop.row.isVies ? 'check' : 'close',
|
|
||||||
color: prop.row.isVies ? 'positive' : 'negative',
|
|
||||||
size: 'sm',
|
|
||||||
}),
|
|
||||||
event: () => {},
|
|
||||||
},
|
|
||||||
isTaxDataChecked: {
|
|
||||||
component: QIcon,
|
|
||||||
props: (prop) => ({
|
|
||||||
name: prop.row.isTaxDataChecked ? 'check' : 'close',
|
|
||||||
color: prop.row.isTaxDataChecked ? 'positive' : 'negative',
|
|
||||||
size: 'sm',
|
|
||||||
}),
|
|
||||||
event: () => {},
|
|
||||||
},
|
|
||||||
isEqualizated: {
|
|
||||||
component: QIcon,
|
|
||||||
props: (prop) => ({
|
|
||||||
name: prop.row.isEqualizated ? 'check' : 'close',
|
|
||||||
color: prop.row.isEqualizated ? 'positive' : 'negative',
|
|
||||||
size: 'sm',
|
|
||||||
}),
|
|
||||||
event: () => {},
|
|
||||||
},
|
|
||||||
isFreezed: {
|
|
||||||
component: QIcon,
|
|
||||||
props: (prop) => ({
|
|
||||||
name: prop.row.isFreezed ? 'check' : 'close',
|
|
||||||
color: prop.row.isFreezed ? 'positive' : 'negative',
|
|
||||||
size: 'sm',
|
|
||||||
}),
|
|
||||||
event: () => {},
|
|
||||||
},
|
|
||||||
hasToInvoice: {
|
|
||||||
component: QIcon,
|
|
||||||
props: (prop) => ({
|
|
||||||
name: prop.row.hasToInvoice ? 'check' : 'close',
|
|
||||||
color: prop.row.hasToInvoice ? 'positive' : 'negative',
|
|
||||||
size: 'sm',
|
|
||||||
}),
|
|
||||||
event: () => {},
|
|
||||||
},
|
|
||||||
hasToInvoiceByAddress: {
|
|
||||||
component: QIcon,
|
|
||||||
props: (prop) => ({
|
|
||||||
name: prop.row.hasToInvoiceByAddress ? 'check' : 'close',
|
|
||||||
color: prop.row.hasToInvoiceByAddress ? 'positive' : 'negative',
|
|
||||||
size: 'sm',
|
|
||||||
}),
|
|
||||||
event: () => {},
|
|
||||||
},
|
|
||||||
isToBeMailed: {
|
|
||||||
component: QIcon,
|
|
||||||
props: (prop) => ({
|
|
||||||
name: prop.row.isToBeMailed ? 'check' : 'close',
|
|
||||||
color: prop.row.isToBeMailed ? 'positive' : 'negative',
|
|
||||||
size: 'sm',
|
|
||||||
}),
|
|
||||||
event: () => {},
|
|
||||||
},
|
|
||||||
hasLcr: {
|
|
||||||
component: QIcon,
|
|
||||||
props: (prop) => ({
|
|
||||||
name: prop.row.hasLcr ? 'check' : 'close',
|
|
||||||
color: prop.row.hasLcr ? 'positive' : 'negative',
|
|
||||||
size: 'sm',
|
|
||||||
}),
|
|
||||||
event: () => {},
|
|
||||||
},
|
|
||||||
hasCoreVnl: {
|
|
||||||
component: QIcon,
|
|
||||||
props: (prop) => ({
|
|
||||||
name: prop.row.hasCoreVnl ? 'check' : 'close',
|
|
||||||
color: prop.row.hasCoreVnl ? 'positive' : 'negative',
|
|
||||||
size: 'sm',
|
|
||||||
}),
|
|
||||||
event: () => {},
|
|
||||||
},
|
|
||||||
hasSepaVnl: {
|
|
||||||
component: QIcon,
|
|
||||||
props: (prop) => ({
|
|
||||||
name: prop.row.hasSepaVnl ? 'check' : 'close',
|
|
||||||
color: prop.row.hasSepaVnl ? 'positive' : 'negative',
|
|
||||||
size: 'sm',
|
|
||||||
}),
|
|
||||||
event: () => {},
|
|
||||||
},
|
|
||||||
actions: {
|
|
||||||
component: CustomerExtendedListActions,
|
|
||||||
props: (prop) => ({
|
|
||||||
id: prop.row.id,
|
|
||||||
}),
|
|
||||||
event: () => {},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const columns = computed(() => [
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
field: '',
|
|
||||||
label: '',
|
|
||||||
name: 'customerStatus',
|
|
||||||
format: () => ' ',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
field: 'id',
|
|
||||||
label: t('customer.extendedList.tableVisibleColumns.id'),
|
|
||||||
name: 'id',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
field: 'name',
|
|
||||||
label: t('customer.extendedList.tableVisibleColumns.name'),
|
|
||||||
name: 'name',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
field: 'socialName',
|
|
||||||
label: t('customer.extendedList.tableVisibleColumns.socialName'),
|
|
||||||
name: 'socialName',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
field: 'fi',
|
|
||||||
label: t('customer.extendedList.tableVisibleColumns.fi'),
|
|
||||||
name: 'fi',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
field: 'salesPerson',
|
|
||||||
label: t('customer.extendedList.tableVisibleColumns.salesPersonFk'),
|
|
||||||
name: 'salesPersonFk',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
field: 'credit',
|
|
||||||
label: t('customer.extendedList.tableVisibleColumns.credit'),
|
|
||||||
name: 'credit',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
field: 'creditInsurance',
|
|
||||||
label: t('customer.extendedList.tableVisibleColumns.creditInsurance'),
|
|
||||||
name: 'creditInsurance',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
field: 'phone',
|
|
||||||
label: t('customer.extendedList.tableVisibleColumns.phone'),
|
|
||||||
name: 'phone',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
field: 'mobile',
|
|
||||||
label: t('customer.extendedList.tableVisibleColumns.mobile'),
|
|
||||||
name: 'mobile',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
field: 'street',
|
|
||||||
label: t('customer.extendedList.tableVisibleColumns.street'),
|
|
||||||
name: 'street',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
field: 'country',
|
|
||||||
label: t('customer.extendedList.tableVisibleColumns.countryFk'),
|
|
||||||
name: 'countryFk',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
field: 'province',
|
|
||||||
label: t('customer.extendedList.tableVisibleColumns.provinceFk'),
|
|
||||||
name: 'provinceFk',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
field: 'city',
|
|
||||||
label: t('customer.extendedList.tableVisibleColumns.city'),
|
|
||||||
name: 'city',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
field: 'postcode',
|
|
||||||
label: t('customer.extendedList.tableVisibleColumns.postcode'),
|
|
||||||
name: 'postcode',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
field: 'email',
|
|
||||||
label: t('customer.extendedList.tableVisibleColumns.email'),
|
|
||||||
name: 'email',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
field: 'created',
|
|
||||||
label: t('customer.extendedList.tableVisibleColumns.created'),
|
|
||||||
name: 'created',
|
|
||||||
format: (value) => toDate(value),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
field: 'businessType',
|
|
||||||
label: t('customer.extendedList.tableVisibleColumns.businessTypeFk'),
|
|
||||||
name: 'businessTypeFk',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
field: 'payMethod',
|
|
||||||
label: t('customer.extendedList.tableVisibleColumns.payMethodFk'),
|
|
||||||
name: 'payMethodFk',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
field: 'sageTaxType',
|
|
||||||
label: t('customer.extendedList.tableVisibleColumns.sageTaxTypeFk'),
|
|
||||||
name: 'sageTaxTypeFk',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
field: 'sageTransactionType',
|
|
||||||
label: t('customer.extendedList.tableVisibleColumns.sageTransactionTypeFk'),
|
|
||||||
name: 'sageTransactionTypeFk',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
field: 'isActive',
|
|
||||||
label: t('customer.extendedList.tableVisibleColumns.isActive'),
|
|
||||||
name: 'isActive',
|
|
||||||
format: () => ' ',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
field: 'isVies',
|
|
||||||
label: t('customer.extendedList.tableVisibleColumns.isVies'),
|
|
||||||
name: 'isVies',
|
|
||||||
format: () => ' ',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
field: 'isTaxDataChecked',
|
|
||||||
label: t('customer.extendedList.tableVisibleColumns.isTaxDataChecked'),
|
|
||||||
name: 'isTaxDataChecked',
|
|
||||||
format: () => ' ',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
field: 'isEqualizated',
|
|
||||||
label: t('customer.extendedList.tableVisibleColumns.isEqualizated'),
|
|
||||||
name: 'isEqualizated',
|
|
||||||
format: () => ' ',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
field: 'isFreezed',
|
|
||||||
label: t('customer.extendedList.tableVisibleColumns.isFreezed'),
|
|
||||||
name: 'isFreezed',
|
|
||||||
format: () => ' ',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
field: 'hasToInvoice',
|
|
||||||
label: t('customer.extendedList.tableVisibleColumns.hasToInvoice'),
|
|
||||||
name: 'hasToInvoice',
|
|
||||||
format: () => ' ',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
field: 'hasToInvoiceByAddress',
|
|
||||||
label: t('customer.extendedList.tableVisibleColumns.hasToInvoiceByAddress'),
|
|
||||||
name: 'hasToInvoiceByAddress',
|
|
||||||
format: () => ' ',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
field: 'isToBeMailed',
|
|
||||||
label: t('customer.extendedList.tableVisibleColumns.isToBeMailed'),
|
|
||||||
name: 'isToBeMailed',
|
|
||||||
format: () => ' ',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
field: 'hasLcr',
|
|
||||||
label: t('customer.extendedList.tableVisibleColumns.hasLcr'),
|
|
||||||
name: 'hasLcr',
|
|
||||||
format: () => ' ',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
field: 'hasCoreVnl',
|
|
||||||
label: t('customer.extendedList.tableVisibleColumns.hasCoreVnl'),
|
|
||||||
name: 'hasCoreVnl',
|
|
||||||
format: () => ' ',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
field: 'hasSepaVnl',
|
|
||||||
label: t('customer.extendedList.tableVisibleColumns.hasSepaVnl'),
|
|
||||||
name: 'hasSepaVnl',
|
|
||||||
format: () => ' ',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'right',
|
|
||||||
field: 'actions',
|
|
||||||
label: '',
|
|
||||||
name: 'actions',
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
const stopEventPropagation = (event, col) => {
|
|
||||||
if (!['id', 'salesPersonFk'].includes(col.name)) return;
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
};
|
|
||||||
|
|
||||||
const navigateToTravelId = (id) => router.push({ path: `/customer/${id}` });
|
|
||||||
|
|
||||||
const selectCustomerId = (id) => (selectedCustomerId.value = id);
|
|
||||||
|
|
||||||
const selectSalesPersonId = (id) => (selectedSalesPersonId.value = id);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<RightMenu>
|
|
||||||
<template #right-panel>
|
|
||||||
<CustomerExtendedListFilter
|
|
||||||
v-if="visibleColumns.length !== 0"
|
|
||||||
data-key="CustomerExtendedList"
|
|
||||||
:visible-columns="visibleColumns"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</RightMenu>
|
|
||||||
<VnSubToolbar>
|
|
||||||
<template #st-data>
|
|
||||||
<TableVisibleColumns
|
|
||||||
:all-columns="allColumnNames"
|
|
||||||
table-code="clientsDetail"
|
|
||||||
labels-traductions-path="customer.extendedList.tableVisibleColumns"
|
|
||||||
@on-config-saved="
|
|
||||||
visibleColumns = ['customerStatus', ...$event, 'actions']
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</VnSubToolbar>
|
|
||||||
<QPage class="column items-center q-pa-md">
|
|
||||||
<VnPaginate
|
|
||||||
data-key="CustomerExtendedList"
|
|
||||||
url="Clients/extendedListFilter"
|
|
||||||
auto-load
|
|
||||||
>
|
|
||||||
<template #body="{ rows }">
|
|
||||||
<div class="q-pa-md">
|
|
||||||
<QTable
|
|
||||||
:columns="columns"
|
|
||||||
:rows="rows"
|
|
||||||
class="full-width q-mt-md"
|
|
||||||
row-key="id"
|
|
||||||
:visible-columns="visibleColumns"
|
|
||||||
@row-click="(evt, row, id) => navigateToTravelId(row.id)"
|
|
||||||
>
|
|
||||||
<template #body-cell="{ col, value }">
|
|
||||||
<QTd @click="stopEventPropagation($event, col)">
|
|
||||||
{{ value }}
|
|
||||||
</QTd>
|
|
||||||
</template>
|
|
||||||
<template #body-cell-customerStatus="props">
|
|
||||||
<QTd @click="stopEventPropagation($event, props.col)">
|
|
||||||
<component
|
|
||||||
:is="tableColumnComponents[props.col.name].component"
|
|
||||||
class="col-content"
|
|
||||||
v-bind="
|
|
||||||
tableColumnComponents[props.col.name].props(props)
|
|
||||||
"
|
|
||||||
@click="
|
|
||||||
tableColumnComponents[props.col.name].event(props)
|
|
||||||
"
|
|
||||||
>
|
|
||||||
</component>
|
|
||||||
</QTd>
|
|
||||||
</template>
|
|
||||||
<template #body-cell-id="props">
|
|
||||||
<QTd @click="stopEventPropagation($event, props.col)">
|
|
||||||
<component
|
|
||||||
:is="tableColumnComponents[props.col.name].component"
|
|
||||||
class="col-content"
|
|
||||||
v-bind="
|
|
||||||
tableColumnComponents[props.col.name].props(props)
|
|
||||||
"
|
|
||||||
@click="
|
|
||||||
tableColumnComponents[props.col.name].event(props)
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<CustomerDescriptorProxy :id="props.row.id" />
|
|
||||||
{{ props.row.id }}
|
|
||||||
</component>
|
|
||||||
</QTd>
|
|
||||||
</template>
|
|
||||||
<template #body-cell-salesPersonFk="props">
|
|
||||||
<QTd @click="stopEventPropagation($event, props.col)">
|
|
||||||
<component
|
|
||||||
v-if="props.row.salesPerson"
|
|
||||||
class="col-content"
|
|
||||||
:is="tableColumnComponents[props.col.name].component"
|
|
||||||
v-bind="
|
|
||||||
tableColumnComponents[props.col.name].props(props)
|
|
||||||
"
|
|
||||||
@click="
|
|
||||||
tableColumnComponents[props.col.name].event(props)
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<WorkerDescriptorProxy
|
|
||||||
:id="props.row.salesPersonFk"
|
|
||||||
/>
|
|
||||||
{{ props.row.salesPerson }}
|
|
||||||
</component>
|
|
||||||
<span class="col-content" v-else>-</span>
|
|
||||||
</QTd>
|
|
||||||
</template>
|
|
||||||
<template #body-cell-actions="props">
|
|
||||||
<QTd @click="stopEventPropagation($event, props.col)">
|
|
||||||
<component
|
|
||||||
:is="tableColumnComponents[props.col.name].component"
|
|
||||||
class="col-content"
|
|
||||||
v-bind="
|
|
||||||
tableColumnComponents[props.col.name].props(props)
|
|
||||||
"
|
|
||||||
@click="
|
|
||||||
tableColumnComponents[props.col.name].event(props)
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
</QTd>
|
|
||||||
</template>
|
|
||||||
</QTable>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</VnPaginate>
|
|
||||||
</QPage>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.col-content {
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 6px;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,60 +0,0 @@
|
||||||
<script setup>
|
|
||||||
import { useI18n } from 'vue-i18n';
|
|
||||||
import { useRouter } from 'vue-router';
|
|
||||||
import CustomerSummary from '../Card/CustomerSummary.vue';
|
|
||||||
import { useSummaryDialog } from 'src/composables/useSummaryDialog';
|
|
||||||
|
|
||||||
const { t } = useI18n();
|
|
||||||
const router = useRouter();
|
|
||||||
const { viewSummary } = useSummaryDialog();
|
|
||||||
|
|
||||||
const $props = defineProps({
|
|
||||||
id: {
|
|
||||||
type: Number,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const redirectToCreateView = () => {
|
|
||||||
router.push({
|
|
||||||
name: 'TicketList',
|
|
||||||
query: {
|
|
||||||
params: JSON.stringify({
|
|
||||||
clientFk: $props.id,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<QIcon
|
|
||||||
@click.stop="redirectToCreateView"
|
|
||||||
color="primary"
|
|
||||||
name="vn:ticket"
|
|
||||||
size="sm"
|
|
||||||
>
|
|
||||||
<QTooltip>
|
|
||||||
{{ t('Client ticket list') }}
|
|
||||||
</QTooltip>
|
|
||||||
</QIcon>
|
|
||||||
<QIcon
|
|
||||||
@click.stop="viewSummary($props.id, CustomerSummary)"
|
|
||||||
class="q-ml-md"
|
|
||||||
color="primary"
|
|
||||||
name="preview"
|
|
||||||
size="sm"
|
|
||||||
>
|
|
||||||
<QTooltip>
|
|
||||||
{{ t('Preview') }}
|
|
||||||
</QTooltip>
|
|
||||||
</QIcon>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<i18n>
|
|
||||||
es:
|
|
||||||
Client ticket list: Listado de tickets del cliente
|
|
||||||
Preview: Vista previa
|
|
||||||
</i18n>
|
|
|
@ -1,571 +0,0 @@
|
||||||
<script setup>
|
|
||||||
import { ref, computed } from 'vue';
|
|
||||||
import { useI18n } from 'vue-i18n';
|
|
||||||
|
|
||||||
import FetchData from 'components/FetchData.vue';
|
|
||||||
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
|
|
||||||
import VnSelect from 'components/common/VnSelect.vue';
|
|
||||||
import VnInput from 'src/components/common/VnInput.vue';
|
|
||||||
import VnInputDate from 'components/common/VnInputDate.vue';
|
|
||||||
import { dateRange } from 'src/filters';
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
dataKey: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
visibleColumns: {
|
|
||||||
type: Array,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const { t } = useI18n();
|
|
||||||
|
|
||||||
const clients = ref();
|
|
||||||
const workers = ref();
|
|
||||||
const countriesOptions = ref([]);
|
|
||||||
const provincesOptions = ref([]);
|
|
||||||
const paymethodsOptions = ref([]);
|
|
||||||
const businessTypesOptions = ref([]);
|
|
||||||
const sageTaxTypesOptions = ref([]);
|
|
||||||
const sageTransactionTypesOptions = ref([]);
|
|
||||||
|
|
||||||
const visibleColumnsSet = computed(() => new Set(props.visibleColumns));
|
|
||||||
|
|
||||||
const exprBuilder = (param, value) => {
|
|
||||||
switch (param) {
|
|
||||||
case 'created':
|
|
||||||
return {
|
|
||||||
'c.created': {
|
|
||||||
between: dateRange(value),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
case 'id':
|
|
||||||
case 'name':
|
|
||||||
case 'socialName':
|
|
||||||
case 'fi':
|
|
||||||
case 'credit':
|
|
||||||
case 'creditInsurance':
|
|
||||||
case 'phone':
|
|
||||||
case 'mobile':
|
|
||||||
case 'street':
|
|
||||||
case 'city':
|
|
||||||
case 'postcode':
|
|
||||||
case 'email':
|
|
||||||
case 'isActive':
|
|
||||||
case 'isVies':
|
|
||||||
case 'isTaxDataChecked':
|
|
||||||
case 'isEqualizated':
|
|
||||||
case 'isFreezed':
|
|
||||||
case 'hasToInvoice':
|
|
||||||
case 'hasToInvoiceByAddress':
|
|
||||||
case 'isToBeMailed':
|
|
||||||
case 'hasSepaVnl':
|
|
||||||
case 'hasLcr':
|
|
||||||
case 'hasCoreVnl':
|
|
||||||
case 'countryFk':
|
|
||||||
case 'provinceFk':
|
|
||||||
case 'salesPersonFk':
|
|
||||||
case 'businessTypeFk':
|
|
||||||
case 'payMethodFk':
|
|
||||||
case 'sageTaxTypeFk':
|
|
||||||
case 'sageTransactionTypeFk':
|
|
||||||
return { [`c.${param}`]: value };
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const shouldRenderColumn = (colName) => {
|
|
||||||
return visibleColumnsSet.value.has(colName);
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<FetchData
|
|
||||||
url="Clients"
|
|
||||||
:filter="{ where: { role: 'socialName' } }"
|
|
||||||
@on-fetch="(data) => (clients = data)"
|
|
||||||
auto-load
|
|
||||||
/>
|
|
||||||
<FetchData
|
|
||||||
url="Workers/activeWithInheritedRole"
|
|
||||||
:filter="{ where: { role: 'salesPerson' } }"
|
|
||||||
@on-fetch="(data) => (workers = data)"
|
|
||||||
auto-load
|
|
||||||
/>
|
|
||||||
<FetchData
|
|
||||||
url="Workers/activeWithInheritedRole"
|
|
||||||
:filter="{ where: { role: 'salesPerson' } }"
|
|
||||||
@on-fetch="(data) => (workers = data)"
|
|
||||||
auto-load
|
|
||||||
/>
|
|
||||||
<FetchData
|
|
||||||
url="Countries"
|
|
||||||
:filter="{ fields: ['id', 'country'], order: 'country ASC' }"
|
|
||||||
@on-fetch="(data) => (countriesOptions = data)"
|
|
||||||
auto-load
|
|
||||||
/>
|
|
||||||
<FetchData
|
|
||||||
ref="provincesFetchDataRef"
|
|
||||||
@on-fetch="(data) => (provincesOptions = data)"
|
|
||||||
auto-load
|
|
||||||
url="Provinces"
|
|
||||||
/>
|
|
||||||
<FetchData
|
|
||||||
url="Paymethods"
|
|
||||||
@on-fetch="(data) => (paymethodsOptions = data)"
|
|
||||||
auto-load
|
|
||||||
/>
|
|
||||||
<FetchData
|
|
||||||
url="BusinessTypes"
|
|
||||||
@on-fetch="(data) => (businessTypesOptions = data)"
|
|
||||||
auto-load
|
|
||||||
/>
|
|
||||||
<FetchData
|
|
||||||
url="SageTaxTypes"
|
|
||||||
auto-load
|
|
||||||
@on-fetch="(data) => (sageTaxTypesOptions = data)"
|
|
||||||
/>
|
|
||||||
<FetchData
|
|
||||||
url="sageTransactionTypes"
|
|
||||||
auto-load
|
|
||||||
@on-fetch="(data) => (sageTransactionTypesOptions = data)"
|
|
||||||
/>
|
|
||||||
<VnFilterPanel
|
|
||||||
:data-key="props.dataKey"
|
|
||||||
:search-button="true"
|
|
||||||
:expr-builder="exprBuilder"
|
|
||||||
>
|
|
||||||
<template #tags="{ tag, formatFn }">
|
|
||||||
<div class="q-gutter-x-xs">
|
|
||||||
<strong
|
|
||||||
>{{ t(`customer.extendedList.tableVisibleColumns.${tag.label}`) }}:
|
|
||||||
</strong>
|
|
||||||
<span>{{ formatFn(tag.value) }}</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template #body="{ params, searchFn }">
|
|
||||||
<QItem v-if="shouldRenderColumn('id')">
|
|
||||||
<QItemSection>
|
|
||||||
<VnInput
|
|
||||||
:label="t('customer.extendedList.tableVisibleColumns.id')"
|
|
||||||
v-model="params.id"
|
|
||||||
is-outlined
|
|
||||||
clearable
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
</QItem>
|
|
||||||
<QItem v-if="shouldRenderColumn('name')">
|
|
||||||
<QItemSection>
|
|
||||||
<VnInput
|
|
||||||
:label="t('customer.extendedList.tableVisibleColumns.name')"
|
|
||||||
v-model="params.name"
|
|
||||||
is-outlined
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
</QItem>
|
|
||||||
<!-- <QItem class="q-mb-sm">
|
|
||||||
<QItemSection v-if="!clients">
|
|
||||||
<QSkeleton type="QInput" class="full-width" />
|
|
||||||
</QItemSection>
|
|
||||||
<QItemSection v-if="clients">
|
|
||||||
<VnSelect
|
|
||||||
:label="t('Social name')"
|
|
||||||
v-model="params.socialName"
|
|
||||||
@update:model-value="searchFn()"
|
|
||||||
:options="clients"
|
|
||||||
option-value="socialName"
|
|
||||||
option-label="socialName"
|
|
||||||
emit-value
|
|
||||||
map-options
|
|
||||||
use-input
|
|
||||||
hide-selected
|
|
||||||
dense
|
|
||||||
outlined
|
|
||||||
rounded
|
|
||||||
:input-debounce="0"
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
</QItem> -->
|
|
||||||
<QItem v-if="shouldRenderColumn('fi')">
|
|
||||||
<QItemSection>
|
|
||||||
<VnInput
|
|
||||||
:label="t('customer.extendedList.tableVisibleColumns.fi')"
|
|
||||||
v-model="params.fi"
|
|
||||||
is-outlined
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
</QItem>
|
|
||||||
<QItem v-if="shouldRenderColumn('salesPersonFk')">
|
|
||||||
<QItemSection v-if="!workers">
|
|
||||||
<QSkeleton type="QInput" class="full-width" />
|
|
||||||
</QItemSection>
|
|
||||||
<QItemSection v-if="workers">
|
|
||||||
<VnSelect
|
|
||||||
:label="
|
|
||||||
t('customer.extendedList.tableVisibleColumns.salesPersonFk')
|
|
||||||
"
|
|
||||||
v-model="params.salesPersonFk"
|
|
||||||
@update:model-value="searchFn()"
|
|
||||||
:options="workers"
|
|
||||||
option-value="id"
|
|
||||||
option-label="name"
|
|
||||||
emit-value
|
|
||||||
map-options
|
|
||||||
use-input
|
|
||||||
hide-selected
|
|
||||||
dense
|
|
||||||
outlined
|
|
||||||
rounded
|
|
||||||
:input-debounce="0"
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
</QItem>
|
|
||||||
<QItem v-if="shouldRenderColumn('credit')">
|
|
||||||
<QItemSection>
|
|
||||||
<VnInput
|
|
||||||
:label="t('customer.extendedList.tableVisibleColumns.credit')"
|
|
||||||
v-model="params.credit"
|
|
||||||
is-outlined
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
</QItem>
|
|
||||||
<QItem v-if="shouldRenderColumn('creditInsurance')">
|
|
||||||
<QItemSection>
|
|
||||||
<VnInput
|
|
||||||
:label="
|
|
||||||
t('customer.extendedList.tableVisibleColumns.creditInsurance')
|
|
||||||
"
|
|
||||||
v-model="params.creditInsurance"
|
|
||||||
is-outlined
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
</QItem>
|
|
||||||
<QItem v-if="shouldRenderColumn('phone')">
|
|
||||||
<QItemSection>
|
|
||||||
<VnInput
|
|
||||||
:label="t('customer.extendedList.tableVisibleColumns.phone')"
|
|
||||||
v-model="params.phone"
|
|
||||||
is-outlined
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
</QItem>
|
|
||||||
<QItem v-if="shouldRenderColumn('mobile')">
|
|
||||||
<QItemSection>
|
|
||||||
<VnInput
|
|
||||||
:label="t('customer.extendedList.tableVisibleColumns.mobile')"
|
|
||||||
v-model="params.mobile"
|
|
||||||
is-outlined
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
</QItem>
|
|
||||||
<QItem v-if="shouldRenderColumn('street')">
|
|
||||||
<QItemSection>
|
|
||||||
<VnInput
|
|
||||||
:label="t('customer.extendedList.tableVisibleColumns.street')"
|
|
||||||
v-model="params.street"
|
|
||||||
is-outlined
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
</QItem>
|
|
||||||
<QItem v-if="shouldRenderColumn('countryFk')">
|
|
||||||
<QItemSection>
|
|
||||||
<VnSelect
|
|
||||||
:label="t('customer.extendedList.tableVisibleColumns.countryFk')"
|
|
||||||
v-model="params.countryFk"
|
|
||||||
@update:model-value="searchFn()"
|
|
||||||
:options="countriesOptions"
|
|
||||||
option-value="id"
|
|
||||||
option-label="country"
|
|
||||||
map-options
|
|
||||||
hide-selected
|
|
||||||
dense
|
|
||||||
outlined
|
|
||||||
rounded
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
</QItem>
|
|
||||||
<QItem v-if="shouldRenderColumn('provinceFk')">
|
|
||||||
<QItemSection>
|
|
||||||
<VnSelect
|
|
||||||
:label="t('customer.extendedList.tableVisibleColumns.provinceFk')"
|
|
||||||
v-model="params.provinceFk"
|
|
||||||
@update:model-value="searchFn()"
|
|
||||||
:options="provincesOptions"
|
|
||||||
option-value="id"
|
|
||||||
option-label="name"
|
|
||||||
map-options
|
|
||||||
hide-selected
|
|
||||||
dense
|
|
||||||
outlined
|
|
||||||
rounded
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
</QItem>
|
|
||||||
<QItem v-if="shouldRenderColumn('city')">
|
|
||||||
<QItemSection>
|
|
||||||
<VnInput
|
|
||||||
:label="t('customer.extendedList.tableVisibleColumns.city')"
|
|
||||||
v-model="params.city"
|
|
||||||
is-outlined
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
</QItem>
|
|
||||||
<QItem v-if="shouldRenderColumn('postcode')">
|
|
||||||
<QItemSection>
|
|
||||||
<VnInput
|
|
||||||
:label="t('customer.extendedList.tableVisibleColumns.postcode')"
|
|
||||||
v-model="params.postcode"
|
|
||||||
is-outlined
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
</QItem>
|
|
||||||
<QItem v-if="shouldRenderColumn('email')">
|
|
||||||
<QItemSection>
|
|
||||||
<VnInput
|
|
||||||
:label="t('customer.extendedList.tableVisibleColumns.email')"
|
|
||||||
v-model="params.email"
|
|
||||||
is-outlined
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
</QItem>
|
|
||||||
|
|
||||||
<QItem v-if="shouldRenderColumn('created')">
|
|
||||||
<QItemSection>
|
|
||||||
<VnInputDate
|
|
||||||
v-model="params.created"
|
|
||||||
:label="t('customer.extendedList.tableVisibleColumns.created')"
|
|
||||||
@update:model-value="searchFn()"
|
|
||||||
is-outlined
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
</QItem>
|
|
||||||
<QItem v-if="shouldRenderColumn('businessTypeFk')">
|
|
||||||
<QItemSection>
|
|
||||||
<VnSelect
|
|
||||||
:label="
|
|
||||||
t('customer.extendedList.tableVisibleColumns.businessTypeFk')
|
|
||||||
"
|
|
||||||
v-model="params.businessTypeFk"
|
|
||||||
:options="businessTypesOptions"
|
|
||||||
@update:model-value="searchFn()"
|
|
||||||
option-value="code"
|
|
||||||
option-label="description"
|
|
||||||
map-options
|
|
||||||
hide-selected
|
|
||||||
dense
|
|
||||||
outlined
|
|
||||||
rounded
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
</QItem>
|
|
||||||
<QItem v-if="shouldRenderColumn('payMethodFk')">
|
|
||||||
<QItemSection>
|
|
||||||
<VnSelect
|
|
||||||
:label="
|
|
||||||
t('customer.extendedList.tableVisibleColumns.payMethodFk')
|
|
||||||
"
|
|
||||||
v-model="params.payMethodFk"
|
|
||||||
:options="paymethodsOptions"
|
|
||||||
@update:model-value="searchFn()"
|
|
||||||
option-value="id"
|
|
||||||
option-label="name"
|
|
||||||
map-options
|
|
||||||
hide-selected
|
|
||||||
dense
|
|
||||||
outlined
|
|
||||||
rounded
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
</QItem>
|
|
||||||
<QItem v-if="shouldRenderColumn('sageTaxTypeFk')">
|
|
||||||
<QItemSection>
|
|
||||||
<VnSelect
|
|
||||||
:label="
|
|
||||||
t('customer.extendedList.tableVisibleColumns.sageTaxTypeFk')
|
|
||||||
"
|
|
||||||
v-model="params.sageTaxTypeFk"
|
|
||||||
@update:model-value="searchFn()"
|
|
||||||
:options="sageTaxTypesOptions"
|
|
||||||
option-value="id"
|
|
||||||
option-label="vat"
|
|
||||||
map-options
|
|
||||||
hide-selected
|
|
||||||
dense
|
|
||||||
outlined
|
|
||||||
rounded
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
</QItem>
|
|
||||||
<QItem v-if="shouldRenderColumn('sageTransactionTypeFk')">
|
|
||||||
<QItemSection>
|
|
||||||
<VnSelect
|
|
||||||
:label="
|
|
||||||
t(
|
|
||||||
'customer.extendedList.tableVisibleColumns.sageTransactionTypeFk'
|
|
||||||
)
|
|
||||||
"
|
|
||||||
v-model="params.sageTransactionTypeFk"
|
|
||||||
@update:model-value="searchFn()"
|
|
||||||
:options="sageTransactionTypesOptions"
|
|
||||||
option-value="id"
|
|
||||||
option-label="transaction"
|
|
||||||
map-options
|
|
||||||
hide-selected
|
|
||||||
dense
|
|
||||||
outlined
|
|
||||||
rounded
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
</QItem>
|
|
||||||
<QItem v-if="shouldRenderColumn('isActive') || shouldRenderColumn('isVies')">
|
|
||||||
<QItemSection v-if="shouldRenderColumn('isActive')">
|
|
||||||
<QCheckbox
|
|
||||||
v-model="params.isActive"
|
|
||||||
@update:model-value="searchFn()"
|
|
||||||
:label="t('customer.extendedList.tableVisibleColumns.isActive')"
|
|
||||||
toggle-indeterminate
|
|
||||||
:false-value="undefined"
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
<QItemSection v-if="shouldRenderColumn('isVies')">
|
|
||||||
<QCheckbox
|
|
||||||
v-model="params.isVies"
|
|
||||||
@update:model-value="searchFn()"
|
|
||||||
:label="t('customer.extendedList.tableVisibleColumns.isVies')"
|
|
||||||
toggle-indeterminate
|
|
||||||
:false-value="undefined"
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
</QItem>
|
|
||||||
<QItem
|
|
||||||
v-if="
|
|
||||||
shouldRenderColumn('isEqualizated') ||
|
|
||||||
shouldRenderColumn('isTaxDataChecked')
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<QItemSection v-if="shouldRenderColumn('isTaxDataChecked')">
|
|
||||||
<QCheckbox
|
|
||||||
v-model="params.isTaxDataChecked"
|
|
||||||
@update:model-value="searchFn()"
|
|
||||||
:label="
|
|
||||||
t(
|
|
||||||
'customer.extendedList.tableVisibleColumns.isTaxDataChecked'
|
|
||||||
)
|
|
||||||
"
|
|
||||||
toggle-indeterminate
|
|
||||||
:false-value="undefined"
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
<QItemSection v-if="shouldRenderColumn('isEqualizated')">
|
|
||||||
<QCheckbox
|
|
||||||
v-model="params.isEqualizated"
|
|
||||||
@update:model-value="searchFn()"
|
|
||||||
:label="
|
|
||||||
t('customer.extendedList.tableVisibleColumns.isEqualizated')
|
|
||||||
"
|
|
||||||
toggle-indeterminate
|
|
||||||
:false-value="undefined"
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
</QItem>
|
|
||||||
<QItem
|
|
||||||
v-if="
|
|
||||||
shouldRenderColumn('hasToInvoice') || shouldRenderColumn('isFreezed')
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<QItemSection v-if="shouldRenderColumn('isFreezed')">
|
|
||||||
<QCheckbox
|
|
||||||
v-model="params.isFreezed"
|
|
||||||
@update:model-value="searchFn()"
|
|
||||||
:label="t('customer.extendedList.tableVisibleColumns.isFreezed')"
|
|
||||||
toggle-indeterminate
|
|
||||||
:false-value="undefined"
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
<QItemSection v-if="shouldRenderColumn('hasToInvoice')">
|
|
||||||
<QCheckbox
|
|
||||||
v-model="params.hasToInvoice"
|
|
||||||
@update:model-value="searchFn()"
|
|
||||||
:label="
|
|
||||||
t('customer.extendedList.tableVisibleColumns.hasToInvoice')
|
|
||||||
"
|
|
||||||
toggle-indeterminate
|
|
||||||
:false-value="undefined"
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
</QItem>
|
|
||||||
<QItem
|
|
||||||
v-if="
|
|
||||||
shouldRenderColumn('isToBeMailed') ||
|
|
||||||
shouldRenderColumn('hasToInvoiceByAddress')
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<QItemSection v-if="shouldRenderColumn('hasToInvoiceByAddress')">
|
|
||||||
<QCheckbox
|
|
||||||
v-model="params.hasToInvoiceByAddress"
|
|
||||||
@update:model-value="searchFn()"
|
|
||||||
:label="
|
|
||||||
t(
|
|
||||||
'customer.extendedList.tableVisibleColumns.hasToInvoiceByAddress'
|
|
||||||
)
|
|
||||||
"
|
|
||||||
toggle-indeterminate
|
|
||||||
:false-value="undefined"
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
<QItemSection v-if="shouldRenderColumn('isToBeMailed')">
|
|
||||||
<QCheckbox
|
|
||||||
v-model="params.isToBeMailed"
|
|
||||||
@update:model-value="searchFn()"
|
|
||||||
:label="
|
|
||||||
t('customer.extendedList.tableVisibleColumns.isToBeMailed')
|
|
||||||
"
|
|
||||||
toggle-indeterminate
|
|
||||||
:false-value="undefined"
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
</QItem>
|
|
||||||
<QItem
|
|
||||||
v-if="shouldRenderColumn('hasLcr') || shouldRenderColumn('hasCoreVnl')"
|
|
||||||
>
|
|
||||||
<QItemSection v-if="shouldRenderColumn('hasLcr')">
|
|
||||||
<QCheckbox
|
|
||||||
v-model="params.hasLcr"
|
|
||||||
@update:model-value="searchFn()"
|
|
||||||
:label="t('customer.extendedList.tableVisibleColumns.hasLcr')"
|
|
||||||
toggle-indeterminate
|
|
||||||
:false-value="undefined"
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
<QItemSection v-if="shouldRenderColumn('hasCoreVnl')">
|
|
||||||
<QCheckbox
|
|
||||||
v-model="params.hasCoreVnl"
|
|
||||||
@update:model-value="searchFn()"
|
|
||||||
:label="t('customer.extendedList.tableVisibleColumns.hasCoreVnl')"
|
|
||||||
toggle-indeterminate
|
|
||||||
:false-value="undefined"
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
</QItem>
|
|
||||||
<QItem v-if="shouldRenderColumn('hasSepaVnl')">
|
|
||||||
<QItemSection>
|
|
||||||
<QCheckbox
|
|
||||||
v-model="params.hasSepaVnl"
|
|
||||||
@update:model-value="searchFn()"
|
|
||||||
:label="t('customer.extendedList.tableVisibleColumns.hasSepaVnl')"
|
|
||||||
toggle-indeterminate
|
|
||||||
:false-value="undefined"
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
</QItem>
|
|
||||||
|
|
||||||
<QSeparator />
|
|
||||||
</template>
|
|
||||||
</VnFilterPanel>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<i18n>
|
|
||||||
es:
|
|
||||||
Social name: Razón social
|
|
||||||
</i18n>
|
|
|
@ -10,8 +10,10 @@ import FetchData from 'src/components/FetchData.vue';
|
||||||
import VnSelect from 'src/components/common/VnSelect.vue';
|
import VnSelect from 'src/components/common/VnSelect.vue';
|
||||||
import VnCurrency from 'src/components/common/VnCurrency.vue';
|
import VnCurrency from 'src/components/common/VnCurrency.vue';
|
||||||
import { toCurrency } from 'src/filters';
|
import { toCurrency } from 'src/filters';
|
||||||
|
import useNotify from 'src/composables/useNotify.js';
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
const { notify } = useNotify();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const arrayData = useArrayData();
|
const arrayData = useArrayData();
|
||||||
const invoiceIn = computed(() => arrayData.store.data);
|
const invoiceIn = computed(() => arrayData.store.data);
|
||||||
|
@ -69,6 +71,7 @@ const isNotEuro = (code) => code != 'EUR';
|
||||||
async function insert() {
|
async function insert() {
|
||||||
await axios.post('/InvoiceInDueDays/new', { id: +invoiceId });
|
await axios.post('/InvoiceInDueDays/new', { id: +invoiceId });
|
||||||
await invoiceInFormRef.value.reload();
|
await invoiceInFormRef.value.reload();
|
||||||
|
notify(t('globals.dataSaved'), 'positive');
|
||||||
}
|
}
|
||||||
const getTotalAmount = (rows) => rows.reduce((acc, { amount }) => acc + +amount, 0);
|
const getTotalAmount = (rows) => rows.reduce((acc, { amount }) => acc + +amount, 0);
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -20,8 +20,8 @@ const { t } = useI18n();
|
||||||
const quasar = useQuasar();
|
const quasar = useQuasar();
|
||||||
const entityId = computed(() => $props.id || route.params.id);
|
const entityId = computed(() => $props.id || route.params.id);
|
||||||
const URL_KEY = 'NotificationSubscriptions';
|
const URL_KEY = 'NotificationSubscriptions';
|
||||||
const active = ref();
|
const active = ref(new Map());
|
||||||
const available = ref();
|
const available = ref(new Map());
|
||||||
|
|
||||||
async function toggleNotification(notification) {
|
async function toggleNotification(notification) {
|
||||||
try {
|
try {
|
||||||
|
@ -56,6 +56,7 @@ const swapEntry = (from, to, key) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
function setNotifications(data) {
|
function setNotifications(data) {
|
||||||
|
console.log('data: ', data);
|
||||||
active.value = new Map(data.active);
|
active.value = new Map(data.active);
|
||||||
available.value = new Map(data.available);
|
available.value = new Map(data.available);
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,6 @@ export default {
|
||||||
main: [
|
main: [
|
||||||
'CustomerList',
|
'CustomerList',
|
||||||
'CustomerPayments',
|
'CustomerPayments',
|
||||||
'CustomerExtendedList',
|
|
||||||
'CustomerNotifications',
|
'CustomerNotifications',
|
||||||
'CustomerDefaulter',
|
'CustomerDefaulter',
|
||||||
],
|
],
|
||||||
|
@ -70,18 +69,6 @@ export default {
|
||||||
component: () =>
|
component: () =>
|
||||||
import('src/pages/Customer/Payments/CustomerPayments.vue'),
|
import('src/pages/Customer/Payments/CustomerPayments.vue'),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: 'extendedList',
|
|
||||||
name: 'CustomerExtendedList',
|
|
||||||
meta: {
|
|
||||||
title: 'extendedList',
|
|
||||||
icon: 'vn:client',
|
|
||||||
},
|
|
||||||
component: () =>
|
|
||||||
import(
|
|
||||||
'src/pages/Customer/ExtendedList/CustomerExtendedList.vue'
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: 'notifications',
|
path: 'notifications',
|
||||||
name: 'CustomerNotifications',
|
name: 'CustomerNotifications',
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ describe('InvoiceInDueDay', () => {
|
||||||
cy.waitForElement('thead');
|
cy.waitForElement('thead');
|
||||||
cy.get(addBtn).click();
|
cy.get(addBtn).click();
|
||||||
|
|
||||||
cy.saveCard();
|
cy.get('tbody > :nth-child(1)').should('exist');
|
||||||
cy.get('.q-notification__message').should('have.text', 'Data saved');
|
cy.get('.q-notification__message').should('have.text', 'Data saved');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,31 +1,98 @@
|
||||||
import { describe, expect, it, beforeAll } from 'vitest';
|
import { describe, expect, it, beforeEach, afterEach, vi } from 'vitest';
|
||||||
import { axios } from 'app/test/vitest/helper';
|
import { axios, flushPromises } from 'app/test/vitest/helper';
|
||||||
import { useArrayData } from 'composables/useArrayData';
|
import { useArrayData } from 'composables/useArrayData';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
import * as vueRouter from 'vue-router';
|
||||||
|
|
||||||
describe('useArrayData', () => {
|
describe('useArrayData', () => {
|
||||||
let arrayData;
|
const filter = '{"order":"","limit":10,"skip":0}';
|
||||||
beforeAll(() => {
|
const params = { supplierFk: 2 };
|
||||||
axios.get.mockResolvedValue({ data: [] });
|
beforeEach(() => {
|
||||||
arrayData = useArrayData('InvoiceIn', { url: 'invoice-in/list' });
|
vi.spyOn(useRouter(), 'replace');
|
||||||
Object.defineProperty(window.location, 'href', {
|
vi.spyOn(useRouter(), 'push');
|
||||||
writable: true,
|
});
|
||||||
value: 'localhost:9000/invoice-in/list',
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fetch and repalce url with new params', async () => {
|
||||||
|
vi.spyOn(axios, 'get').mockReturnValueOnce({ data: [] });
|
||||||
|
|
||||||
|
const arrayData = useArrayData('ArrayData', { url: 'mockUrl' });
|
||||||
|
|
||||||
|
arrayData.store.userParams = params;
|
||||||
|
arrayData.fetch({});
|
||||||
|
|
||||||
|
await flushPromises();
|
||||||
|
const routerReplace = useRouter().replace.mock.calls[0][0];
|
||||||
|
|
||||||
|
expect(axios.get.mock.calls[0][1].params).toEqual({
|
||||||
|
filter,
|
||||||
|
supplierFk: 2,
|
||||||
|
});
|
||||||
|
expect(routerReplace.path).toEqual('mockSection/list');
|
||||||
|
expect(JSON.parse(routerReplace.query.params)).toEqual(
|
||||||
|
expect.objectContaining(params)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should get data and send new URL without keeping parameters, if there is only one record', async () => {
|
||||||
|
vi.spyOn(axios, 'get').mockReturnValueOnce({ data: [{ id: 1 }] });
|
||||||
|
|
||||||
|
const arrayData = useArrayData('ArrayData', { url: 'mockUrl', navigate: {} });
|
||||||
|
|
||||||
|
arrayData.store.userParams = params;
|
||||||
|
arrayData.fetch({});
|
||||||
|
|
||||||
|
await flushPromises();
|
||||||
|
const routerPush = useRouter().push.mock.calls[0][0];
|
||||||
|
|
||||||
|
expect(axios.get.mock.calls[0][1].params).toEqual({
|
||||||
|
filter,
|
||||||
|
supplierFk: 2,
|
||||||
|
});
|
||||||
|
expect(routerPush.path).toEqual('mockName/1');
|
||||||
|
expect(routerPush.query).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should get data and send new URL keeping parameters, if you have more than one record', async () => {
|
||||||
|
vi.spyOn(axios, 'get').mockReturnValueOnce({ data: [{ id: 1 }, { id: 2 }] });
|
||||||
|
|
||||||
|
vi.spyOn(vueRouter, 'useRoute').mockReturnValue({
|
||||||
|
matched: [],
|
||||||
|
query: {},
|
||||||
|
params: {},
|
||||||
|
meta: { moduleName: 'mockName' },
|
||||||
|
path: 'mockName/1',
|
||||||
|
});
|
||||||
|
vi.spyOn(vueRouter, 'useRouter').mockReturnValue({
|
||||||
|
push: vi.fn(),
|
||||||
|
replace: vi.fn(),
|
||||||
|
currentRoute: {
|
||||||
|
value: {
|
||||||
|
params: {
|
||||||
|
id: 1,
|
||||||
|
},
|
||||||
|
meta: { moduleName: 'mockName' },
|
||||||
|
matched: [{ path: 'mockName/:id' }],
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Mock the window.history.pushState method within useArrayData
|
const arrayData = useArrayData('ArrayData', { url: 'mockUrl', navigate: {} });
|
||||||
window.history.pushState = (data, title, url) => (window.location.href = url);
|
|
||||||
|
|
||||||
// Mock the URL constructor within useArrayData
|
arrayData.store.userParams = params;
|
||||||
global.URL = class URL {
|
arrayData.fetch({});
|
||||||
constructor(url) {
|
|
||||||
this.hash = url.split('localhost:9000/')[1];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should add the params to the url', async () => {
|
await flushPromises();
|
||||||
arrayData.store.userParams = { supplierFk: 2 };
|
const routerPush = useRouter().push.mock.calls[0][0];
|
||||||
arrayData.updateStateParams();
|
|
||||||
expect(window.location.href).contain('params=%7B%22supplierFk%22%3A2%7D');
|
expect(axios.get.mock.calls[0][1].params).toEqual({
|
||||||
|
filter,
|
||||||
|
supplierFk: 2,
|
||||||
|
});
|
||||||
|
expect(routerPush.path).toEqual('mockName/');
|
||||||
|
expect(routerPush.query.params).toBeDefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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' });
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -15,16 +15,19 @@ installQuasarPlugin({
|
||||||
});
|
});
|
||||||
const pinia = createTestingPinia({ createSpy: vi.fn, stubActions: false });
|
const pinia = createTestingPinia({ createSpy: vi.fn, stubActions: false });
|
||||||
const mockPush = vi.fn();
|
const mockPush = vi.fn();
|
||||||
|
const mockReplace = vi.fn();
|
||||||
|
|
||||||
vi.mock('vue-router', () => ({
|
vi.mock('vue-router', () => ({
|
||||||
useRouter: () => ({
|
useRouter: () => ({
|
||||||
push: mockPush,
|
push: mockPush,
|
||||||
|
replace: mockReplace,
|
||||||
currentRoute: {
|
currentRoute: {
|
||||||
value: {
|
value: {
|
||||||
params: {
|
params: {
|
||||||
id: 1,
|
id: 1,
|
||||||
},
|
},
|
||||||
meta: { moduleName: 'mockName' },
|
meta: { moduleName: 'mockName' },
|
||||||
|
matched: [{ path: 'mockName/list' }],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
@ -33,6 +36,7 @@ vi.mock('vue-router', () => ({
|
||||||
query: {},
|
query: {},
|
||||||
params: {},
|
params: {},
|
||||||
meta: { moduleName: 'mockName' },
|
meta: { moduleName: 'mockName' },
|
||||||
|
path: 'mockSection/list',
|
||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue