#6825 - VnTable V1 #396
|
@ -27,7 +27,6 @@ export default {
|
|||
this.$el.addEventListener('keyup', function (evt) {
|
||||
if (evt.key === 'Enter') {
|
||||
const input = evt.target;
|
||||
console.log('input', input);
|
||||
if (input.type == 'textarea' && evt.shiftKey) {
|
||||
evt.preventDefault();
|
||||
let { selectionStart, selectionEnd } = input;
|
||||
|
|
|
@ -67,6 +67,10 @@ const $props = defineProps({
|
|||
default: '',
|
||||
description: 'It is used for redirect on click "save and continue"',
|
||||
},
|
||||
hasSubtoolbar: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
});
|
||||
|
||||
const isLoading = ref(false);
|
||||
|
@ -75,6 +79,7 @@ const originalData = ref();
|
|||
const vnPaginateRef = ref();
|
||||
const formData = ref();
|
||||
const saveButtonRef = ref(null);
|
||||
const watchChanges = ref();
|
||||
const formUrl = computed(() => $props.url);
|
||||
|
||||
const emit = defineEmits(['onFetch', 'update:selected', 'saveChanges']);
|
||||
|
@ -89,6 +94,7 @@ defineExpose({
|
|||
saveChanges,
|
||||
getChanges,
|
||||
formData,
|
||||
vnPaginateRef,
|
||||
});
|
||||
|
||||
async function fetch(data) {
|
||||
|
@ -97,19 +103,26 @@ async function fetch(data) {
|
|||
data.map((d) => (d.$index = $index++));
|
||||
}
|
||||
|
||||
originalData.value = data && JSON.parse(JSON.stringify(data));
|
||||
formData.value = data && JSON.parse(JSON.stringify(data));
|
||||
watch(formData, () => (hasChanges.value = true), { deep: true });
|
||||
resetData(data);
|
||||
|
||||
emit('onFetch', 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() {
|
||||
await fetch(originalData.value);
|
||||
hasChanges.value = false;
|
||||
}
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
|
||||
function filter(value, update, filterOptions) {
|
||||
update(
|
||||
() => {
|
||||
|
@ -271,8 +284,9 @@ function isEmpty(obj) {
|
|||
if (obj.length > 0) return false;
|
||||
}
|
||||
|
||||
async function reload() {
|
||||
vnPaginateRef.value.fetch();
|
||||
async function reload(params) {
|
||||
const data = await vnPaginateRef.value.fetch(params);
|
||||
fetch(data);
|
||||
}
|
||||
|
||||
watch(formUrl, async () => {
|
||||
|
@ -284,10 +298,11 @@ watch(formUrl, async () => {
|
|||
<VnPaginate
|
||||
:url="url"
|
||||
:limit="limit"
|
||||
v-bind="$attrs"
|
||||
@on-fetch="fetch"
|
||||
@on-change="resetData"
|
||||
:skeleton="false"
|
||||
ref="vnPaginateRef"
|
||||
v-bind="$attrs"
|
||||
>
|
||||
<template #body v-if="formData">
|
||||
<slot
|
||||
|
@ -298,8 +313,8 @@ watch(formUrl, async () => {
|
|||
></slot>
|
||||
</template>
|
||||
</VnPaginate>
|
||||
<SkeletonTable v-if="!formData" />
|
||||
<Teleport to="#st-actions" v-if="stateStore?.isSubToolbarShown()">
|
||||
<SkeletonTable v-if="!formData" :columns="$attrs.columns?.length" />
|
||||
<Teleport to="#st-actions" v-if="stateStore?.isSubToolbarShown() && hasSubtoolbar">
|
||||
<QBtnGroup push style="column-gap: 10px">
|
||||
<slot name="moreBeforeActions" />
|
||||
<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"
|
||||
alexm marked this conversation as resolved
Outdated
|
||||
@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>
|
||||
alexm marked this conversation as resolved
jsegarra
commented
Duda, si hay uno, podemos quitar el nombre? Duda, si hay uno, podemos quitar el nombre?
Porque revisando una instancia me ha dado a entender que había mas de uno, y no es así.
alexm
commented
pongo un beforeChip asi estan las dos posibilidades pongo un beforeChip asi estan las dos posibilidades
|
||||
<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 });
|
||||
alexm marked this conversation as resolved
Outdated
jsegarra
commented
Me sale un warning en consola Me sale un warning en consola
alexm
commented
He tenido que poner He tenido que poner `const model = defineModel(undefined, { required: true });` para que no sale el warning, yo creo que es mas cosa de el linter...
De hecho lo uso como esta en la documentación: https://vuejs.org/guide/components/v-model
|
||||
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 });
|
||||
alexm marked this conversation as resolved
Outdated
jsegarra
commented
warning warning
jsegarra
commented
Yo te iba a sugerir esta linea Yo te iba a sugerir esta linea `// eslint-disable-next-line vue/require-prop-types`
|
||||
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),
|
||||
};
|
||||
|
||||
alexm marked this conversation as resolved
Outdated
jsegarra
commented
Hay muchas lineas/props repetidas Mi propuesta es definir un componente base donde se añadan atributos que sean genericos
Para input quedaría algo así:
Hay muchas lineas/props repetidas
Mi propuesta es definir un componente base donde se añadan atributos que sean genericos
```
const baseComponent = (component, event) => ({
component: markRaw(component),
event,
forceAttrs: {
label: $props.showTitle ? '' : $props.column.label,
},
attrs: {
class: 'q-px-sm q-pb-xs q-pt-none',
dense: true,
filled: !$props.showTitle,
},
});
```
Para input quedaría algo así:
```
input: {
...baseComponent(VnInput),
attrs: {
clearable: true,
},
},
```
alexm
commented
Lo he probado y no funciona pq al fusionar objetos se fusionan de manera superficial. es decir los objetos anidados no se fusionan se machacan.
Habría que hacerlo a nivel de attrs, forceAttrs, etc Lo he probado y no funciona pq al fusionar objetos se fusionan de manera superficial. es decir los objetos anidados no se fusionan se machacan.
Quedando input como:
```
attrs: {
clearable: true,
},
```
Habría que hacerlo a nivel de attrs, forceAttrs, etc
|
||||
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:
|
||||
alexm marked this conversation as resolved
Outdated
jsegarra
commented
No creo que se definan mas posiciones o posiciones "custom", sino considerar cambiar por un switch o un objeto. No creo que se definan mas posiciones o posiciones "custom", sino considerar cambiar por un switch o un objeto.
alexm
commented
No soy demasiado fan de swtich la vd, pero cambiado
a
No soy demasiado fan de swtich la vd, pero cambiado
```
if ($props.column.align == 'left') return 'justify-start items-start';
if ($props.column.align == 'right') return 'justify-end items-end';
return 'flex-center';
```
a
```
switch ($props.column.align) {
case 'left':
return 'justify-start items-start';
case 'right':
return 'justify-end items-end';
default:
return 'flex-center';
}
```
|
||||
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"
|
||||
alexm marked this conversation as resolved
jsegarra
commented
Se puede añadir default: () => {} a la definición de la columna? Se puede añadir default: () => {} a la definición de la columna?
|
||||
default="input"
|
||||
v-model="model"
|
||||
:components="components"
|
||||
component-prop="columnFilter"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
alexm marked this conversation as resolved
Outdated
jsegarra
commented
fixme?? fixme??
|
|
@ -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'
|
||||
alexm marked this conversation as resolved
jsegarra
commented
'card' se repite 5 veces Propuesta, definir constantes 'card' se repite 5 veces
'table' se repite 7 veces
Propuesta, definir constantes
alexm
commented
Si me cuentas las propiedades de los objetos si jajaj Si me cuentas las propiedades de los objetos si jajaj
Pasado a constantes
|
||||
},
|
||||
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}` });
|
||||
}
|
||||
alexm marked this conversation as resolved
Outdated
jsegarra
commented
Revisar las llamadas porque no es necesario pasarle $event Revisar las llamadas porque no es necesario pasarle $event
|
||||
|
||||
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"
|
||||
alexm marked this conversation as resolved
Outdated
jsegarra
commented
Se repite 4 veces Se repite 4 veces
alexm
commented
Inevitable Inevitable
|
||||
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"
|
||||
alexm marked this conversation as resolved
Outdated
jsegarra
commented
👀 👀
alexm
commented
Es un comentario Es un comentario
|
||||
/>
|
||||
</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)"
|
||||
>
|
||||
alexm marked this conversation as resolved
jsegarra
commented
👀 👀
|
||||
<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>
|
||||
alexm marked this conversation as resolved
jsegarra
commented
Este template de class se repite 3 veces Este template de class se repite 3 veces
|
||||
<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>
|
||||
alexm marked this conversation as resolved
Outdated
pablone
commented
scrollbar en app.scss /* ===== Scrollbar CSS ===== /
/* Chrome, Edge, and Safari */ *::-webkit-scrollbar-thumb { scrollbar en app.scss
/* ===== Scrollbar CSS ===== */
/* Firefox */
* {
scrollbar-width: auto;
scrollbar-color: $primary;
}
/* Chrome, Edge, and Safari */
*::-webkit-scrollbar {
width: 10px;
height: 10px;
}
*::-webkit-scrollbar-thumb {
background-color: $primary;
border-radius: 10px;
}
|
||||
</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>
|
||||
import { onBeforeMount, computed, watchEffect } from 'vue';
|
||||
import { useRoute, onBeforeRouteUpdate } from 'vue-router';
|
||||
import { onBeforeMount, computed } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useArrayData } from 'src/composables/useArrayData';
|
||||
import { useStateStore } from 'stores/useStateStore';
|
||||
import useCardSize from 'src/composables/useCardSize';
|
||||
|
@ -41,20 +41,6 @@ onBeforeMount(async () => {
|
|||
if (!props.baseUrl) arrayData.store.filter.where = { id: route.params.id };
|
||||
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>
|
||||
<template>
|
||||
<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 { 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({
|
||||
modelValue: {
|
||||
|
|
|
@ -97,7 +97,7 @@ const styleAttrs = computed(() => {
|
|||
<QIcon
|
||||
name="close"
|
||||
size="xs"
|
||||
v-if="hover && value"
|
||||
v-if="hover && value && !readonly"
|
||||
@click="onDateUpdate(null)"
|
||||
></QIcon>
|
||||
<QIcon name="event" class="cursor-pointer">
|
||||
|
|
|
@ -16,11 +16,11 @@ const $props = defineProps({
|
|||
},
|
||||
optionLabel: {
|
||||
type: [String],
|
||||
default: '',
|
||||
default: 'name',
|
||||
},
|
||||
optionValue: {
|
||||
type: String,
|
||||
default: '',
|
||||
default: 'id',
|
||||
},
|
||||
optionFilter: {
|
||||
type: String,
|
||||
|
@ -116,7 +116,9 @@ async function fetchFilter(val) {
|
|||
if (new RegExp(/\d/g).test(val)) key = optionFilter.value ?? optionValue.value;
|
||||
|
||||
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) {
|
||||
|
@ -178,6 +180,7 @@ watch(modelValue, (newValue) => {
|
|||
>
|
||||
<template v-if="isClearable" #append>
|
||||
<QIcon
|
||||
v-show="value"
|
||||
name="close"
|
||||
@click.stop="value = null"
|
||||
class="cursor-pointer"
|
||||
|
|
|
@ -1,25 +1,38 @@
|
|||
<script setup>
|
||||
defineProps({
|
||||
columns: {
|
||||
type: Number,
|
||||
default: 6,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div class="q-pa-md w">
|
||||
<div class="row q-gutter-md q-mb-md">
|
||||
<QSkeleton type="rect" square />
|
||||
<QSkeleton type="rect" square />
|
||||
<QSkeleton type="rect" square />
|
||||
<QSkeleton type="rect" square />
|
||||
<QSkeleton type="rect" square />
|
||||
<QSkeleton type="rect" square />
|
||||
</div>
|
||||
<div class="row q-gutter-md q-mb-md" v-for="n in 5" :key="n">
|
||||
<QSkeleton type="QInput" square />
|
||||
<QSkeleton type="QInput" square />
|
||||
<QSkeleton type="QInput" square />
|
||||
<QSkeleton type="QInput" square />
|
||||
<QSkeleton type="QInput" square />
|
||||
<QSkeleton type="QInput" square />
|
||||
<div class="q-pa-md q-mx-md container">
|
||||
<div class="row q-gutter-md q-mb-md justify-around no-wrap">
|
||||
<QSkeleton type="rect" square v-for="n in columns" :key="n" class="column" />
|
||||
</div>
|
||||
<div
|
||||
class="row q-gutter-md q-mb-md justify-around no-wrap"
|
||||
v-for="n in 5"
|
||||
:key="n"
|
||||
>
|
||||
<QSkeleton
|
||||
type="QInput"
|
||||
square
|
||||
v-for="m in columns"
|
||||
:key="m"
|
||||
class="column"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
.w {
|
||||
width: 80vw;
|
||||
.container {
|
||||
width: 100%;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
.column {
|
||||
flex-shrink: 0;
|
||||
width: 200px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -4,11 +4,11 @@ import { useI18n } from 'vue-i18n';
|
|||
import { useArrayData } from 'composables/useArrayData';
|
||||
import { useRoute } from 'vue-router';
|
||||
import toDate from 'filters/toDate';
|
||||
import useRedirect from 'src/composables/useRedirect';
|
||||
import VnFilterPanelChip from 'components/ui/VnFilterPanelChip.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
const props = defineProps({
|
||||
const params = defineModel({ default: {}, required: true, type: Object });
|
||||
const $props = defineProps({
|
||||
dataKey: {
|
||||
type: String,
|
||||
required: true,
|
||||
|
@ -18,11 +18,6 @@ const props = defineProps({
|
|||
required: false,
|
||||
default: false,
|
||||
},
|
||||
params: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
showAll: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
|
@ -40,12 +35,20 @@ const props = defineProps({
|
|||
},
|
||||
hiddenTags: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
default: () => ['filter'],
|
||||
},
|
||||
customTags: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
disableSubmitEvent: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
searchUrl: {
|
||||
type: String,
|
||||
default: 'params',
|
||||
},
|
||||
redirect: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
|
@ -54,61 +57,64 @@ const props = defineProps({
|
|||
|
||||
const emit = defineEmits(['refresh', 'clear', 'search', 'init', 'remove']);
|
||||
|
||||
const arrayData = useArrayData(props.dataKey, {
|
||||
exprBuilder: props.exprBuilder,
|
||||
const arrayData = useArrayData($props.dataKey, {
|
||||
exprBuilder: $props.exprBuilder,
|
||||
searchUrl: $props.searchUrl,
|
||||
navigate: {},
|
||||
});
|
||||
const route = useRoute();
|
||||
const store = arrayData.store;
|
||||
const userParams = ref({});
|
||||
const { navigate } = useRedirect();
|
||||
|
||||
onMounted(() => {
|
||||
if (props.params) userParams.value = JSON.parse(JSON.stringify(props.params));
|
||||
if (Object.keys(store.userParams).length > 0) {
|
||||
userParams.value = JSON.parse(JSON.stringify(store.userParams));
|
||||
}
|
||||
emit('init', { params: userParams.value });
|
||||
emit('init', { params: params.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(
|
||||
() => route.query.params,
|
||||
(val) => {
|
||||
if (!val) {
|
||||
userParams.value = {};
|
||||
} else {
|
||||
const parsedParams = JSON.parse(val);
|
||||
userParams.value = { ...parsedParams };
|
||||
}
|
||||
}
|
||||
() => route.query[$props.searchUrl],
|
||||
(val) => setUserParams(val)
|
||||
);
|
||||
|
||||
watch(
|
||||
() => arrayData.store.userParams,
|
||||
(val) => setUserParams(val)
|
||||
);
|
||||
|
||||
const isLoading = ref(false);
|
||||
async function search() {
|
||||
async function search(evt) {
|
||||
if (evt && $props.disableSubmitEvent) return;
|
||||
|
||||
store.filter.where = {};
|
||||
isLoading.value = true;
|
||||
const params = { ...userParams.value };
|
||||
const filter = { ...params.value };
|
||||
store.userParamsChanged = true;
|
||||
store.filter.skip = 0;
|
||||
store.skip = 0;
|
||||
const { params: newParams } = await arrayData.addFilter({ params });
|
||||
userParams.value = newParams;
|
||||
const { params: newParams } = await arrayData.addFilter({ params: params.value });
|
||||
params.value = newParams;
|
||||
|
||||
if (!props.showAll && !Object.values(params).length) store.data = [];
|
||||
if (!$props.showAll && !Object.values(filter).length) store.data = [];
|
||||
|
||||
isLoading.value = false;
|
||||
emit('search');
|
||||
if (props.redirect) navigate(store.data, {});
|
||||
}
|
||||
|
||||
async function reload() {
|
||||
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 });
|
||||
if (!props.showAll && !params.length) store.data = [];
|
||||
if (!$props.showAll && !params.length) store.data = [];
|
||||
isLoading.value = false;
|
||||
emit('refresh');
|
||||
if (props.redirect) navigate(store.data, {});
|
||||
}
|
||||
|
||||
async function clearFilters() {
|
||||
|
@ -117,18 +123,19 @@ async function clearFilters() {
|
|||
store.filter.skip = 0;
|
||||
store.skip = 0;
|
||||
// Filtrar los params no removibles
|
||||
const removableFilters = Object.keys(userParams.value).filter((param) =>
|
||||
props.unremovableParams.includes(param)
|
||||
const removableFilters = Object.keys(params.value).filter((param) =>
|
||||
$props.unremovableParams.includes(param)
|
||||
);
|
||||
const newParams = {};
|
||||
// Conservar solo los params que no son removibles
|
||||
for (const key of removableFilters) {
|
||||
newParams[key] = userParams.value[key];
|
||||
newParams[key] = params.value[key];
|
||||
}
|
||||
userParams.value = { ...newParams }; // Actualizar los params con los removibles
|
||||
await arrayData.applyFilter({ params: userParams.value });
|
||||
params.value = {};
|
||||
params.value = { ...newParams }; // Actualizar los params con los removibles
|
||||
await arrayData.applyFilter({ params: params.value });
|
||||
|
||||
if (!props.showAll) {
|
||||
if (!$props.showAll) {
|
||||
store.data = [];
|
||||
}
|
||||
|
||||
|
@ -136,36 +143,32 @@ async function clearFilters() {
|
|||
emit('clear');
|
||||
}
|
||||
|
||||
const tagsList = computed(() =>
|
||||
Object.entries(userParams.value)
|
||||
.filter(([key, value]) => value && !(props.hiddenTags || []).includes(key))
|
||||
.map(([key, value]) => ({
|
||||
label: key,
|
||||
value: value,
|
||||
}))
|
||||
);
|
||||
const tagsList = computed(() => {
|
||||
const tagList = [];
|
||||
for (const key of Object.keys(params.value)) {
|
||||
const value = params.value[key];
|
||||
if (value == null || ($props.hiddenTags || []).includes(key)) continue;
|
||||
tagList.push({ label: key, value });
|
||||
}
|
||||
return tagList;
|
||||
});
|
||||
|
||||
const tags = computed(() =>
|
||||
tagsList.value.filter((tag) => !(props.customTags || []).includes(tag.label))
|
||||
);
|
||||
const tags = computed(() => {
|
||||
return tagsList.value.filter((tag) => !($props.customTags || []).includes(tag.key));
|
||||
});
|
||||
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) {
|
||||
userParams.value[key] = null;
|
||||
await arrayData.applyFilter({ params: userParams.value });
|
||||
params.value[key] = undefined;
|
||||
search();
|
||||
emit('remove', key);
|
||||
}
|
||||
|
||||
function formatValue(value) {
|
||||
if (typeof value === 'boolean') {
|
||||
return value ? t('Yes') : t('No');
|
||||
}
|
||||
|
||||
if (isNaN(value) && !isNaN(Date.parse(value))) {
|
||||
return toDate(value);
|
||||
}
|
||||
if (typeof value === 'boolean') return value ? t('Yes') : t('No');
|
||||
jsegarra
commented
Si la operacion ternaria es para el valor de la traducción, porque no ponerla dentro de la funcion t? Otra cosa es, hacen falta las traducciones? Porque en globals tenemos yes y no Si la operacion ternaria es para el valor de la traducción, porque no ponerla dentro de la funcion t?
Otra cosa es, hacen falta las traducciones? Porque en globals tenemos yes y no
|
||||
if (isNaN(value) && !isNaN(Date.parse(value))) return toDate(value);
|
||||
|
||||
return `"${value}"`;
|
||||
}
|
||||
|
@ -226,14 +229,14 @@ function formatValue(value) {
|
|||
<slot name="tags" :tag="chip" :format-fn="formatValue">
|
||||
<div class="q-gutter-x-xs">
|
||||
<strong>{{ chip.label }}:</strong>
|
||||
<span>"{{ chip.value }}"</span>
|
||||
<span>"{{ formatValue(chip.value) }}"</span>
|
||||
</div>
|
||||
</slot>
|
||||
</VnFilterPanelChip>
|
||||
<slot
|
||||
v-if="$slots.customTags"
|
||||
name="customTags"
|
||||
:params="userParams"
|
||||
:params="params"
|
||||
:tags="customTags"
|
||||
:format-fn="formatValue"
|
||||
:search-fn="search"
|
||||
|
@ -243,9 +246,9 @@ function formatValue(value) {
|
|||
<QSeparator />
|
||||
</QList>
|
||||
<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>
|
||||
<template v-if="props.searchButton">
|
||||
<template v-if="$props.searchButton">
|
||||
<QItem>
|
||||
<QItemSection class="q-py-sm">
|
||||
<QBtn
|
||||
|
@ -255,7 +258,7 @@ function formatValue(value) {
|
|||
dense
|
||||
icon="search"
|
||||
rounded
|
||||
type="submit"
|
||||
:type="disableSubmitEvent ? 'button' : 'submit'"
|
||||
unelevated
|
||||
/>
|
||||
</QItemSection>
|
||||
|
@ -269,7 +272,6 @@ function formatValue(value) {
|
|||
color="primary"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.list {
|
||||
width: 256px;
|
||||
|
|
|
@ -58,14 +58,19 @@ const props = defineProps({
|
|||
type: Function,
|
||||
default: null,
|
||||
},
|
||||
searchUrl: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
disableInfiniteScroll: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['onFetch', 'onPaginate']);
|
||||
const emit = defineEmits(['onFetch', 'onPaginate', 'onChange']);
|
||||
const isLoading = ref(false);
|
||||
const mounted = ref(false);
|
||||
const pagination = ref({
|
||||
sortBy: props.order,
|
||||
rowsPerPage: props.limit,
|
||||
|
@ -81,11 +86,13 @@ const arrayData = useArrayData(props.dataKey, {
|
|||
userParams: props.userParams,
|
||||
exprBuilder: props.exprBuilder,
|
||||
keepOpts: props.keepOpts,
|
||||
searchUrl: props.searchUrl,
|
||||
});
|
||||
const store = arrayData.store;
|
||||
|
||||
onMounted(() => {
|
||||
if (props.autoLoad) fetch();
|
||||
onMounted(async () => {
|
||||
if (props.autoLoad) await fetch();
|
||||
mounted.value = true;
|
||||
});
|
||||
|
||||
watch(
|
||||
|
@ -95,11 +102,22 @@ watch(
|
|||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => store.data,
|
||||
(data) => emit('onChange', data)
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.url,
|
||||
(url) => fetch({ url })
|
||||
);
|
||||
|
||||
const addFilter = async (filter, params) => {
|
||||
await arrayData.addFilter({ filter, params });
|
||||
};
|
||||
|
||||
async function fetch() {
|
||||
async function fetch(params) {
|
||||
useArrayData(props.dataKey, params);
|
||||
store.filter.skip = 0;
|
||||
store.skip = 0;
|
||||
await arrayData.fetch({ append: false });
|
||||
|
@ -107,6 +125,7 @@ async function fetch() {
|
|||
isLoading.value = false;
|
||||
}
|
||||
emit('onFetch', store.data);
|
||||
return store.data;
|
||||
}
|
||||
|
||||
async function paginate() {
|
||||
|
@ -138,7 +157,7 @@ function endPagination() {
|
|||
emit('onPaginate');
|
||||
}
|
||||
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);
|
||||
|
||||
|
@ -150,7 +169,7 @@ async function onLoad(index, done) {
|
|||
done(isDone);
|
||||
}
|
||||
|
||||
defineExpose({ fetch, addFilter });
|
||||
defineExpose({ fetch, addFilter, paginate });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -199,12 +218,6 @@ defineExpose({ fetch, addFilter });
|
|||
<QSpinner color="orange" size="md" />
|
||||
</div>
|
||||
</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>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
|
@ -3,7 +3,6 @@ import { onMounted, ref, watch } from 'vue';
|
|||
import { useQuasar } from 'quasar';
|
||||
import { useArrayData } from 'composables/useArrayData';
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
import useRedirect from 'src/composables/useRedirect';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useStateStore } from 'src/stores/useStateStore';
|
||||
|
||||
|
@ -18,17 +17,14 @@ const props = defineProps({
|
|||
},
|
||||
label: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'Search',
|
||||
},
|
||||
info: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
redirect: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true,
|
||||
},
|
||||
url: {
|
||||
|
@ -73,10 +69,20 @@ const props = defineProps({
|
|||
},
|
||||
});
|
||||
|
||||
let arrayData = useArrayData(props.dataKey, { ...props });
|
||||
let store = arrayData.store;
|
||||
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(
|
||||
() => props.dataKey,
|
||||
|
@ -106,13 +112,6 @@ async function search() {
|
|||
search: searchText.value,
|
||||
},
|
||||
});
|
||||
|
||||
if (!props.redirect) return;
|
||||
|
||||
navigate(store.data, {
|
||||
customRouteRedirectName: props.customRouteRedirectName,
|
||||
searchText: searchText.value,
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { onMounted, ref, computed } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useRouter, useRoute } from 'vue-router';
|
||||
import axios from 'axios';
|
||||
import { useArrayDataStore } from 'stores/useArrayDataStore';
|
||||
import { buildFilter } from 'filters/filterPanel';
|
||||
|
@ -13,6 +13,7 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
|
|||
|
||||
const store = arrayDataStore.get(key);
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
let canceller = null;
|
||||
|
||||
const page = ref(1);
|
||||
|
@ -22,8 +23,13 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
|
|||
store.skip = 0;
|
||||
|
||||
const query = route.query;
|
||||
if (query.params) {
|
||||
store.userParams = JSON.parse(query.params);
|
||||
const searchUrl = store.searchUrl;
|
||||
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',
|
||||
'userFilter',
|
||||
'exprBuilder',
|
||||
'searchUrl',
|
||||
'navigate',
|
||||
];
|
||||
if (typeof userOptions === 'object') {
|
||||
for (const option in userOptions) {
|
||||
const isEmpty = userOptions[option] == null || userOptions[option] === '';
|
||||
if (isEmpty || !allowedOptions.includes(option)) continue;
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(store, option)) {
|
||||
if (Object.hasOwn(store, option)) {
|
||||
const defaultOpts = userOptions[option];
|
||||
store[option] = userOptions.keepOpts?.includes(option)
|
||||
? Object.assign(defaultOpts, store[option])
|
||||
|
@ -87,8 +95,8 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
|
|||
|
||||
Object.assign(params, userParams);
|
||||
|
||||
store.isLoading = true;
|
||||
store.currentFilter = params;
|
||||
store.isLoading = true;
|
||||
const response = await axios.get(store.url, {
|
||||
signal: canceller.signal,
|
||||
params,
|
||||
|
@ -118,6 +126,10 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
|
|||
}
|
||||
}
|
||||
|
||||
function deleteOption(option) {
|
||||
delete store[option];
|
||||
}
|
||||
|
||||
function cancelRequest() {
|
||||
if (canceller) {
|
||||
canceller.abort();
|
||||
|
@ -128,7 +140,7 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
|
|||
async function applyFilter({ filter, params }) {
|
||||
if (filter) store.userFilter = filter;
|
||||
store.filter = {};
|
||||
if (params) store.userParams = Object.assign({}, params);
|
||||
if (params) store.userParams = { ...params };
|
||||
|
||||
const response = await fetch({ append: false });
|
||||
return response;
|
||||
|
@ -137,7 +149,7 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
|
|||
async function addFilter({ filter, params }) {
|
||||
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);
|
||||
|
||||
store.userParams = userParams;
|
||||
|
@ -149,15 +161,20 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
|
|||
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) {
|
||||
for (const param in params) {
|
||||
if (params[param] === '' || params[param] === null) {
|
||||
delete store.userParams[param];
|
||||
delete params[param];
|
||||
if (store.filter?.where) {
|
||||
const key = Object.keys(
|
||||
exprBuilder && exprBuilder(param) ? exprBuilder(param) : param
|
||||
);
|
||||
const key = Object.keys(exprBuilder ? exprBuilder(param) : param);
|
||||
if (key[0]) delete store.filter.where[key[0]];
|
||||
if (Object.keys(store.filter.where).length === 0) {
|
||||
delete store.filter.where;
|
||||
|
@ -182,22 +199,34 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
|
|||
}
|
||||
|
||||
function updateStateParams() {
|
||||
const query = {};
|
||||
if (store.order) query.order = store.order;
|
||||
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 newUrl = { path: route.path, query: { ...(route.query ?? {}) } };
|
||||
newUrl.query[store.searchUrl] = JSON.stringify(store.currentFilter);
|
||||
|
||||
const url = new URL(window.location.href);
|
||||
const { hash: currentHash } = url;
|
||||
const [currentRoute] = currentHash.split('?');
|
||||
if (store.navigate) {
|
||||
const { customRouteRedirectName, searchText } = store.navigate;
|
||||
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();
|
||||
for (const param in query) params.append(param, query[param]);
|
||||
const to =
|
||||
store?.data?.length === 1
|
||||
? path.replace(/\/(list|:id)|-list/, `/${store.data[0].id}`)
|
||||
: path.replace(/:id.*/, '');
|
||||
|
||||
url.hash = currentRoute + '?' + params.toString();
|
||||
window.history.pushState({}, '', url.hash);
|
||||
if (route.path != to) {
|
||||
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);
|
||||
|
@ -207,6 +236,7 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
|
|||
fetch,
|
||||
applyFilter,
|
||||
addFilter,
|
||||
addFilterWhere,
|
||||
refresh,
|
||||
destroy,
|
||||
loadMore,
|
||||
|
@ -214,5 +244,6 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
|
|||
totalRows,
|
||||
updateStateParams,
|
||||
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);
|
||||
}
|
||||
|
||||
.text-primary-light {
|
||||
color: $primary-light !important;
|
||||
}
|
||||
.bg-primary-light {
|
||||
background: $primary-light !important;
|
||||
}
|
||||
|
||||
.fill-icon {
|
||||
font-variation-settings: 'FILL' 1;
|
||||
}
|
||||
|
@ -189,3 +196,26 @@ input::-webkit-inner-spin-button {
|
|||
.q-scrollarea__content {
|
||||
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: #7979794d;
|
||||
$border-thin-light: 1px solid $color-spacer-light;
|
||||
$primary-light: lighten($primary, 35%);
|
||||
$primary-light: #f5b351;
|
||||
$dark-shadow-color: black;
|
||||
$layout-shadow-dark: 0 0 10px 2px #00000033, 0 0px 10px #0000003d;
|
||||
$spacing-md: 16px;
|
||||
|
|
|
@ -279,8 +279,8 @@ customer:
|
|||
extendedList:
|
||||
tableVisibleColumns:
|
||||
id: Identifier
|
||||
name: Name
|
||||
socialName: Social name
|
||||
name: Comercial name
|
||||
socialName: Business name
|
||||
fi: Tax number
|
||||
salesPersonFk: Salesperson
|
||||
credit: Credit
|
||||
|
|
|
@ -278,7 +278,7 @@ customer:
|
|||
extendedList:
|
||||
tableVisibleColumns:
|
||||
id: Identificador
|
||||
name: Nombre
|
||||
name: Nombre Comercial
|
||||
socialName: Razón social
|
||||
fi: NIF / CIF
|
||||
salesPersonFk: Comercial
|
||||
|
|
|
@ -10,7 +10,7 @@ import CustomerFilter from '../CustomerFilter.vue';
|
|||
:descriptor="CustomerDescriptor"
|
||||
:filter-panel="CustomerFilter"
|
||||
search-data-key="CustomerList"
|
||||
search-url="Clients/filter"
|
||||
search-url="Clients/extendedListFilter"
|
||||
searchbar-label="Search customer"
|
||||
searchbar-info="You can search by customer id or name"
|
||||
/>
|
||||
|
|
|
@ -29,7 +29,7 @@ const zones = ref();
|
|||
@on-fetch="(data) => (workers = data)"
|
||||
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 }">
|
||||
<div class="q-gutter-x-xs">
|
||||
<strong>{{ t(`params.${tag.label}`) }}: </strong>
|
||||
|
|
|
@ -1,94 +1,439 @@
|
|||
<script setup>
|
||||
import { ref, computed, markRaw } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRouter } from 'vue-router';
|
||||
import VnPaginate from 'src/components/ui/VnPaginate.vue';
|
||||
import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
|
||||
import CustomerFilter from './CustomerFilter.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 VnTable from 'components/VnTable/VnTable.vue';
|
||||
import VnLocation from 'src/components/common/VnLocation.vue';
|
||||
import VnSearchbar from 'components/ui/VnSearchbar.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 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();
|
||||
|
||||
function navigate(id) {
|
||||
router.push({ path: `/customer/${id}` });
|
||||
}
|
||||
|
||||
const redirectToCreateView = () => {
|
||||
router.push({ name: 'CustomerCreate' });
|
||||
const redirectToCreateView = (row) => {
|
||||
router.push({
|
||||
name: 'TicketList',
|
||||
query: {
|
||||
params: JSON.stringify({
|
||||
clientFk: row.id,
|
||||
}),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
function handleLocation(data, location) {
|
||||
const { town, code, provinceFk, countryFk } = location ?? {};
|
||||
data.postcode = code;
|
||||
data.city = town;
|
||||
data.provinceFk = provinceFk;
|
||||
data.countryFk = countryFk;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VnSearchbar
|
||||
:info="t('You can search by customer id or name')"
|
||||
:label="t('Search customer')"
|
||||
data-key="CustomerList"
|
||||
data-key="Customer"
|
||||
/>
|
||||
<RightMenu>
|
||||
<template #right-panel>
|
||||
<CustomerFilter data-key="CustomerList" />
|
||||
</template>
|
||||
</RightMenu>
|
||||
<QPage class="column items-center q-pa-md">
|
||||
<div class="vn-card-list">
|
||||
<VnPaginate
|
||||
auto-load
|
||||
data-key="CustomerList"
|
||||
order="id DESC"
|
||||
url="/Clients/filter"
|
||||
>
|
||||
<template #body="{ rows }">
|
||||
<CardList
|
||||
:id="row.id"
|
||||
:key="row.id"
|
||||
:title="row.name"
|
||||
@click="navigate(row.id)"
|
||||
v-for="row of rows"
|
||||
>
|
||||
<template #list-items>
|
||||
<VnLv :label="t('customer.list.email')" :value="row.email" />
|
||||
<VnLv :value="row.phone">
|
||||
<template #label>
|
||||
{{ t('customer.list.phone') }}
|
||||
<VnLinkPhone :phone-number="row.phone" />
|
||||
</template>
|
||||
</VnLv>
|
||||
</template>
|
||||
<template #actions>
|
||||
<QBtn
|
||||
:label="t('components.smartCard.openCard')"
|
||||
@click.stop="navigate(row.id)"
|
||||
outline
|
||||
/>
|
||||
<QBtn
|
||||
:label="t('components.smartCard.openSummary')"
|
||||
@click.stop="viewSummary(row.id, CustomerSummary)"
|
||||
color="primary"
|
||||
style="margin-top: 15px"
|
||||
/>
|
||||
</template>
|
||||
</CardList>
|
||||
<VnTable
|
||||
ref="tableRef"
|
||||
data-key="Customer"
|
||||
url="Clients/extendedListFilter"
|
||||
:create="{
|
||||
urlCreate: 'Clients/createWithUser',
|
||||
title: 'Create client',
|
||||
onDataSaved: ({ id }) => tableRef.redirect(id),
|
||||
formInitialData: {
|
||||
active: true,
|
||||
isEqualizated: false,
|
||||
},
|
||||
}"
|
||||
order="id DESC"
|
||||
:columns="columns"
|
||||
default-mode="table"
|
||||
redirect="customer"
|
||||
auto-load
|
||||
>
|
||||
<template #more-create-dialog="{ data }">
|
||||
<VnLocation
|
||||
:roles-allowed-to-create="['deliveryAssistant']"
|
||||
:options="postcodesOptions"
|
||||
v-model="data.location"
|
||||
@update:model-value="(location) => handleLocation(data, location)"
|
||||
/>
|
||||
<QInput v-model="data.userName" :label="t('Web user')" />
|
||||
<QInput :label="t('Email')" clearable type="email" v-model="data.email">
|
||||
<template #append>
|
||||
<QIcon name="info" class="cursor-info">
|
||||
<QTooltip max-width="400px">{{
|
||||
t('customer.basicData.youCanSaveMultipleEmails')
|
||||
}}</QTooltip>
|
||||
</QIcon>
|
||||
</template>
|
||||
</VnPaginate>
|
||||
</div>
|
||||
<QPageSticky :offset="[20, 20]">
|
||||
<QBtn @click="redirectToCreateView()" color="primary" fab icon="add" />
|
||||
<QTooltip>
|
||||
{{ t('New client') }}
|
||||
</QTooltip>
|
||||
</QPageSticky>
|
||||
</QPage>
|
||||
</QInput>
|
||||
</template>
|
||||
</VnTable>
|
||||
</template>
|
||||
|
||||
<i18n>
|
||||
es:
|
||||
Search customer: Buscar cliente
|
||||
You can search by customer id or name: Puedes buscar por id o nombre del cliente
|
||||
New client: Nuevo cliente
|
||||
Web user: Usuario Web
|
||||
</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 VnCurrency from 'src/components/common/VnCurrency.vue';
|
||||
import { toCurrency } from 'src/filters';
|
||||
import useNotify from 'src/composables/useNotify.js';
|
||||
|
||||
const route = useRoute();
|
||||
const { notify } = useNotify();
|
||||
const { t } = useI18n();
|
||||
const arrayData = useArrayData();
|
||||
const invoiceIn = computed(() => arrayData.store.data);
|
||||
|
@ -69,6 +71,7 @@ const isNotEuro = (code) => code != 'EUR';
|
|||
async function insert() {
|
||||
await axios.post('/InvoiceInDueDays/new', { id: +invoiceId });
|
||||
await invoiceInFormRef.value.reload();
|
||||
notify(t('globals.dataSaved'), 'positive');
|
||||
}
|
||||
const getTotalAmount = (rows) => rows.reduce((acc, { amount }) => acc + +amount, 0);
|
||||
</script>
|
||||
|
|
|
@ -20,8 +20,8 @@ const { t } = useI18n();
|
|||
const quasar = useQuasar();
|
||||
const entityId = computed(() => $props.id || route.params.id);
|
||||
const URL_KEY = 'NotificationSubscriptions';
|
||||
const active = ref();
|
||||
const available = ref();
|
||||
const active = ref(new Map());
|
||||
const available = ref(new Map());
|
||||
|
||||
async function toggleNotification(notification) {
|
||||
try {
|
||||
|
@ -56,6 +56,7 @@ const swapEntry = (from, to, key) => {
|
|||
};
|
||||
|
||||
function setNotifications(data) {
|
||||
console.log('data: ', data);
|
||||
active.value = new Map(data.active);
|
||||
available.value = new Map(data.available);
|
||||
}
|
||||
|
|
|
@ -14,7 +14,6 @@ export default {
|
|||
main: [
|
||||
'CustomerList',
|
||||
'CustomerPayments',
|
||||
'CustomerExtendedList',
|
||||
'CustomerNotifications',
|
||||
'CustomerDefaulter',
|
||||
],
|
||||
|
@ -70,18 +69,6 @@ export default {
|
|||
component: () =>
|
||||
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',
|
||||
name: 'CustomerNotifications',
|
||||
|
|
|
@ -21,6 +21,8 @@ export const useArrayDataStore = defineStore('arrayDataStore', () => {
|
|||
isLoading: false,
|
||||
userParamsChanged: false,
|
||||
exprBuilder: null,
|
||||
searchUrl: 'params',
|
||||
navigate: null,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ describe('InvoiceInDueDay', () => {
|
|||
cy.waitForElement('thead');
|
||||
cy.get(addBtn).click();
|
||||
|
||||
cy.saveCard();
|
||||
cy.get('tbody > :nth-child(1)').should('exist');
|
||||
cy.get('.q-notification__message').should('have.text', 'Data saved');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,31 +1,98 @@
|
|||
import { describe, expect, it, beforeAll } from 'vitest';
|
||||
import { axios } from 'app/test/vitest/helper';
|
||||
import { describe, expect, it, beforeEach, afterEach, vi } from 'vitest';
|
||||
import { axios, flushPromises } from 'app/test/vitest/helper';
|
||||
import { useArrayData } from 'composables/useArrayData';
|
||||
import { useRouter } from 'vue-router';
|
||||
import * as vueRouter from 'vue-router';
|
||||
|
||||
describe('useArrayData', () => {
|
||||
let arrayData;
|
||||
beforeAll(() => {
|
||||
axios.get.mockResolvedValue({ data: [] });
|
||||
arrayData = useArrayData('InvoiceIn', { url: 'invoice-in/list' });
|
||||
Object.defineProperty(window.location, 'href', {
|
||||
writable: true,
|
||||
value: 'localhost:9000/invoice-in/list',
|
||||
const filter = '{"order":"","limit":10,"skip":0}';
|
||||
const params = { supplierFk: 2 };
|
||||
beforeEach(() => {
|
||||
vi.spyOn(useRouter(), 'replace');
|
||||
vi.spyOn(useRouter(), 'push');
|
||||
});
|
||||
|
||||
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
|
||||
window.history.pushState = (data, title, url) => (window.location.href = url);
|
||||
const arrayData = useArrayData('ArrayData', { url: 'mockUrl', navigate: {} });
|
||||
|
||||
// Mock the URL constructor within useArrayData
|
||||
global.URL = class URL {
|
||||
constructor(url) {
|
||||
this.hash = url.split('localhost:9000/')[1];
|
||||
}
|
||||
};
|
||||
});
|
||||
arrayData.store.userParams = params;
|
||||
arrayData.fetch({});
|
||||
|
||||
it('should add the params to the url', async () => {
|
||||
arrayData.store.userParams = { supplierFk: 2 };
|
||||
arrayData.updateStateParams();
|
||||
expect(window.location.href).contain('params=%7B%22supplierFk%22%3A2%7D');
|
||||
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/');
|
||||
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 mockPush = vi.fn();
|
||||
const mockReplace = vi.fn();
|
||||
|
||||
vi.mock('vue-router', () => ({
|
||||
useRouter: () => ({
|
||||
push: mockPush,
|
||||
replace: mockReplace,
|
||||
currentRoute: {
|
||||
value: {
|
||||
params: {
|
||||
id: 1,
|
||||
},
|
||||
meta: { moduleName: 'mockName' },
|
||||
matched: [{ path: 'mockName/list' }],
|
||||
},
|
||||
},
|
||||
}),
|
||||
|
@ -33,6 +36,7 @@ vi.mock('vue-router', () => ({
|
|||
query: {},
|
||||
params: {},
|
||||
meta: { moduleName: 'mockName' },
|
||||
path: 'mockSection/list',
|
||||
}),
|
||||
}));
|
||||
|
||||
|
|
Si quitas $event, se le pasa igualmente