#7136 - Enable paginate event in VnSelectFilter #255
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "salix-front",
|
||||
"version": "24.26.2",
|
||||
"version": "24.28.1",
|
||||
"description": "Salix frontend",
|
||||
"productName": "Salix",
|
||||
"author": "Verdnatura",
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import axios from 'axios';
|
||||
import { Notify } from 'quasar';
|
||||
import { useSession } from 'src/composables/useSession';
|
||||
import { Router } from 'src/router';
|
||||
import { i18n } from './i18n';
|
||||
import useNotify from 'src/composables/useNotify.js';
|
||||
|
||||
const session = useSession();
|
||||
const { t } = i18n.global;
|
||||
const { notify } = useNotify();
|
||||
|
||||
axios.defaults.baseURL = '/api/';
|
||||
|
||||
|
@ -27,10 +26,7 @@ const onResponse = (response) => {
|
|||
|
||||
const isSaveRequest = method === 'patch';
|
||||
if (isSaveRequest) {
|
||||
Notify.create({
|
||||
message: t('globals.dataSaved'),
|
||||
type: 'positive',
|
||||
});
|
||||
notify('globals.dataSaved', 'positive');
|
||||
}
|
||||
|
||||
return response;
|
||||
|
@ -67,10 +63,7 @@ const onResponseError = (error) => {
|
|||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
Notify.create({
|
||||
message: t(message),
|
||||
type: 'negative',
|
||||
});
|
||||
notify(message, 'negative');
|
||||
|
||||
return Promise.reject(error);
|
||||
};
|
||||
|
|
|
@ -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(
|
||||
() => {
|
||||
|
@ -135,7 +148,7 @@ async function onSubmit() {
|
|||
await saveChanges($props.saveFn ? formData.value : null);
|
||||
}
|
||||
|
||||
async function onSumbitAndGo() {
|
||||
async function onSubmitAndGo() {
|
||||
await onSubmit();
|
||||
push({ path: $props.goTo });
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -324,7 +339,7 @@ watch(formUrl, async () => {
|
|||
/>
|
||||
<QBtnDropdown
|
||||
v-if="$props.goTo && $props.defaultSave"
|
||||
@click="onSumbitAndGo"
|
||||
@click="onSubmitAndGo"
|
||||
:label="tMobile('globals.saveAndContinue')"
|
||||
:title="t('globals.saveAndContinue')"
|
||||
:disable="!hasChanges"
|
||||
|
|
|
@ -83,6 +83,10 @@ const $props = defineProps({
|
|||
default: '',
|
||||
description: 'It is used for redirect on click "save and continue"',
|
||||
},
|
||||
reload: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
const emit = defineEmits(['onFetch', 'onDataSaved']);
|
||||
const modelValue = computed(
|
||||
|
@ -201,6 +205,7 @@ async function save() {
|
|||
if ($props.urlCreate) notify('globals.dataCreated', 'positive');
|
||||
|
||||
updateAndEmit('onDataSaved', formData.value, response?.data);
|
||||
if ($props.reload) await arrayData.fetch({});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
notify('errors.writeRequest', 'negative');
|
||||
|
@ -246,7 +251,13 @@ function updateAndEmit(evt, val, res) {
|
|||
emit(evt, state.get(modelValue), res);
|
||||
}
|
||||
|
||||
defineExpose({ save, isLoading, hasChanges });
|
||||
defineExpose({
|
||||
save,
|
||||
isLoading,
|
||||
hasChanges,
|
||||
reset,
|
||||
fetch,
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div class="column items-center full-width">
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
import { ref, reactive } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import { useQuasar } from 'quasar';
|
||||
import VnConfirm from 'components/ui/VnConfirm.vue';
|
||||
import VnRow from 'components/ui/VnRow.vue';
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import VnSelect from 'components/common/VnSelect.vue';
|
||||
|
@ -18,33 +19,68 @@ const $props = defineProps({
|
|||
},
|
||||
});
|
||||
|
||||
const quasar = useQuasar();
|
||||
const { t } = useI18n();
|
||||
const router = useRouter();
|
||||
const { notify } = useNotify();
|
||||
|
||||
const checked = ref(true);
|
||||
const transferInvoiceParams = reactive({
|
||||
id: $props.invoiceOutData?.id,
|
||||
refFk: $props.invoiceOutData?.ref,
|
||||
});
|
||||
const closeButton = ref(null);
|
||||
const clientsOptions = ref([]);
|
||||
|
||||
const rectificativeTypeOptions = ref([]);
|
||||
const siiTypeInvoiceOutsOptions = ref([]);
|
||||
const invoiceCorrectionTypesOptions = ref([]);
|
||||
|
||||
const closeForm = () => {
|
||||
if (closeButton.value) closeButton.value.click();
|
||||
const selectedClient = (client) => {
|
||||
transferInvoiceParams.selectedClientData = client;
|
||||
};
|
||||
|
||||
const transferInvoice = async () => {
|
||||
const makeInvoice = async () => {
|
||||
const hasToInvoiceByAddress =
|
||||
transferInvoiceParams.selectedClientData.hasToInvoiceByAddress;
|
||||
|
||||
const params = {
|
||||
id: transferInvoiceParams.id,
|
||||
cplusRectificationTypeFk: transferInvoiceParams.cplusRectificationTypeFk,
|
||||
invoiceCorrectionTypeFk: transferInvoiceParams.invoiceCorrectionTypeFk,
|
||||
newClientFk: transferInvoiceParams.newClientFk,
|
||||
refFk: transferInvoiceParams.refFk,
|
||||
siiTypeInvoiceOutFk: transferInvoiceParams.siiTypeInvoiceOutFk,
|
||||
makeInvoice: checked.value,
|
||||
};
|
||||
|
||||
try {
|
||||
const { data } = await axios.post(
|
||||
'InvoiceOuts/transferInvoice',
|
||||
transferInvoiceParams
|
||||
);
|
||||
if (checked.value && hasToInvoiceByAddress) {
|
||||
const response = await new Promise((resolve) => {
|
||||
quasar
|
||||
.dialog({
|
||||
component: VnConfirm,
|
||||
componentProps: {
|
||||
title: t('Bill destination client'),
|
||||
message: t('transferInvoiceInfo'),
|
||||
},
|
||||
})
|
||||
.onOk(() => {
|
||||
resolve(true);
|
||||
})
|
||||
.onCancel(() => {
|
||||
resolve(false);
|
||||
});
|
||||
});
|
||||
if (!response) {
|
||||
console.log('entra cuando no checkbox');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('params: ', params);
|
||||
const { data } = await axios.post('InvoiceOuts/transferInvoice', params);
|
||||
console.log('data: ', data);
|
||||
notify(t('Transferred invoice'), 'positive');
|
||||
closeForm();
|
||||
router.push('InvoiceOutSummary', { id: data.id });
|
||||
const id = data?.[0];
|
||||
if (id) router.push({ name: 'InvoiceOutSummary', params: { id } });
|
||||
} catch (err) {
|
||||
console.error('Error transfering invoice', err);
|
||||
}
|
||||
|
@ -52,22 +88,30 @@ const transferInvoice = async () => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<FetchData
|
||||
url="Clients"
|
||||
@on-fetch="(data) => (clientsOptions = data)"
|
||||
:filter="{ fields: ['id', 'name'], order: 'id', limit: 30 }"
|
||||
auto-load
|
||||
/>
|
||||
<FetchData
|
||||
url="CplusRectificationTypes"
|
||||
:filter="{ order: 'description' }"
|
||||
@on-fetch="(data) => (rectificativeTypeOptions = data)"
|
||||
@on-fetch="
|
||||
(data) => (
|
||||
(rectificativeTypeOptions = data),
|
||||
(transferInvoiceParams.cplusRectificationTypeFk = data.filter(
|
||||
(type) => type.description == 'I – Por diferencias'
|
||||
)[0].id)
|
||||
)
|
||||
"
|
||||
auto-load
|
||||
/>
|
||||
<FetchData
|
||||
url="SiiTypeInvoiceOuts"
|
||||
:filter="{ where: { code: { like: 'R%' } } }"
|
||||
@on-fetch="(data) => (siiTypeInvoiceOutsOptions = data)"
|
||||
@on-fetch="
|
||||
(data) => (
|
||||
(siiTypeInvoiceOutsOptions = data),
|
||||
(transferInvoiceParams.siiTypeInvoiceOutFk = data.filter(
|
||||
(type) => type.code == 'R4'
|
||||
)[0].id)
|
||||
)
|
||||
"
|
||||
auto-load
|
||||
/>
|
||||
<FetchData
|
||||
|
@ -76,7 +120,7 @@ const transferInvoice = async () => {
|
|||
auto-load
|
||||
/>
|
||||
<FormPopup
|
||||
@on-submit="transferInvoice()"
|
||||
@on-submit="makeInvoice()"
|
||||
:title="t('Transfer invoice')"
|
||||
:custom-submit-button-label="t('Transfer client')"
|
||||
:default-cancel-button="false"
|
||||
|
@ -91,13 +135,18 @@ const transferInvoice = async () => {
|
|||
option-value="id"
|
||||
v-model="transferInvoiceParams.newClientFk"
|
||||
:required="true"
|
||||
url="Clients"
|
||||
:fields="['id', 'name', 'hasToInvoiceByAddress']"
|
||||
auto-load
|
||||
>
|
||||
<template #option="scope">
|
||||
<QItem v-bind="scope.itemProps">
|
||||
<QItem
|
||||
v-bind="scope.itemProps"
|
||||
@click="selectedClient(scope.opt)"
|
||||
>
|
||||
<QItemSection>
|
||||
<QItemLabel>
|
||||
#{{ scope.opt?.id }} -
|
||||
{{ scope.opt?.name }}
|
||||
#{{ scope.opt?.id }} - {{ scope.opt?.name }}
|
||||
</QItemLabel>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
|
@ -144,11 +193,23 @@ const transferInvoice = async () => {
|
|||
:required="true"
|
||||
/>
|
||||
</VnRow>
|
||||
<VnRow class="row q-gutter-md q-mb-md">
|
||||
<div>
|
||||
<QCheckbox :label="t('Bill destination client')" v-model="checked" />
|
||||
<QIcon name="info" class="cursor-info q-ml-sm" size="sm">
|
||||
<QTooltip>{{ t('transferInvoiceInfo') }}</QTooltip>
|
||||
</QIcon>
|
||||
</div>
|
||||
</VnRow>
|
||||
</template>
|
||||
</FormPopup>
|
||||
</template>
|
||||
|
||||
<i18n>
|
||||
en:
|
||||
checkInfo: New tickets from the destination customer will be generated in the consignee by default.
|
||||
transferInvoiceInfo: Destination customer is marked to bill in the consignee
|
||||
confirmTransferInvoice: The destination customer has selected to bill in the consignee, do you want to continue?
|
||||
es:
|
||||
Transfer invoice: Transferir factura
|
||||
Transfer client: Transferir cliente
|
||||
|
@ -157,4 +218,7 @@ es:
|
|||
Class: Clase
|
||||
Type: Tipo
|
||||
Transferred invoice: Factura transferida
|
||||
Bill destination client: Facturar cliente destino
|
||||
transferInvoiceInfo: Los nuevos tickets del cliente destino, serán generados en el consignatario por defecto.
|
||||
confirmTransferInvoice: El cliente destino tiene marcado facturar por consignatario, desea continuar?
|
||||
</i18n>
|
||||
|
|
|
@ -11,6 +11,7 @@ import VnSelect from 'src/components/common/VnSelect.vue';
|
|||
import VnRow from 'components/ui/VnRow.vue';
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import { useClipboard } from 'src/composables/useClipboard';
|
||||
import VnImg from 'src/components/ui/VnImg.vue';
|
||||
|
||||
const state = useState();
|
||||
const session = useSession();
|
||||
|
@ -47,7 +48,6 @@ const darkMode = computed({
|
|||
});
|
||||
|
||||
const user = state.getUser();
|
||||
const token = session.getTokenMultimedia();
|
||||
const warehousesData = ref();
|
||||
const companiesData = ref();
|
||||
const accountBankData = ref();
|
||||
|
@ -149,10 +149,7 @@ function saveUserData(param, value) {
|
|||
|
||||
<div class="col column items-center q-mb-sm">
|
||||
<QAvatar size="80px">
|
||||
<QImg
|
||||
:src="`/api/Images/user/160x160/${user.id}/download?access_token=${token}`"
|
||||
spinner-color="white"
|
||||
/>
|
||||
<VnImg :id="user.id" collection="user" size="160x160" />
|
||||
</QAvatar>
|
||||
|
||||
<div class="text-subtitle1 q-mt-md">
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
<script setup>
|
||||
defineProps({
|
||||
columns: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
row: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
|
||||
function stopEventPropagation(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<slot name="beforeChip" :row="row"></slot>
|
||||
<span
|
||||
v-for="col of columns"
|
||||
:key="col.name"
|
||||
@click="stopEventPropagation"
|
||||
class="cursor-text"
|
||||
>
|
||||
<QChip
|
||||
v-if="col.chip.condition(row[col.name], row)"
|
||||
:title="col.label"
|
||||
:class="[
|
||||
col.chip.color
|
||||
? col.chip.color(row)
|
||||
: !col.chip.icon && 'bg-chip-secondary',
|
||||
col.chip.icon && 'q-px-none',
|
||||
]"
|
||||
dense
|
||||
square
|
||||
>
|
||||
<span v-if="!col.chip.icon">{{ row[col.name] }}</span>
|
||||
<QIcon v-else :name="col.chip.icon" color="primary-light" />
|
||||
</QChip>
|
||||
</span>
|
||||
<slot name="afterChip" :row="row"></slot>
|
||||
</template>
|
||||
<style lang="scss">
|
||||
.bg-chip-secondary {
|
||||
background-color: var(--vn-page-color);
|
||||
color: var(--vn-text-color);
|
||||
}
|
||||
|
||||
.cursor-text {
|
||||
pointer-events: all;
|
||||
cursor: text;
|
||||
user-select: all;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,161 @@
|
|||
<script setup>
|
||||
import { markRaw, computed, defineModel } from 'vue';
|
||||
import { QIcon, QCheckbox } from 'quasar';
|
||||
import { dashIfEmpty } from 'src/filters';
|
||||
|
||||
/* basic input */
|
||||
import VnSelect from 'components/common/VnSelect.vue';
|
||||
import VnInput from 'components/common/VnInput.vue';
|
||||
import VnInputDate from 'components/common/VnInputDate.vue';
|
||||
import VnComponent from 'components/common/VnComponent.vue';
|
||||
|
||||
const model = defineModel(undefined, { required: true });
|
||||
const $props = defineProps({
|
||||
column: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
row: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
default: {
|
||||
type: [Object, String],
|
||||
default: null,
|
||||
},
|
||||
componentProp: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
isEditable: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
components: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
showLabel: {
|
||||
type: Boolean,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
|
||||
const defaultComponents = {
|
||||
input: {
|
||||
component: markRaw(VnInput),
|
||||
attrs: {
|
||||
disable: !$props.isEditable,
|
||||
},
|
||||
forceAttrs: {
|
||||
label: $props.showLabel && $props.column.label,
|
||||
},
|
||||
},
|
||||
number: {
|
||||
component: markRaw(VnInput),
|
||||
attrs: {
|
||||
disable: !$props.isEditable,
|
||||
},
|
||||
forceAttrs: {
|
||||
label: $props.showLabel && $props.column.label,
|
||||
},
|
||||
},
|
||||
date: {
|
||||
component: markRaw(VnInputDate),
|
||||
attrs: {
|
||||
readonly: true,
|
||||
disable: !$props.isEditable,
|
||||
style: 'min-width: 125px',
|
||||
},
|
||||
forceAttrs: {
|
||||
label: $props.showLabel && $props.column.label,
|
||||
},
|
||||
},
|
||||
checkbox: {
|
||||
component: markRaw(QCheckbox),
|
||||
attrs: (prop) => {
|
||||
const defaultAttrs = {
|
||||
disable: !$props.isEditable,
|
||||
'model-value': Boolean(prop),
|
||||
class: 'no-padding',
|
||||
};
|
||||
|
||||
if (typeof prop == 'number') {
|
||||
defaultAttrs['true-value'] = 1;
|
||||
defaultAttrs['false-value'] = 0;
|
||||
}
|
||||
return defaultAttrs;
|
||||
},
|
||||
forceAttrs: {
|
||||
label: $props.showLabel && $props.column.label,
|
||||
},
|
||||
},
|
||||
select: {
|
||||
component: markRaw(VnSelect),
|
||||
attrs: {
|
||||
disable: !$props.isEditable,
|
||||
},
|
||||
forceAttrs: {
|
||||
label: $props.showLabel && $props.column.label,
|
||||
},
|
||||
},
|
||||
icon: {
|
||||
component: markRaw(QIcon),
|
||||
},
|
||||
};
|
||||
|
||||
const value = computed(() => {
|
||||
return $props.column.format
|
||||
? $props.column.format($props.row, dashIfEmpty)
|
||||
: dashIfEmpty($props.row[$props.column.name]);
|
||||
});
|
||||
|
||||
const col = computed(() => {
|
||||
let newColumn = { ...$props.column };
|
||||
const specific = newColumn[$props.componentProp];
|
||||
if (specific) {
|
||||
newColumn = {
|
||||
...newColumn,
|
||||
...specific,
|
||||
...specific.attrs,
|
||||
...specific.forceAttrs,
|
||||
};
|
||||
}
|
||||
if (
|
||||
(newColumn.name.startsWith('is') || newColumn.name.startsWith('has')) &&
|
||||
!newColumn.component
|
||||
)
|
||||
newColumn.component = 'checkbox';
|
||||
if ($props.default && !newColumn.component) newColumn.component = $props.default;
|
||||
|
||||
return newColumn;
|
||||
});
|
||||
|
||||
const components = computed(() => $props.components ?? defaultComponents);
|
||||
</script>
|
||||
<template>
|
||||
<div class="row no-wrap fit">
|
||||
<VnComponent
|
||||
v-if="col.before"
|
||||
:prop="col.before"
|
||||
:components="components"
|
||||
:value="model"
|
||||
v-model="model"
|
||||
/>
|
||||
<VnComponent
|
||||
v-if="col.component"
|
||||
:prop="col"
|
||||
:components="components"
|
||||
:value="model"
|
||||
v-model="model"
|
||||
/>
|
||||
<span :title="value" v-else>{{ value }}</span>
|
||||
<VnComponent
|
||||
v-if="col.after"
|
||||
:prop="col.after"
|
||||
:components="components"
|
||||
:value="model"
|
||||
v-model="model"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,146 @@
|
|||
<script setup>
|
||||
import { markRaw, computed, defineModel } from 'vue';
|
||||
import { QCheckbox } from 'quasar';
|
||||
import { useArrayData } from 'composables/useArrayData';
|
||||
|
||||
/* basic input */
|
||||
import VnSelect from 'components/common/VnSelect.vue';
|
||||
import VnInput from 'components/common/VnInput.vue';
|
||||
import VnInputDate from 'components/common/VnInputDate.vue';
|
||||
import VnTableColumn from 'components/VnTable/VnColumn.vue';
|
||||
|
||||
const $props = defineProps({
|
||||
column: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
showTitle: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
dataKey: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
searchUrl: {
|
||||
type: String,
|
||||
default: 'params',
|
||||
},
|
||||
});
|
||||
const model = defineModel(undefined, { required: true });
|
||||
const arrayData = useArrayData($props.dataKey, { searchUrl: $props.searchUrl });
|
||||
const columnFilter = computed(() => $props.column?.columnFilter);
|
||||
|
||||
const updateEvent = { 'update:modelValue': addFilter };
|
||||
const enterEvent = {
|
||||
'keyup.enter': () => addFilter(model.value),
|
||||
remove: () => addFilter(null),
|
||||
};
|
||||
|
||||
const defaultAttrs = {
|
||||
filled: !$props.showTitle,
|
||||
class: 'q-px-sm q-pb-xs q-pt-none',
|
||||
dense: true,
|
||||
};
|
||||
|
||||
const forceAttrs = {
|
||||
label: $props.showTitle ? '' : $props.column.label,
|
||||
};
|
||||
|
||||
const components = {
|
||||
input: {
|
||||
component: markRaw(VnInput),
|
||||
event: enterEvent,
|
||||
attrs: {
|
||||
...defaultAttrs,
|
||||
clearable: true,
|
||||
},
|
||||
forceAttrs,
|
||||
},
|
||||
number: {
|
||||
component: markRaw(VnInput),
|
||||
event: enterEvent,
|
||||
attrs: {
|
||||
...defaultAttrs,
|
||||
clearable: true,
|
||||
},
|
||||
forceAttrs,
|
||||
},
|
||||
date: {
|
||||
component: markRaw(VnInputDate),
|
||||
event: updateEvent,
|
||||
attrs: {
|
||||
...defaultAttrs,
|
||||
style: 'min-width: 150px',
|
||||
},
|
||||
forceAttrs,
|
||||
},
|
||||
checkbox: {
|
||||
component: markRaw(QCheckbox),
|
||||
event: updateEvent,
|
||||
attrs: {
|
||||
dense: true,
|
||||
class: $props.showTitle ? 'q-py-sm q-mt-md' : 'q-px-md q-py-xs',
|
||||
'toggle-indeterminate': true,
|
||||
},
|
||||
forceAttrs,
|
||||
},
|
||||
select: {
|
||||
component: markRaw(VnSelect),
|
||||
event: updateEvent,
|
||||
attrs: {
|
||||
class: 'q-px-md q-pb-xs q-pt-none',
|
||||
dense: true,
|
||||
filled: !$props.showTitle,
|
||||
},
|
||||
forceAttrs,
|
||||
},
|
||||
};
|
||||
|
||||
async function addFilter(value) {
|
||||
value ??= undefined;
|
||||
if (value && typeof value === 'object') value = model.value;
|
||||
value = value === '' ? undefined : value;
|
||||
let field = columnFilter.value?.name ?? $props.column.name;
|
||||
|
||||
if (columnFilter.value?.inWhere) {
|
||||
if (columnFilter.value.alias) field = columnFilter.value.alias + '.' + field;
|
||||
return await arrayData.addFilterWhere({ [field]: value });
|
||||
}
|
||||
await arrayData.addFilter({ params: { [field]: value } });
|
||||
}
|
||||
|
||||
function alignRow() {
|
||||
switch ($props.column.align) {
|
||||
case 'left':
|
||||
return 'justify-start items-start';
|
||||
case 'right':
|
||||
return 'justify-end items-end';
|
||||
default:
|
||||
return 'flex-center';
|
||||
}
|
||||
}
|
||||
|
||||
const showFilter = computed(
|
||||
() => $props.column?.columnFilter !== false && $props.column.name != 'tableActions'
|
||||
);
|
||||
</script>
|
||||
<template>
|
||||
<div
|
||||
v-if="showTitle"
|
||||
class="q-pt-sm q-px-sm ellipsis"
|
||||
:class="`text-${column?.align ?? 'left'}`"
|
||||
:style="!showFilter ? { 'min-height': 72 + 'px' } : ''"
|
||||
>
|
||||
{{ column?.label }}
|
||||
</div>
|
||||
<div v-if="showFilter" class="full-width" :class="alignRow()">
|
||||
<VnTableColumn
|
||||
:column="$props.column"
|
||||
default="input"
|
||||
v-model="model"
|
||||
:components="components"
|
||||
component-prop="columnFilter"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,628 @@
|
|||
<script setup>
|
||||
import { ref, onMounted, computed, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { useQuasar } from 'quasar';
|
||||
|
||||
import { useStateStore } from 'stores/useStateStore';
|
||||
import FormModelPopup from 'components/FormModelPopup.vue';
|
||||
import CrudModel from 'src/components/CrudModel.vue';
|
||||
import VnFilterPanel from 'components/ui/VnFilterPanel.vue';
|
||||
import VnLv from 'components/ui/VnLv.vue';
|
||||
|
||||
import VnTableColumn from 'components/VnTable/VnColumn.vue';
|
||||
import VnTableFilter from 'components/VnTable/VnFilter.vue';
|
||||
import VnTableChip from 'components/VnTable/VnChip.vue';
|
||||
|
||||
const $props = defineProps({
|
||||
columns: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
defaultMode: {
|
||||
type: String,
|
||||
default: 'card', // 'table', 'card'
|
||||
},
|
||||
columnSearch: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
rightSearch: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
rowClick: {
|
||||
type: Function,
|
||||
default: null,
|
||||
},
|
||||
redirect: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
create: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
cardClass: {
|
||||
type: String,
|
||||
default: 'flex-one',
|
||||
},
|
||||
searchUrl: {
|
||||
type: String,
|
||||
default: 'table',
|
||||
},
|
||||
isEditable: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
useModel: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
const { t } = useI18n();
|
||||
const stateStore = useStateStore();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const quasar = useQuasar();
|
||||
|
||||
const DEFAULT_MODE = 'card';
|
||||
const TABLE_MODE = 'table';
|
||||
const mode = ref(DEFAULT_MODE);
|
||||
const selected = ref([]);
|
||||
const routeQuery = JSON.parse(route?.query[$props.searchUrl] ?? '{}');
|
||||
const params = ref({ ...routeQuery, ...routeQuery.filter?.where });
|
||||
const CrudModelRef = ref({});
|
||||
const showForm = ref(false);
|
||||
const splittedColumns = ref({ columns: [] });
|
||||
const tableModes = [
|
||||
{
|
||||
icon: 'view_column',
|
||||
title: t('table view'),
|
||||
value: TABLE_MODE,
|
||||
},
|
||||
{
|
||||
icon: 'grid_view',
|
||||
title: t('grid view'),
|
||||
value: DEFAULT_MODE,
|
||||
},
|
||||
];
|
||||
|
||||
onMounted(() => {
|
||||
mode.value = quasar.platform.is.mobile ? DEFAULT_MODE : $props.defaultMode;
|
||||
stateStore.rightDrawer = true;
|
||||
setUserParams(route.query[$props.searchUrl]);
|
||||
});
|
||||
|
||||
watch(
|
||||
() => $props.columns,
|
||||
(value) => splitColumns(value),
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
watch(
|
||||
() => route.query[$props.searchUrl],
|
||||
(val) => setUserParams(val)
|
||||
);
|
||||
|
||||
function setUserParams(watchedParams) {
|
||||
if (!watchedParams) return;
|
||||
|
||||
if (typeof watchedParams == 'string') watchedParams = JSON.parse(watchedParams);
|
||||
const where = JSON.parse(watchedParams?.filter)?.where;
|
||||
watchedParams = { ...watchedParams, ...where };
|
||||
delete watchedParams.filter;
|
||||
params.value = { ...params.value, ...watchedParams };
|
||||
}
|
||||
|
||||
function splitColumns(columns) {
|
||||
splittedColumns.value = {
|
||||
columns: [],
|
||||
chips: [],
|
||||
create: [],
|
||||
visible: [],
|
||||
};
|
||||
|
||||
for (const col of columns) {
|
||||
if (col.name == 'tableActions') splittedColumns.value.actions = col;
|
||||
if (col.chip) splittedColumns.value.chips.push(col);
|
||||
if (col.isTitle) splittedColumns.value.title = col;
|
||||
if (col.create) splittedColumns.value.create.push(col);
|
||||
if (col.cardVisible) splittedColumns.value.visible.push(col);
|
||||
if ($props.isEditable && col.disable == null) col.disable = false;
|
||||
if ($props.useModel) col.columnFilter = { ...col.columnFilter, inWhere: true };
|
||||
splittedColumns.value.columns.push(col);
|
||||
}
|
||||
// Status column
|
||||
if (splittedColumns.value.chips.length) {
|
||||
splittedColumns.value.columnChips = splittedColumns.value.chips.filter(
|
||||
(c) => !c.isId
|
||||
);
|
||||
if (splittedColumns.value.columnChips.length)
|
||||
splittedColumns.value.columns.unshift({
|
||||
align: 'left',
|
||||
label: t('status'),
|
||||
name: 'tableStatus',
|
||||
columnFilter: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const rowClickFunction = computed(() => {
|
||||
if ($props.rowClick) return $props.rowClick;
|
||||
if ($props.redirect) return ({ id }) => redirectFn(id);
|
||||
return () => {};
|
||||
});
|
||||
|
||||
function redirectFn(id) {
|
||||
router.push({ path: `/${$props.redirect}/${id}` });
|
||||
}
|
||||
|
||||
function stopEventPropagation(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
function reload(params) {
|
||||
CrudModelRef.value.reload(params);
|
||||
}
|
||||
|
||||
function columnName(col) {
|
||||
const column = { ...col, ...col.columnFilter };
|
||||
let name = column.name;
|
||||
if (column.alias) name = column.alias + '.' + name;
|
||||
return name;
|
||||
}
|
||||
|
||||
function getColAlign(col) {
|
||||
return 'text-' + (col.align ?? 'left')
|
||||
}
|
||||
defineExpose({
|
||||
reload,
|
||||
redirect: redirectFn,
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<QDrawer
|
||||
v-if="$props.rightSearch"
|
||||
v-model="stateStore.rightDrawer"
|
||||
side="right"
|
||||
:width="256"
|
||||
show-if-above
|
||||
>
|
||||
<QScrollArea class="fit">
|
||||
<VnFilterPanel
|
||||
:data-key="$attrs['data-key']"
|
||||
:search-button="true"
|
||||
v-model="params"
|
||||
:disable-submit-event="true"
|
||||
:search-url="searchUrl"
|
||||
>
|
||||
<template #body>
|
||||
<VnTableFilter
|
||||
:column="col"
|
||||
:data-key="$attrs['data-key']"
|
||||
v-for="col of splittedColumns.columns"
|
||||
:key="col.id"
|
||||
v-model="params[columnName(col)]"
|
||||
:search-url="searchUrl"
|
||||
/>
|
||||
</template>
|
||||
<slot
|
||||
name="moreFilterPanel"
|
||||
:params="params"
|
||||
:columns="splittedColumns.columns"
|
||||
/>
|
||||
</VnFilterPanel>
|
||||
</QScrollArea>
|
||||
</QDrawer>
|
||||
<!-- class in div to fix warn-->
|
||||
<div class="q-px-md">
|
||||
<CrudModel
|
||||
v-bind="$attrs"
|
||||
:limit="20"
|
||||
ref="CrudModelRef"
|
||||
:search-url="searchUrl"
|
||||
:disable-infinite-scroll="mode == TABLE_MODE"
|
||||
@save-changes="reload"
|
||||
:has-subtoolbar="isEditable"
|
||||
>
|
||||
<template #body="{ rows }">
|
||||
<QTable
|
||||
v-bind="$attrs['QTable']"
|
||||
class="vnTable"
|
||||
:columns="splittedColumns.columns"
|
||||
:rows="rows"
|
||||
v-model:selected="selected"
|
||||
:grid="mode != TABLE_MODE"
|
||||
table-header-class="bg-header"
|
||||
card-container-class="grid-three"
|
||||
flat
|
||||
:style="mode == TABLE_MODE && 'max-height: 90vh'"
|
||||
virtual-scroll
|
||||
@virtual-scroll="
|
||||
(event) =>
|
||||
event.index > rows.length - 2 &&
|
||||
CrudModelRef.vnPaginateRef.paginate()
|
||||
"
|
||||
@row-click="(_, row) => rowClickFunction(row)"
|
||||
>
|
||||
<template #top-left>
|
||||
<slot name="top-left"></slot>
|
||||
</template>
|
||||
<template #top-right>
|
||||
<!-- <QBtn
|
||||
icon="visibility"
|
||||
title="asd"
|
||||
class="bg-vn-section-color q-mr-md"
|
||||
dense
|
||||
v-if="mode == 'table'"
|
||||
/> -->
|
||||
<QBtnToggle
|
||||
v-model="mode"
|
||||
toggle-color="primary"
|
||||
class="bg-vn-section-color"
|
||||
dense
|
||||
:options="tableModes"
|
||||
/>
|
||||
<QBtn
|
||||
icon="filter_alt"
|
||||
title="asd"
|
||||
class="bg-vn-section-color q-ml-md"
|
||||
dense
|
||||
@click="stateStore.toggleRightDrawer()"
|
||||
/>
|
||||
</template>
|
||||
<template #header-cell="{ col }">
|
||||
<QTh
|
||||
auto-width
|
||||
style="min-width: 100px"
|
||||
v-if="$props.columnSearch"
|
||||
>
|
||||
<VnTableFilter
|
||||
:column="col"
|
||||
:show-title="true"
|
||||
:data-key="$attrs['data-key']"
|
||||
v-model="params[columnName(col)]"
|
||||
:search-url="searchUrl"
|
||||
/>
|
||||
</QTh>
|
||||
</template>
|
||||
<template #header-cell-tableActions>
|
||||
<QTh auto-width class="sticky" />
|
||||
</template>
|
||||
<template #body-cell-tableStatus="{ col, row }">
|
||||
<QTd auto-width :class="getColAlign(col)">
|
||||
<VnTableChip
|
||||
:columns="splittedColumns.columnChips"
|
||||
:row="row"
|
||||
>
|
||||
<template #afterChip>
|
||||
<slot name="afterChip" :row="row"></slot>
|
||||
</template>
|
||||
</VnTableChip>
|
||||
</QTd>
|
||||
</template>
|
||||
<template #body-cell="{ col, row }">
|
||||
<!-- Columns -->
|
||||
<QTd
|
||||
auto-width
|
||||
class="no-margin q-px-xs"
|
||||
:class="getColAlign(col)"
|
||||
>
|
||||
<VnTableColumn
|
||||
:column="col"
|
||||
:row="row"
|
||||
:is-editable="false"
|
||||
v-model="row[col.name]"
|
||||
component-prop="columnField"
|
||||
/>
|
||||
</QTd>
|
||||
</template>
|
||||
<template #body-cell-tableActions="{ col, row }">
|
||||
<QTd
|
||||
auto-width
|
||||
:class="getColAlign(col)"
|
||||
class="sticky no-padding"
|
||||
@click="stopEventPropagation($event)"
|
||||
>
|
||||
<QBtn
|
||||
v-for="(btn, index) of col.actions"
|
||||
:key="index"
|
||||
:title="btn.title"
|
||||
:icon="btn.icon"
|
||||
class="q-px-sm"
|
||||
flat
|
||||
:class="
|
||||
btn.isPrimary
|
||||
? 'text-primary-light'
|
||||
: 'color-vn-text '
|
||||
"
|
||||
@click="btn.action(row)"
|
||||
/>
|
||||
</QTd>
|
||||
</template>
|
||||
<template #item="{ row, colsMap }">
|
||||
<component
|
||||
:is="$props.redirect ? 'router-link' : 'span'"
|
||||
:to="`/${$props.redirect}/` + row.id"
|
||||
>
|
||||
<QCard
|
||||
bordered
|
||||
flat
|
||||
class="row no-wrap justify-between cursor-pointer"
|
||||
@click="
|
||||
(_, row) => {
|
||||
$props.rowClick && $props.rowClick(row);
|
||||
}
|
||||
"
|
||||
>
|
||||
<QCardSection
|
||||
vertical
|
||||
class="no-margin no-padding"
|
||||
:class="colsMap.tableActions ? 'w-80' : 'fit'"
|
||||
>
|
||||
<!-- Chips -->
|
||||
<QCardSection
|
||||
v-if="splittedColumns.chips.length"
|
||||
class="no-margin q-px-xs q-py-none"
|
||||
>
|
||||
<VnTableChip
|
||||
:columns="splittedColumns.chips"
|
||||
:row="row"
|
||||
>
|
||||
<template #afterChip>
|
||||
<slot name="afterChip" :row="row"></slot>
|
||||
</template>
|
||||
</VnTableChip>
|
||||
</QCardSection>
|
||||
<!-- Title -->
|
||||
<QCardSection
|
||||
v-if="splittedColumns.title"
|
||||
class="q-pl-sm q-py-none text-primary-light text-bold text-h6 cardEllipsis"
|
||||
>
|
||||
<span
|
||||
:title="row[splittedColumns.title.name]"
|
||||
@click="stopEventPropagation($event)"
|
||||
class="cursor-text"
|
||||
>
|
||||
{{ row[splittedColumns.title.name] }}
|
||||
</span>
|
||||
</QCardSection>
|
||||
<!-- Fields -->
|
||||
<QCardSection
|
||||
class="q-pl-sm q-pr-lg q-py-xs"
|
||||
:class="$props.cardClass"
|
||||
>
|
||||
<div
|
||||
v-for="col of splittedColumns.visible"
|
||||
:key="col.name"
|
||||
class="fields"
|
||||
>
|
||||
<VnLv
|
||||
:label="
|
||||
!col.component &&
|
||||
col.label &&
|
||||
`${col.label}:`
|
||||
"
|
||||
>
|
||||
<template #value>
|
||||
<span
|
||||
@click="
|
||||
stopEventPropagation($event)
|
||||
"
|
||||
>
|
||||
<VnTableColumn
|
||||
:column="col"
|
||||
:row="row"
|
||||
:is-editable="false"
|
||||
v-model="row[col.name]"
|
||||
component-prop="columnField"
|
||||
:show-label="true"
|
||||
/>
|
||||
</span>
|
||||
</template>
|
||||
</VnLv>
|
||||
</div>
|
||||
</QCardSection>
|
||||
</QCardSection>
|
||||
<!-- Actions -->
|
||||
<QCardSection
|
||||
v-if="colsMap.tableActions"
|
||||
class="column flex-center w-10 no-margin q-pa-xs q-gutter-y-xs"
|
||||
@click="stopEventPropagation($event)"
|
||||
>
|
||||
<QBtn
|
||||
v-for="(btn, index) of splittedColumns.actions
|
||||
.actions"
|
||||
:key="index"
|
||||
:title="btn.title"
|
||||
:icon="btn.icon"
|
||||
class="q-pa-xs"
|
||||
flat
|
||||
:class="
|
||||
btn.isPrimary
|
||||
? 'text-primary-light'
|
||||
: 'color-vn-text '
|
||||
"
|
||||
@click="btn.action(row)"
|
||||
/>
|
||||
</QCardSection>
|
||||
</QCard>
|
||||
</component>
|
||||
</template>
|
||||
</QTable>
|
||||
</template>
|
||||
</CrudModel>
|
||||
</div>
|
||||
<QPageSticky v-if="create" :offset="[20, 20]" style="z-index: 2">
|
||||
<QBtn @click="showForm = !showForm" color="primary" fab icon="add" />
|
||||
<QTooltip>
|
||||
{{ create.title }}
|
||||
</QTooltip>
|
||||
</QPageSticky>
|
||||
<QDialog v-model="showForm" transition-show="scale" transition-hide="scale">
|
||||
<FormModelPopup
|
||||
v-bind="create"
|
||||
:model="$attrs['data-key'] + 'Create'"
|
||||
@on-data-saved="(_, res) => create.onDataSaved(res)"
|
||||
>
|
||||
<template #form-inputs="{ data }">
|
||||
<div class="grid-create">
|
||||
<VnTableColumn
|
||||
v-for="column of splittedColumns.create"
|
||||
:key="column.name"
|
||||
:column="column"
|
||||
:row="{}"
|
||||
default="input"
|
||||
v-model="data[column.name]"
|
||||
:show-label="true"
|
||||
/>
|
||||
<slot name="more-create-dialog" :data="data" />
|
||||
</div>
|
||||
</template>
|
||||
</FormModelPopup>
|
||||
</QDialog>
|
||||
</template>
|
||||
<i18n>
|
||||
en:
|
||||
status: Status
|
||||
es:
|
||||
status: Estados
|
||||
</i18n>
|
||||
|
||||
<style lang="scss">
|
||||
.bg-chip-secondary {
|
||||
background-color: var(--vn-page-color);
|
||||
color: var(--vn-text-color);
|
||||
}
|
||||
|
||||
.bg-header {
|
||||
background-color: #5d5d5d;
|
||||
color: var(--vn-text-color);
|
||||
}
|
||||
|
||||
.q-table--dark .q-table__bottom,
|
||||
.q-table--dark thead,
|
||||
.q-table--dark tr,
|
||||
.q-table--dark th,
|
||||
.q-table--dark td {
|
||||
border-color: #222222;
|
||||
}
|
||||
|
||||
.q-table__container > div:first-child {
|
||||
background-color: var(--vn-page-color);
|
||||
}
|
||||
|
||||
.grid-three {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(400px, max-content));
|
||||
max-width: 100%;
|
||||
grid-gap: 20px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.grid-create {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(150px, max-content));
|
||||
max-width: 100%;
|
||||
grid-gap: 20px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.flex-one {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
div.fields {
|
||||
width: 100%;
|
||||
.vn-label-value {
|
||||
display: flex;
|
||||
gap: 2%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.q-table th {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.vnTable {
|
||||
thead tr th {
|
||||
position: sticky;
|
||||
z-index: 2;
|
||||
}
|
||||
thead tr:first-child th {
|
||||
top: 0;
|
||||
}
|
||||
.q-table__top {
|
||||
top: 0;
|
||||
}
|
||||
tbody {
|
||||
.q-checkbox {
|
||||
display: flex;
|
||||
margin-bottom: 9px;
|
||||
& .q-checkbox__label {
|
||||
margin-left: 31px;
|
||||
color: var(--vn-text-color);
|
||||
}
|
||||
& .q-checkbox__inner {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
color: var(--vn-label-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
.sticky {
|
||||
position: sticky;
|
||||
right: 0;
|
||||
}
|
||||
td.sticky {
|
||||
background-color: var(--q-dark);
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.vn-label-value {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
color: var(--vn-text-color);
|
||||
.value {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
pointer-events: all;
|
||||
cursor: text;
|
||||
user-select: all;
|
||||
}
|
||||
}
|
||||
|
||||
.cardEllipsis {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.grid-two {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(150px, max-content));
|
||||
max-width: 100%;
|
||||
margin: 0 auto;
|
||||
overflow: scroll;
|
||||
white-space: wrap;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.w-80 {
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.w-20 {
|
||||
width: 20%;
|
||||
}
|
||||
|
||||
.cursor-text {
|
||||
pointer-events: all;
|
||||
cursor: text;
|
||||
user-select: all;
|
||||
}
|
||||
</style>
|
|
@ -1,6 +1,6 @@
|
|||
<script setup>
|
||||
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: {
|
||||
|
@ -17,6 +22,10 @@ const $props = defineProps({
|
|||
type: String,
|
||||
default: '',
|
||||
},
|
||||
clearable: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
});
|
||||
|
||||
const { t } = useI18n();
|
||||
|
@ -78,13 +87,12 @@ const inputRules = [
|
|||
<template v-if="$slots.prepend" #prepend>
|
||||
<slot name="prepend" />
|
||||
</template>
|
||||
|
||||
<template #append>
|
||||
<slot name="append" v-if="$slots.append && !$attrs.disabled" />
|
||||
<QIcon
|
||||
name="close"
|
||||
size="xs"
|
||||
v-if="hover && value && !$attrs.disabled"
|
||||
v-if="hover && value && !$attrs.disabled && $props.clearable"
|
||||
@click="value = null"
|
||||
></QIcon>
|
||||
<QIcon v-if="info" name="info">
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<script setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import isValidDate from 'filters/isValidDate';
|
||||
|
||||
const props = defineProps({
|
||||
|
@ -24,6 +25,9 @@ const hover = ref(false);
|
|||
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
|
||||
const { t } = useI18n();
|
||||
const requiredFieldRule = (val) => !!val || t('globals.fieldRequired');
|
||||
|
||||
const joinDateAndTime = (date, time) => {
|
||||
if (!date) {
|
||||
return null;
|
||||
|
@ -91,13 +95,15 @@ const styleAttrs = computed(() => {
|
|||
readonly
|
||||
:model-value="displayDate(value)"
|
||||
v-bind="{ ...$attrs, ...styleAttrs }"
|
||||
:class="{ required: $attrs.required }"
|
||||
:rules="$attrs.required ? [requiredFieldRule] : null"
|
||||
@click="isPopupOpen = true"
|
||||
>
|
||||
<template #append>
|
||||
<QIcon
|
||||
name="close"
|
||||
size="xs"
|
||||
v-if="hover && value"
|
||||
v-if="hover && value && !readonly"
|
||||
@click="onDateUpdate(null)"
|
||||
></QIcon>
|
||||
<QIcon name="event" class="cursor-pointer">
|
||||
|
|
|
@ -17,8 +17,9 @@ const props = defineProps({
|
|||
default: false,
|
||||
},
|
||||
});
|
||||
const { t } = useI18n();
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
const { t } = useI18n();
|
||||
const requiredFieldRule = (val) => !!val || t('globals.fieldRequired');
|
||||
|
||||
const value = computed({
|
||||
get() {
|
||||
|
@ -71,6 +72,8 @@ const styleAttrs = computed(() => {
|
|||
readonly
|
||||
:model-value="formatTime(value)"
|
||||
v-bind="{ ...$attrs, ...styleAttrs }"
|
||||
:class="{ required: $attrs.required }"
|
||||
:rules="$attrs.required ? [requiredFieldRule] : null"
|
||||
@click="isPopupOpen = true"
|
||||
>
|
||||
<template #append>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { ref, onUnmounted } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute } from 'vue-router';
|
||||
import axios from 'axios';
|
||||
|
@ -376,6 +376,10 @@ async function clearFilter() {
|
|||
}
|
||||
|
||||
setLogTree();
|
||||
|
||||
onUnmounted(() => {
|
||||
stateStore.rightDrawer = false;
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<FetchData
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
<script setup>
|
||||
defineProps({
|
||||
title: { type: String, default: null },
|
||||
content: { type: [String, Number], default: null },
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<QPopupProxy>
|
||||
<QCard>
|
||||
<slot name="title">
|
||||
<div
|
||||
class="header q-px-sm q-py-xs q-ma-none text-white text-bold bg-primary"
|
||||
v-text="title"
|
||||
/>
|
||||
</slot>
|
||||
<slot name="content">
|
||||
<QCardSection class="change-detail q-pa-sm">
|
||||
{{ content }}
|
||||
</QCardSection>
|
||||
</slot>
|
||||
</QCard>
|
||||
</QPopupProxy>
|
||||
</template>
|
|
@ -0,0 +1,97 @@
|
|||
<script setup>
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const $props = defineProps({
|
||||
progress: {
|
||||
type: Number, //Progress value (1.0 > x > 0.0)
|
||||
required: true,
|
||||
},
|
||||
showDialog: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
cancelled: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['cancel', 'close']);
|
||||
|
||||
const dialogRef = ref(null);
|
||||
|
||||
const _showDialog = computed({
|
||||
get: () => $props.showDialog,
|
||||
set: (value) => {
|
||||
if (value) dialogRef.value.show();
|
||||
},
|
||||
});
|
||||
|
||||
const _progress = computed(() => $props.progress);
|
||||
|
||||
const progressLabel = computed(() => `${Math.round($props.progress * 100)}%`);
|
||||
|
||||
const cancel = () => {
|
||||
dialogRef.value.hide();
|
||||
emit('cancel');
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<QDialog ref="dialogRef" v-model="_showDialog" @hide="onDialogHide">
|
||||
<QCard class="full-width dialog">
|
||||
<QCardSection class="row">
|
||||
<span class="text-h6">{{ t('Progress') }}</span>
|
||||
<QSpace />
|
||||
<QBtn icon="close" flat round dense @click="emit('close')" />
|
||||
</QCardSection>
|
||||
<QCardSection>
|
||||
<div class="column">
|
||||
<span>{{ t('Total progress') }}:</span>
|
||||
<QLinearProgress
|
||||
size="30px"
|
||||
:value="_progress"
|
||||
color="primary"
|
||||
stripe
|
||||
class="q-mt-sm q-mb-md"
|
||||
>
|
||||
<div class="absolute-full flex flex-center">
|
||||
<QBadge
|
||||
v-if="cancelled"
|
||||
text-color="white"
|
||||
color="negative"
|
||||
:label="t('Cancelled')"
|
||||
/>
|
||||
<span v-else class="text-white text-subtitle1">
|
||||
{{ progressLabel }}
|
||||
</span>
|
||||
</div>
|
||||
</QLinearProgress>
|
||||
<slot />
|
||||
</div>
|
||||
</QCardSection>
|
||||
<QCardActions align="right">
|
||||
<QBtn
|
||||
v-if="!cancelled && progress < 1"
|
||||
type="button"
|
||||
flat
|
||||
class="text-primary"
|
||||
@click="cancel()"
|
||||
>
|
||||
{{ t('globals.cancel') }}
|
||||
</QBtn>
|
||||
</QCardActions>
|
||||
</QCard>
|
||||
</QDialog>
|
||||
</template>
|
||||
|
||||
<i18n>
|
||||
es:
|
||||
Progress: Progreso
|
||||
Total progress: Progreso total
|
||||
Cancelled: Cancelado
|
||||
</i18n>
|
|
@ -1,6 +1,5 @@
|
|||
<script setup>
|
||||
import { ref, toRefs, computed, watch, h } from 'vue';
|
||||
import { onMounted } from 'vue';
|
||||
import { ref, toRefs, computed, watch, h, onMounted } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
const emit = defineEmits(['update:modelValue', 'update:options']);
|
||||
import { useArrayData } from 'src/composables/useArrayData';
|
||||
|
@ -17,11 +16,11 @@ const $props = defineProps({
|
|||
},
|
||||
optionLabel: {
|
||||
type: [String],
|
||||
default: '',
|
||||
default: 'name',
|
||||
},
|
||||
optionValue: {
|
||||
type: String,
|
||||
default: '',
|
||||
default: 'id',
|
||||
},
|
||||
optionFilter: {
|
||||
type: String,
|
||||
|
@ -59,6 +58,10 @@ const $props = defineProps({
|
|||
type: [Number, String],
|
||||
default: '30',
|
||||
},
|
||||
focusOnMount: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const { t } = useI18n();
|
||||
|
@ -144,7 +147,9 @@ async function fetchFilter(val) {
|
|||
: optionValue.value;
|
||||
jsegarra marked this conversation as resolved
Outdated
|
||||
|
||||
const where = { ...{ [key]: { like: `%${val}%` } }, ...$props.where };
|
||||
return arrayData.value.fetch({ fields, where, order: sortBy, limit });
|
||||
const fetchOptions = { where, order: sortBy, limit };
|
||||
if (fields) fetchOptions.fields = fields;
|
||||
alexm
commented
Si de por si solo entra en fetchFilter si hay url, no hace falta separar la construccion del where en una funcion apart, que solo se usa en un sitio Si de por si solo entra en fetchFilter si hay url, no hace falta separar la construccion del where en una funcion apart, que solo se usa en un sitio
jsegarra
commented
Vale, lo debí separar para otra situación que acabé borrando y esto se quedó así Vale, lo debí separar para otra situación que acabé borrando y esto se quedó así
Lo limipio
|
||||
return arrayData.value.fetch(fetchOptions);
|
||||
}
|
||||
|
||||
async function filterHandler(val, update) {
|
||||
|
@ -190,6 +195,10 @@ async function onScroll(scrollEv) {
|
|||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
if ($props.focusOnMount) setTimeout(() => vnSelectRef.value.showPopup(), 300);
|
||||
});
|
||||
</script>
|
||||
|
||||
alexm
commented
Usando arrayData + lo que hablamos ayer creo que ya no hace falta el FetchData Usando arrayData + lo que hablamos ayer creo que ya no hace falta el FetchData
|
||||
<template>
|
||||
|
@ -215,6 +224,7 @@ async function onScroll(scrollEv) {
|
|||
>
|
||||
<template v-if="isClearable" #append>
|
||||
<QIcon
|
||||
v-show="value"
|
||||
name="close"
|
||||
@click.stop="value = null"
|
||||
class="cursor-pointer"
|
||||
|
|
|
@ -184,6 +184,7 @@ en:
|
|||
minAmount: 'A minimum amount of 50€ (VAT excluded) is required for your order
|
||||
{ orderId } of { shipped } to receive it without additional shipping costs.'
|
||||
orderChanges: 'Order {orderId} of { shipped }: { changes }'
|
||||
productNotAvailable: 'Verdnatura communicates: Your order {ticketFk} with reception date on {landed}. {notAvailables} not available. Sorry for the inconvenience.'
|
||||
en: English
|
||||
es: Spanish
|
||||
fr: French
|
||||
|
@ -203,6 +204,7 @@ es:
|
|||
Te recomendamos amplíes para no generar costes extra, provocarán un incremento de tu tarifa.
|
||||
¡Un saludo!'
|
||||
orderChanges: 'Pedido {orderId} con llegada estimada día { landing }: { changes }'
|
||||
productNotAvailable: 'Verdnatura le comunica: Pedido {ticketFk} con fecha de recepción {landed}. {notAvailables} no disponible/s. Disculpe las molestias.'
|
||||
en: Inglés
|
||||
es: Español
|
||||
fr: Francés
|
||||
|
@ -222,6 +224,7 @@ fr:
|
|||
Montant minimum nécessaire de 50 euros pour recevoir la commande { orderId } livraison { landing }.
|
||||
Merci.'
|
||||
orderChanges: 'Commande {orderId} livraison {landing} indisponible/s. Désolés pour le dérangement.'
|
||||
productNotAvailable: 'Verdnatura communique : Votre commande {ticketFk} avec date de réception le {landed}. {notAvailables} non disponible. Nous sommes désolés pour les inconvénients.'
|
||||
en: Anglais
|
||||
es: Espagnol
|
||||
fr: Français
|
||||
|
@ -240,6 +243,7 @@ pt:
|
|||
minAmount: 'É necessário um valor mínimo de 50€ (sem IVA) em seu pedido
|
||||
{ orderId } do dia { landing } para recebê-lo sem custos de envio adicionais.'
|
||||
orderChanges: 'Pedido { orderId } com chegada dia { landing }: { changes }'
|
||||
productNotAvailable: 'Verdnatura comunica: Seu pedido {ticketFk} com data de recepção em {landed}. {notAvailables} não disponível/eis. Desculpe pelo transtorno.'
|
||||
en: Inglês
|
||||
es: Espanhol
|
||||
fr: Francês
|
||||
|
|
|
@ -39,6 +39,7 @@ const $props = defineProps({
|
|||
});
|
||||
|
||||
const state = useState();
|
||||
const route = useRoute();
|
||||
const { t } = useI18n();
|
||||
const { viewSummary } = useSummaryDialog();
|
||||
let arrayData;
|
||||
|
@ -57,7 +58,7 @@ onBeforeMount(async () => {
|
|||
store = arrayData.store;
|
||||
entity = computed(() => (Array.isArray(store.data) ? store.data[0] : store.data));
|
||||
// It enables to load data only once if the module is the same as the dataKey
|
||||
if ($props.dataKey !== useRoute().meta.moduleName) await getData();
|
||||
if ($props.dataKey !== route.meta.moduleName || !route.params.id) await getData();
|
||||
watch(
|
||||
() => [$props.url, $props.filter],
|
||||
async () => await getData()
|
||||
|
|
|
@ -22,11 +22,15 @@ const props = defineProps({
|
|||
type: String,
|
||||
default: '',
|
||||
},
|
||||
moduleName: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
const emit = defineEmits(['onFetch']);
|
||||
const route = useRoute();
|
||||
const isSummary = ref();
|
||||
const arrayData = useArrayData(props.dataKey || route.meta.moduleName, {
|
||||
const arrayData = useArrayData(props.dataKey, {
|
||||
url: props.url,
|
||||
filter: props.filter,
|
||||
skip: 0,
|
||||
|
@ -83,7 +87,7 @@ function existSummary(routes) {
|
|||
v-if="showRedirectToSummaryIcon"
|
||||
class="header link"
|
||||
:to="{
|
||||
name: `${route.meta.moduleName}Summary`,
|
||||
name: `${moduleName ?? route.meta.moduleName}Summary`,
|
||||
params: { id: entityId || entity.id },
|
||||
}"
|
||||
>
|
||||
|
@ -159,9 +163,9 @@ function existSummary(routes) {
|
|||
margin-top: 2px;
|
||||
.label {
|
||||
color: var(--vn-label-color);
|
||||
width: 8em;
|
||||
width: 9em;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
white-space: wrap;
|
||||
text-overflow: ellipsis;
|
||||
margin-right: 10px;
|
||||
flex-grow: 0;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -67,6 +67,7 @@ async function confirm() {
|
|||
</QCardSection>
|
||||
<QCardSection class="row items-center">
|
||||
<span v-html="message"></span>
|
||||
<slot name="customHTML"></slot>
|
||||
</QCardSection>
|
||||
<QCardActions align="right">
|
||||
<QBtn
|
||||
|
|
|
@ -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');
|
||||
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;
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { useSession } from 'src/composables/useSession';
|
||||
|
||||
const $props = defineProps({
|
||||
storage: {
|
||||
type: [String, Number],
|
||||
default: 'Images',
|
||||
},
|
||||
collection: {
|
||||
type: String,
|
||||
default: 'catalog',
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: '200x200',
|
||||
},
|
||||
zoomSize: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'lg',
|
||||
},
|
||||
id: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
const show = ref(false);
|
||||
const token = useSession().getTokenMultimedia();
|
||||
const timeStamp = ref(`timestamp=${Date.now()}`);
|
||||
const url = computed(
|
||||
() =>
|
||||
`/api/${$props.storage}/${$props.collection}/${$props.size}/${$props.id}/download?access_token=${token}&${timeStamp.value}`
|
||||
);
|
||||
const reload = () => {
|
||||
timeStamp.value = `timestamp=${Date.now()}`;
|
||||
};
|
||||
defineExpose({
|
||||
reload,
|
||||
});
|
||||
|
||||
onMounted(() => {});
|
||||
</script>
|
||||
<template>
|
||||
<QImg :src="url" v-bind="$attrs" @click="show = !show" spinner-color="primary" />
|
||||
<QDialog v-model="show" v-if="$props.zoomSize">
|
||||
<QImg
|
||||
:src="url"
|
||||
size="full"
|
||||
class="img_zoom"
|
||||
v-bind="$attrs"
|
||||
spinner-color="primary"
|
||||
/>
|
||||
</QDialog>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.q-img {
|
||||
cursor: zoom-in;
|
||||
min-width: 50px;
|
||||
}
|
||||
.rounded {
|
||||
border-radius: 50%;
|
||||
}
|
||||
.img_zoom {
|
||||
border-radius: 0%;
|
||||
}
|
||||
</style>
|
|
@ -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;
|
||||
|
||||
onMounted(() => {
|
||||
|
@ -20,8 +21,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 };
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -38,13 +44,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])
|
||||
|
@ -85,8 +93,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,
|
||||
|
@ -116,6 +124,10 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
|
|||
}
|
||||
}
|
||||
|
||||
function deleteOption(option) {
|
||||
delete store[option];
|
||||
}
|
||||
|
||||
function cancelRequest() {
|
||||
if (canceller) {
|
||||
canceller.abort();
|
||||
|
@ -126,7 +138,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;
|
||||
|
@ -135,7 +147,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;
|
||||
|
@ -147,15 +159,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;
|
||||
|
@ -180,22 +197,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);
|
||||
|
@ -205,6 +234,7 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
|
|||
fetch,
|
||||
applyFilter,
|
||||
addFilter,
|
||||
addFilterWhere,
|
||||
refresh,
|
||||
destroy,
|
||||
loadMore,
|
||||
|
@ -212,5 +242,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;
|
||||
|
|
|
@ -8,11 +8,8 @@ export default function (value, fractionSize = 2) {
|
|||
const options = {
|
||||
style: 'percent',
|
||||
minimumFractionDigits: fractionSize,
|
||||
maximumFractionDigits: fractionSize
|
||||
maximumFractionDigits: fractionSize,
|
||||
};
|
||||
|
||||
return new Intl.NumberFormat(locale, options)
|
||||
.format(parseFloat(value));
|
||||
|
||||
|
||||
}
|
||||
return new Intl.NumberFormat(locale, options).format(parseFloat(value));
|
||||
}
|
||||
|
|
|
@ -113,6 +113,7 @@ globals:
|
|||
name: Name
|
||||
new: New
|
||||
comment: Comment
|
||||
observations: Observations
|
||||
errors:
|
||||
statusUnauthorized: Access denied
|
||||
statusInternalServerError: An internal server error has ocurred
|
||||
|
@ -279,8 +280,8 @@ customer:
|
|||
extendedList:
|
||||
tableVisibleColumns:
|
||||
id: Identifier
|
||||
name: Name
|
||||
socialName: Social name
|
||||
name: Comercial name
|
||||
socialName: Business name
|
||||
fi: Tax number
|
||||
salesPersonFk: Salesperson
|
||||
credit: Credit
|
||||
|
@ -443,6 +444,10 @@ ticket:
|
|||
sms: Sms
|
||||
notes: Notes
|
||||
sale: Sale
|
||||
ticketAdvance: Advance tickets
|
||||
futureTickets: Future tickets
|
||||
purchaseRequest: Purchase request
|
||||
weeklyTickets: Weekly tickets
|
||||
list:
|
||||
nickname: Nickname
|
||||
state: State
|
||||
|
@ -844,7 +849,7 @@ worker:
|
|||
calendar: Calendar
|
||||
timeControl: Time control
|
||||
locker: Locker
|
||||
|
||||
formation: Formation
|
||||
list:
|
||||
name: Name
|
||||
email: Email
|
||||
|
@ -914,6 +919,16 @@ worker:
|
|||
payMethods: Pay method
|
||||
iban: IBAN
|
||||
bankEntity: Swift / BIC
|
||||
formation:
|
||||
tableVisibleColumns:
|
||||
course: Curso
|
||||
startDate: Fecha Inicio
|
||||
endDate: Fecha Fin
|
||||
center: Centro Formación
|
||||
invoice: Factura
|
||||
amount: Importe
|
||||
remark: Bonficado
|
||||
hasDiploma: Diploma
|
||||
imageNotFound: Image not found
|
||||
wagon:
|
||||
pageTitles:
|
||||
|
@ -992,6 +1007,18 @@ route:
|
|||
shipped: Preparation date
|
||||
viewCmr: View CMR
|
||||
downloadCmrs: Download CMRs
|
||||
columnLabels:
|
||||
Id: Id
|
||||
vehicle: Vehicle
|
||||
description: Description
|
||||
isServed: Served
|
||||
worker: Worker
|
||||
date: Date
|
||||
started: Started
|
||||
actions: Actions
|
||||
agency: Agency
|
||||
volume: Volume
|
||||
finished: Finished
|
||||
supplier:
|
||||
pageTitles:
|
||||
suppliers: Suppliers
|
||||
|
|
|
@ -107,12 +107,14 @@ globals:
|
|||
aliasUsers: Usuarios
|
||||
subRoles: Subroles
|
||||
inheritedRoles: Roles heredados
|
||||
workers: Trabajadores
|
||||
created: Fecha creación
|
||||
worker: Trabajador
|
||||
now: Ahora
|
||||
name: Nombre
|
||||
new: Nuevo
|
||||
comment: Comentario
|
||||
observations: Observaciones
|
||||
errors:
|
||||
statusUnauthorized: Acceso denegado
|
||||
statusInternalServerError: Ha ocurrido un error interno del servidor
|
||||
|
@ -277,7 +279,7 @@ customer:
|
|||
extendedList:
|
||||
tableVisibleColumns:
|
||||
id: Identificador
|
||||
name: Nombre
|
||||
name: Nombre Comercial
|
||||
socialName: Razón social
|
||||
fi: NIF / CIF
|
||||
salesPersonFk: Comercial
|
||||
|
@ -441,6 +443,10 @@ ticket:
|
|||
sms: Sms
|
||||
notes: Notas
|
||||
sale: Lineas del pedido
|
||||
ticketAdvance: Adelantar tickets
|
||||
futureTickets: Tickets a futuro
|
||||
purchaseRequest: Petición de compra
|
||||
weeklyTickets: Tickets programados
|
||||
list:
|
||||
nickname: Alias
|
||||
state: Estado
|
||||
|
@ -839,6 +845,7 @@ worker:
|
|||
calendar: Calendario
|
||||
timeControl: Control de horario
|
||||
locker: Taquilla
|
||||
formation: Formación
|
||||
list:
|
||||
name: Nombre
|
||||
email: Email
|
||||
|
@ -899,6 +906,16 @@ worker:
|
|||
payMethods: Método de pago
|
||||
iban: IBAN
|
||||
bankEntity: Swift / BIC
|
||||
formation:
|
||||
tableVisibleColumns:
|
||||
course: Curso
|
||||
startDate: Fecha Inicio
|
||||
endDate: Fecha Fin
|
||||
center: Centro Formación
|
||||
invoice: Factura
|
||||
amount: Importe
|
||||
remark: Bonficado
|
||||
hasDiploma: Diploma
|
||||
imageNotFound: No se ha encontrado la imagen
|
||||
wagon:
|
||||
pageTitles:
|
||||
|
@ -977,6 +994,18 @@ route:
|
|||
shipped: Fecha preparación
|
||||
viewCmr: Ver CMR
|
||||
downloadCmrs: Descargar CMRs
|
||||
columnLabels:
|
||||
Id: Id
|
||||
vehicle: Vehículo
|
||||
description: Descripción
|
||||
isServed: Servida
|
||||
worker: Trabajador
|
||||
date: Fecha
|
||||
started: Iniciada
|
||||
actions: Acciones
|
||||
agency: Agencia
|
||||
volume: Volumen
|
||||
finished: Finalizada
|
||||
supplier:
|
||||
pageTitles:
|
||||
suppliers: Proveedores
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
<script setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import FormModelPopup from 'components/FormModelPopup.vue';
|
||||
import VnSelect from 'src/components/common/VnSelect.vue';
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
const router = useRouter();
|
||||
|
||||
const newAccountForm = reactive({
|
||||
active: true,
|
||||
});
|
||||
const rolesOptions = ref([]);
|
||||
|
||||
const redirectToAccountBasicData = (_, { id }) => {
|
||||
router.push({ name: 'AccountBasicData', params: { id } });
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FetchData
|
||||
url="VnRoles"
|
||||
:filter="{ fields: ['id', 'name'], order: 'name ASC' }"
|
||||
@on-fetch="(data) => (rolesOptions = data)"
|
||||
auto-load
|
||||
/>
|
||||
<FormModelPopup
|
||||
:title="t('account.card.newUser')"
|
||||
url-create="VnUsers"
|
||||
model="users"
|
||||
:form-initial-data="newAccountForm"
|
||||
@on-data-saved="redirectToAccountBasicData"
|
||||
>
|
||||
<template #form-inputs="{ data, validate }">
|
||||
<div class="column q-gutter-sm">
|
||||
<VnInput
|
||||
v-model="data.name"
|
||||
:label="t('account.create.name')"
|
||||
:rules="validate('VnUser.name')"
|
||||
/>
|
||||
<VnInput
|
||||
v-model="data.nickname"
|
||||
:label="t('account.create.nickname')"
|
||||
:rules="validate('VnUser.nickname')"
|
||||
/>
|
||||
<VnInput
|
||||
v-model="data.email"
|
||||
:label="t('account.create.email')"
|
||||
type="email"
|
||||
:rules="validate('VnUser.email')"
|
||||
/>
|
||||
<VnSelect
|
||||
:label="t('account.create.role')"
|
||||
v-model="data.roleFk"
|
||||
:options="rolesOptions"
|
||||
option-value="id"
|
||||
option-label="name"
|
||||
map-options
|
||||
hide-selected
|
||||
:rules="validate('VnUser.roleFk')"
|
||||
/>
|
||||
<VnInput
|
||||
v-model="data.password"
|
||||
:label="t('account.create.password')"
|
||||
type="password"
|
||||
:rules="validate('VnUser.password')"
|
||||
/>
|
||||
<QCheckbox
|
||||
:label="t('account.create.active')"
|
||||
v-model="data.active"
|
||||
:toggle-indeterminate="false"
|
||||
:rules="validate('VnUser.active')"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</FormModelPopup>
|
||||
</template>
|
|
@ -0,0 +1,87 @@
|
|||
<script setup>
|
||||
import { ref } 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';
|
||||
|
||||
const { t } = useI18n();
|
||||
const props = defineProps({
|
||||
dataKey: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
exprBuilder: {
|
||||
type: Function,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
|
||||
const rolesOptions = ref([]);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FetchData
|
||||
url="VnRoles"
|
||||
:filter="{ fields: ['id', 'name'], order: 'name ASC' }"
|
||||
@on-fetch="(data) => (rolesOptions = data)"
|
||||
auto-load
|
||||
/>
|
||||
<VnFilterPanel
|
||||
:data-key="props.dataKey"
|
||||
:search-button="true"
|
||||
:hidden-tags="['search']"
|
||||
:redirect="false"
|
||||
>
|
||||
<template #tags="{ tag, formatFn }">
|
||||
<div class="q-gutter-x-xs">
|
||||
<strong>{{ t(`account.card.${tag.label}`) }}: </strong>
|
||||
<span>{{ formatFn(tag.value) }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #body="{ params, searchFn }">
|
||||
<QItem class="q-my-sm">
|
||||
<QItemSection>
|
||||
<VnInput
|
||||
:label="t('account.card.name')"
|
||||
v-model="params.name"
|
||||
lazy-rules
|
||||
is-outlined
|
||||
/>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
<QItem class="q-my-sm">
|
||||
<QItemSection>
|
||||
<VnInput
|
||||
:label="t('account.card.alias')"
|
||||
v-model="params.nickname"
|
||||
lazy-rules
|
||||
is-outlined
|
||||
/>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
<QItem class="q-mb-sm">
|
||||
<QItemSection>
|
||||
<VnSelect
|
||||
:label="t('account.card.role')"
|
||||
v-model="params.roleFk"
|
||||
@update:model-value="searchFn()"
|
||||
:options="rolesOptions"
|
||||
option-value="id"
|
||||
option-label="name"
|
||||
emit-value
|
||||
map-options
|
||||
use-input
|
||||
hide-selected
|
||||
dense
|
||||
outlined
|
||||
rounded
|
||||
:input-debounce="0"
|
||||
/>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
</template>
|
||||
</VnFilterPanel>
|
||||
</template>
|
|
@ -1 +1,144 @@
|
|||
<template>Account list</template>
|
||||
<script setup>
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import VnPaginate from 'src/components/ui/VnPaginate.vue';
|
||||
import VnSearchbar from 'components/ui/VnSearchbar.vue';
|
||||
import VnLv from 'src/components/ui/VnLv.vue';
|
||||
import CardList from 'src/components/ui/CardList.vue';
|
||||
import AccountSummary from './Card/AccountSummary.vue';
|
||||
import AccountFilter from './AccountFilter.vue';
|
||||
import AccountCreate from './AccountCreate.vue';
|
||||
|
||||
import { useSummaryDialog } from 'src/composables/useSummaryDialog';
|
||||
import { useStateStore } from 'stores/useStateStore';
|
||||
import { useRole } from 'src/composables/useRole';
|
||||
import { QDialog } from 'quasar';
|
||||
|
||||
const stateStore = useStateStore();
|
||||
const router = useRouter();
|
||||
const { t } = useI18n();
|
||||
const { viewSummary } = useSummaryDialog();
|
||||
const accountCreateDialogRef = ref(null);
|
||||
const showNewUserBtn = computed(() => useRole().hasAny(['itManagement']));
|
||||
|
||||
const filter = {
|
||||
fields: ['id', 'nickname', 'name', 'role'],
|
||||
include: { relation: 'role', scope: { fields: ['id', 'name'] } },
|
||||
};
|
||||
|
||||
const exprBuilder = (param, value) => {
|
||||
switch (param) {
|
||||
case 'search':
|
||||
return /^\d+$/.test(value)
|
||||
? { id: value }
|
||||
: {
|
||||
or: [
|
||||
{ name: { like: `%${value}%` } },
|
||||
{ nickname: { like: `%${value}%` } },
|
||||
],
|
||||
};
|
||||
case 'name':
|
||||
case 'nickname':
|
||||
return { [param]: { like: `%${value}%` } };
|
||||
case 'roleFk':
|
||||
return { [param]: value };
|
||||
}
|
||||
};
|
||||
|
||||
const getApiUrl = () => new URL(window.location).origin;
|
||||
|
||||
const navigate = (event, id) => {
|
||||
if (event.ctrlKey || event.metaKey)
|
||||
return window.open(`${getApiUrl()}/#/account/${id}/summary`);
|
||||
router.push({ path: `/account/${id}` });
|
||||
};
|
||||
|
||||
const openCreateModal = () => accountCreateDialogRef.value.show();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<template v-if="stateStore.isHeaderMounted()">
|
||||
<Teleport to="#searchbar">
|
||||
<VnSearchbar
|
||||
data-key="AccountList"
|
||||
url="VnUsers/preview"
|
||||
:expr-builder="exprBuilder"
|
||||
:label="t('account.search')"
|
||||
:info="t('account.searchInfo')"
|
||||
/>
|
||||
</Teleport>
|
||||
<Teleport to="#actions-append">
|
||||
<div class="row q-gutter-x-sm">
|
||||
<QBtn
|
||||
flat
|
||||
@click="stateStore.toggleRightDrawer()"
|
||||
round
|
||||
dense
|
||||
icon="menu"
|
||||
>
|
||||
<QTooltip bottom anchor="bottom right">
|
||||
{{ t('globals.collapseMenu') }}
|
||||
</QTooltip>
|
||||
</QBtn>
|
||||
</div>
|
||||
</Teleport>
|
||||
</template>
|
||||
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
|
||||
<QScrollArea class="fit text-grey-8">
|
||||
<AccountFilter data-key="AccountList" :expr-builder="exprBuilder" />
|
||||
</QScrollArea>
|
||||
</QDrawer>
|
||||
<QPage class="column items-center q-pa-md">
|
||||
<div class="vn-card-list">
|
||||
<VnPaginate
|
||||
:filter="filter"
|
||||
data-key="AccountList"
|
||||
url="VnUsers/preview"
|
||||
auto-load
|
||||
>
|
||||
<template #body="{ rows }">
|
||||
<CardList
|
||||
v-for="row of rows"
|
||||
:id="row.id"
|
||||
:key="row.id"
|
||||
:title="row.nickname"
|
||||
@click="navigate($event, row.id)"
|
||||
>
|
||||
<template #list-items>
|
||||
<VnLv :label="t('account.card.name')" :value="row.nickname">
|
||||
</VnLv>
|
||||
<VnLv
|
||||
:label="t('account.card.nickname')"
|
||||
:value="row.username"
|
||||
>
|
||||
</VnLv>
|
||||
</template>
|
||||
<template #actions>
|
||||
<QBtn
|
||||
:label="t('components.smartCard.openSummary')"
|
||||
@click.stop="viewSummary(row.id, AccountSummary)"
|
||||
color="primary"
|
||||
style="margin-top: 15px"
|
||||
/>
|
||||
</template>
|
||||
</CardList>
|
||||
</template>
|
||||
</VnPaginate>
|
||||
</div>
|
||||
<QDialog
|
||||
ref="accountCreateDialogRef"
|
||||
transition-hide="scale"
|
||||
transition-show="scale"
|
||||
>
|
||||
<AccountCreate />
|
||||
</QDialog>
|
||||
<QPageSticky :offset="[20, 20]" v-if="showNewUserBtn">
|
||||
<QBtn @click="openCreateModal" color="primary" fab icon="add" />
|
||||
<QTooltip class="text-no-wrap">
|
||||
{{ t('account.card.newUser') }}
|
||||
</QTooltip>
|
||||
</QPageSticky>
|
||||
</QPage>
|
||||
</template>
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
<script setup>
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import VnSelect from 'src/components/common/VnSelect.vue';
|
||||
import FormModel from 'components/FormModel.vue';
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
import { ref, watch } from 'vue';
|
||||
|
||||
const route = useRoute();
|
||||
const { t } = useI18n();
|
||||
const formModelRef = ref(null);
|
||||
|
||||
const accountFilter = {
|
||||
where: { id: route.params.id },
|
||||
fields: ['id', 'email', 'nickname', 'name', 'accountStateFk', 'packages', 'pickup'],
|
||||
include: [],
|
||||
};
|
||||
|
||||
watch(
|
||||
() => route.params.id,
|
||||
() => formModelRef.value.reset()
|
||||
);
|
||||
</script>
|
||||
<template>
|
||||
<FormModel
|
||||
ref="formModelRef"
|
||||
:url="`VnUsers/preview`"
|
||||
:url-update="`VnUsers/${route.params.id}/update-user`"
|
||||
:filter="accountFilter"
|
||||
model="Accounts"
|
||||
auto-load
|
||||
@on-data-saved="formModelRef.fetch()"
|
||||
>
|
||||
<template #form="{ data }">
|
||||
<div class="q-gutter-y-sm">
|
||||
<VnInput v-model="data.name" :label="t('account.card.nickname')" />
|
||||
<VnInput v-model="data.nickname" :label="t('account.card.alias')" />
|
||||
<VnInput v-model="data.email" :label="t('account.card.email')" />
|
||||
<VnSelect
|
||||
v-model="data.lang"
|
||||
:options="['es', 'en']"
|
||||
:label="t('account.card.lang')"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</FormModel>
|
||||
</template>
|
|
@ -0,0 +1,34 @@
|
|||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
import VnCard from 'components/common/VnCard.vue';
|
||||
import AccountDescriptor from './AccountDescriptor.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
const route = useRoute();
|
||||
|
||||
const routeName = computed(() => route.name);
|
||||
const customRouteRedirectName = computed(() => routeName.value);
|
||||
const searchBarDataKeys = {
|
||||
AccountSummary: 'AccountSummary',
|
||||
AccountInheritedRoles: 'AccountInheritedRoles',
|
||||
AccountMailForwarding: 'AccountMailForwarding',
|
||||
AccountMailAlias: 'AccountMailAlias',
|
||||
AccountPrivileges: 'AccountPrivileges',
|
||||
AccountLog: 'AccountLog',
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VnCard
|
||||
data-key="Account"
|
||||
:descriptor="AccountDescriptor"
|
||||
:search-data-key="searchBarDataKeys[routeName]"
|
||||
:search-custom-route-redirect="customRouteRedirectName"
|
||||
:search-redirect="!!customRouteRedirectName"
|
||||
:searchbar-label="t('account.search')"
|
||||
:searchbar-info="t('account.searchInfo')"
|
||||
/>
|
||||
</template>
|
|
@ -0,0 +1,130 @@
|
|||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import CardDescriptor from 'components/ui/CardDescriptor.vue';
|
||||
import VnLv from 'src/components/ui/VnLv.vue';
|
||||
import useCardDescription from 'src/composables/useCardDescription';
|
||||
import AccountDescriptorMenu from './AccountDescriptorMenu.vue';
|
||||
import FetchData from 'src/components/FetchData.vue';
|
||||
import VnImg from 'src/components/ui/VnImg.vue';
|
||||
|
||||
const $props = defineProps({
|
||||
id: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
|
||||
const route = useRoute();
|
||||
const { t } = useI18n();
|
||||
const entityId = computed(() => {
|
||||
return $props.id || route.params.id;
|
||||
});
|
||||
const data = ref(useCardDescription());
|
||||
const setData = (entity) => (data.value = useCardDescription(entity.nickname, entity.id));
|
||||
|
||||
const filter = {
|
||||
where: { id: entityId },
|
||||
fields: ['id', 'nickname', 'name', 'role'],
|
||||
include: { relation: 'role', scope: { fields: ['id', 'name'] } },
|
||||
};
|
||||
const hasAccount = ref(false);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FetchData
|
||||
:url="`Accounts/${entityId}/exists`"
|
||||
auto-load
|
||||
@on-fetch="(data) => (hasAccount = data.exists)"
|
||||
/>
|
||||
<CardDescriptor
|
||||
ref="descriptor"
|
||||
:url="`VnUsers/preview`"
|
||||
:filter="filter"
|
||||
module="Account"
|
||||
@on-fetch="setData"
|
||||
data-key="AccountId"
|
||||
:title="data.title"
|
||||
:subtitle="data.subtitle"
|
||||
>
|
||||
<template #header-extra-action>
|
||||
<QBtn
|
||||
round
|
||||
flat
|
||||
size="md"
|
||||
color="white"
|
||||
icon="face"
|
||||
:to="{ name: 'AccountList' }"
|
||||
>
|
||||
<QTooltip>
|
||||
{{ t('Go to module index') }}
|
||||
</QTooltip>
|
||||
</QBtn>
|
||||
</template>
|
||||
<template #menu>
|
||||
<AccountDescriptorMenu :has-account="hasAccount" />
|
||||
</template>
|
||||
<template #before>
|
||||
<!-- falla id :id="entityId.value" collection="user" size="160x160" -->
|
||||
<VnImg :id="entityId" collection="user" size="160x160" class="photo">
|
||||
<template #error>
|
||||
<div
|
||||
class="absolute-full picture text-center q-pa-md flex flex-center"
|
||||
>
|
||||
<div>
|
||||
<div class="text-grey-5" style="opacity: 0.4; font-size: 5vh">
|
||||
<QIcon name="vn:claims" />
|
||||
</div>
|
||||
<div class="text-grey-5" style="opacity: 0.4">
|
||||
{{ t('account.imageNotFound') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</VnImg>
|
||||
</template>
|
||||
<template #body="{ entity }">
|
||||
<VnLv :label="t('account.card.nickname')" :value="entity.nickname" />
|
||||
<VnLv :label="t('account.card.role')" :value="entity.role.name" />
|
||||
</template>
|
||||
<template #actions="{ entity }">
|
||||
<QCardActions class="q-gutter-x-md">
|
||||
<QIcon
|
||||
v-if="!entity.active"
|
||||
color="primary"
|
||||
name="vn:disabled"
|
||||
flat
|
||||
round
|
||||
size="sm"
|
||||
class="fill-icon"
|
||||
>
|
||||
<QTooltip>{{ t('account.card.deactivated') }}</QTooltip>
|
||||
</QIcon>
|
||||
<QIcon
|
||||
color="primary"
|
||||
name="contact_mail"
|
||||
v-if="entity.hasAccount"
|
||||
flat
|
||||
round
|
||||
size="sm"
|
||||
class="fill-icon"
|
||||
>
|
||||
<QTooltip>{{ t('account.card.enabled') }}</QTooltip>
|
||||
</QIcon>
|
||||
</QCardActions>
|
||||
</template>
|
||||
</CardDescriptor>
|
||||
</template>
|
||||
<style scoped>
|
||||
.q-item__label {
|
||||
margin-top: 0;
|
||||
}
|
||||
</style>
|
||||
<i18n>
|
||||
en:
|
||||
accountRate: Claming rate
|
||||
es:
|
||||
accountRate: Ratio de reclamación
|
||||
</i18n>
|
|
@ -0,0 +1,187 @@
|
|||
<script setup>
|
||||
import axios from 'axios';
|
||||
import { computed, ref, toRefs } from 'vue';
|
||||
import { useQuasar } from 'quasar';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useVnConfirm } from 'composables/useVnConfirm';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useArrayData } from 'src/composables/useArrayData';
|
||||
import CustomerChangePassword from 'src/pages/Customer/components/CustomerChangePassword.vue';
|
||||
import VnConfirm from 'src/components/ui/VnConfirm.vue';
|
||||
|
||||
const quasar = useQuasar();
|
||||
const $props = defineProps({
|
||||
hasAccount: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
const { t } = useI18n();
|
||||
const { hasAccount } = toRefs($props);
|
||||
const { openConfirmationModal } = useVnConfirm();
|
||||
const route = useRoute();
|
||||
|
||||
const account = computed(() => useArrayData('AccountId').store.data[0]);
|
||||
account.value.hasAccount = hasAccount.value;
|
||||
const entityId = computed(() => +route.params.id);
|
||||
|
||||
async function updateStatusAccount(active) {
|
||||
if (active) {
|
||||
await axios.post(`Accounts`, { id: entityId.value });
|
||||
} else {
|
||||
await axios.delete(`Accounts/${entityId.value}`);
|
||||
}
|
||||
|
||||
account.value.hasAccount = active;
|
||||
const status = active ? 'enable' : 'disable';
|
||||
quasar.notify({
|
||||
message: t(`account.card.${status}Account.success`),
|
||||
type: 'positive',
|
||||
});
|
||||
}
|
||||
async function updateStatusUser(active) {
|
||||
await axios.patch(`VnUsers/${entityId.value}`, { active });
|
||||
account.value.active = active;
|
||||
const status = active ? 'activate' : 'deactivate';
|
||||
quasar.notify({
|
||||
message: t(`account.card.actions.${status}User.success`),
|
||||
type: 'positive',
|
||||
});
|
||||
}
|
||||
function setPassword() {
|
||||
quasar.dialog({
|
||||
component: CustomerChangePassword,
|
||||
componentProps: {
|
||||
id: entityId.value,
|
||||
},
|
||||
});
|
||||
}
|
||||
const showSyncDialog = ref(false);
|
||||
const syncPassword = ref(null);
|
||||
const shouldSyncPassword = ref(false);
|
||||
async function sync() {
|
||||
const params = { force: true };
|
||||
if (shouldSyncPassword.value) params.password = syncPassword.value;
|
||||
await axios.patch(`Accounts/${account.value.name}/sync`, {
|
||||
params,
|
||||
});
|
||||
quasar.notify({
|
||||
message: t('account.card.actions.sync.success'),
|
||||
type: 'positive',
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<VnConfirm
|
||||
v-model="showSyncDialog"
|
||||
:message="t('account.card.actions.sync.message')"
|
||||
:title="t('account.card.actions.sync.title')"
|
||||
:promise="sync"
|
||||
>
|
||||
<template #customHTML>
|
||||
{{ shouldSyncPassword }}
|
||||
<QCheckbox
|
||||
:label="t('account.card.actions.sync.checkbox')"
|
||||
v-model="shouldSyncPassword"
|
||||
class="full-width"
|
||||
clearable
|
||||
clear-icon="close"
|
||||
>
|
||||
<QIcon style="padding-left: 10px" color="primary" name="info" size="sm">
|
||||
<QTooltip>{{ t('account.card.actions.sync.tooltip') }}</QTooltip>
|
||||
</QIcon></QCheckbox
|
||||
>
|
||||
<QInput
|
||||
v-if="shouldSyncPassword"
|
||||
:label="t('login.password')"
|
||||
v-model="syncPassword"
|
||||
class="full-width"
|
||||
clearable
|
||||
clear-icon="close"
|
||||
type="password"
|
||||
/>
|
||||
</template>
|
||||
</VnConfirm>
|
||||
<QItem v-ripple clickable @click="setPassword">
|
||||
<QItemSection>{{ t('account.card.actions.setPassword') }}</QItemSection>
|
||||
</QItem>
|
||||
<QItem
|
||||
v-if="!account.hasAccount"
|
||||
v-ripple
|
||||
clickable
|
||||
@click="
|
||||
openConfirmationModal(
|
||||
t('account.card.actions.enableAccount.title'),
|
||||
t('account.card.actions.enableAccount.subtitle'),
|
||||
() => updateStatusAccount(true)
|
||||
)
|
||||
"
|
||||
>
|
||||
<QItemSection>{{ t('account.card.actions.enableAccount.name') }}</QItemSection>
|
||||
</QItem>
|
||||
<QItem
|
||||
v-if="account.hasAccount"
|
||||
v-ripple
|
||||
clickable
|
||||
@click="
|
||||
openConfirmationModal(
|
||||
t('account.card.actions.disableAccount.title'),
|
||||
t('account.card.actions.disableAccount.subtitle'),
|
||||
() => updateStatusAccount(false)
|
||||
)
|
||||
"
|
||||
>
|
||||
<QItemSection>{{ t('account.card.actions.disableAccount.name') }}</QItemSection>
|
||||
</QItem>
|
||||
|
||||
<QItem
|
||||
v-if="!account.active"
|
||||
v-ripple
|
||||
clickable
|
||||
@click="
|
||||
openConfirmationModal(
|
||||
t('account.card.actions.activateUser.title'),
|
||||
t('account.card.actions.activateUser.title'),
|
||||
() => updateStatusUser(true)
|
||||
)
|
||||
"
|
||||
>
|
||||
<QItemSection>{{ t('account.card.actions.activateUser.name') }}</QItemSection>
|
||||
</QItem>
|
||||
<QItem
|
||||
v-if="account.active"
|
||||
v-ripple
|
||||
clickable
|
||||
@click="
|
||||
openConfirmationModal(
|
||||
t('account.card.actions.deactivateUser.title'),
|
||||
t('account.card.actions.deactivateUser.title'),
|
||||
() => updateStatusUser(false)
|
||||
)
|
||||
"
|
||||
>
|
||||
<QItemSection>{{ t('account.card.actions.deactivateUser.name') }}</QItemSection>
|
||||
</QItem>
|
||||
<QItem v-ripple clickable @click="showSyncDialog = true">
|
||||
<QItemSection>{{ t('account.card.actions.sync.name') }}</QItemSection>
|
||||
</QItem>
|
||||
|
||||
<QSeparator />
|
||||
<QItem
|
||||
@click="
|
||||
openConfirmationModal(
|
||||
t('account.card.actions.delete.title'),
|
||||
t('account.card.actions.delete.subTitle'),
|
||||
removeAccount
|
||||
)
|
||||
"
|
||||
v-ripple
|
||||
clickable
|
||||
>
|
||||
<QItemSection avatar>
|
||||
<QIcon name="delete" />
|
||||
</QItemSection>
|
||||
<QItemSection>{{ t('account.card.actions.delete.name') }}</QItemSection>
|
||||
</QItem>
|
||||
</template>
|
|
@ -0,0 +1,7 @@
|
|||
<script setup>
|
||||
import InheritedRoles from '../InheritedRoles.vue';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<InheritedRoles data-key="AccountInheritedRoles" />
|
||||
</template>
|
|
@ -0,0 +1,6 @@
|
|||
<script setup>
|
||||
import VnLog from 'src/components/common/VnLog.vue';
|
||||
</script>
|
||||
<template>
|
||||
<VnLog model="User" />
|
||||
</template>
|
|
@ -0,0 +1,187 @@
|
|||
<script setup>
|
||||
import { computed, ref, watch, onMounted, nextTick } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
import VnPaginate from 'components/ui/VnPaginate.vue';
|
||||
import AccountMailAliasCreateForm from './AccountMailAliasCreateForm.vue';
|
||||
|
||||
import { useVnConfirm } from 'composables/useVnConfirm';
|
||||
import { useArrayData } from 'composables/useArrayData';
|
||||
import useNotify from 'src/composables/useNotify.js';
|
||||
import axios from 'axios';
|
||||
|
||||
const { t } = useI18n();
|
||||
const route = useRoute();
|
||||
const { openConfirmationModal } = useVnConfirm();
|
||||
const { notify } = useNotify();
|
||||
|
||||
const paginateRef = ref(null);
|
||||
const createMailAliasDialogRef = ref(null);
|
||||
|
||||
const arrayData = useArrayData('AccountMailAliases');
|
||||
const store = arrayData.store;
|
||||
|
||||
const loading = ref(false);
|
||||
const hasAccount = ref(false);
|
||||
const data = computed(() => {
|
||||
const dataCopy = store.data;
|
||||
return dataCopy.sort((a, b) => a.alias?.alias.localeCompare(b.alias?.alias));
|
||||
});
|
||||
|
||||
const filter = computed(() => ({
|
||||
where: { account: route.params.id },
|
||||
include: {
|
||||
relation: 'alias',
|
||||
scope: {
|
||||
fields: ['id', 'alias', 'description'],
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
const urlPath = 'MailAliasAccounts';
|
||||
|
||||
const columns = computed(() => [
|
||||
{
|
||||
name: 'name',
|
||||
},
|
||||
{
|
||||
name: 'action',
|
||||
},
|
||||
]);
|
||||
|
||||
const fetchAccountExistence = async () => {
|
||||
try {
|
||||
const { data } = await axios.get(`Accounts/${route.params.id}/exists`);
|
||||
return data.exists;
|
||||
} catch (error) {
|
||||
console.error('Error fetching account existence', error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const deleteMailAlias = async (row) => {
|
||||
try {
|
||||
await axios.delete(`${urlPath}/${row.id}`);
|
||||
fetchMailAliases();
|
||||
notify(t('Unsubscribed from alias!'), 'positive');
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
const createMailAlias = async (mailAliasFormData) => {
|
||||
try {
|
||||
await axios.post(urlPath, mailAliasFormData);
|
||||
notify(t('Subscribed to alias!'), 'positive');
|
||||
fetchMailAliases();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
const fetchMailAliases = async () => {
|
||||
await nextTick();
|
||||
paginateRef.value.fetch();
|
||||
};
|
||||
|
||||
const getAccountData = async () => {
|
||||
loading.value = true;
|
||||
hasAccount.value = await fetchAccountExistence();
|
||||
if (!hasAccount.value) {
|
||||
loading.value = false;
|
||||
store.data = [];
|
||||
return;
|
||||
}
|
||||
await fetchMailAliases();
|
||||
loading.value = false;
|
||||
};
|
||||
|
||||
const openCreateMailAliasForm = () => createMailAliasDialogRef.value.show();
|
||||
|
||||
watch(
|
||||
() => route.params.id,
|
||||
() => {
|
||||
store.url = urlPath;
|
||||
store.filter = filter.value;
|
||||
getAccountData();
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(async () => await getAccountData());
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<QPage class="column items-center q-pa-md">
|
||||
<div class="full-width" style="max-width: 400px">
|
||||
<QSpinner v-if="loading" color="primary" size="md" />
|
||||
<VnPaginate
|
||||
ref="paginateRef"
|
||||
data-key="AccountMailAliases"
|
||||
:filter="filter"
|
||||
:url="urlPath"
|
||||
auto-load
|
||||
>
|
||||
<template #body="{ rows }">
|
||||
<QTable
|
||||
v-if="hasAccount && !loading"
|
||||
:rows="data"
|
||||
:columns="columns"
|
||||
hide-header
|
||||
>
|
||||
<template #body="{ row, rowIndex }">
|
||||
<QTr>
|
||||
<QTd>
|
||||
<div class="column">
|
||||
<span>{{ row.alias?.alias }}</span>
|
||||
<span class="color-vn-label">{{
|
||||
row.alias?.description
|
||||
}}</span>
|
||||
</div>
|
||||
</QTd>
|
||||
<QTd style="width: 50px !important">
|
||||
<QIcon
|
||||
name="delete"
|
||||
size="sm"
|
||||
class="cursor-pointer"
|
||||
color="primary"
|
||||
@click.stop.prevent="
|
||||
openConfirmationModal(
|
||||
t('User will be removed from alias'),
|
||||
t('¿Seguro que quieres continuar?'),
|
||||
() => deleteMailAlias(row, rows, rowIndex)
|
||||
)
|
||||
"
|
||||
>
|
||||
<QTooltip>
|
||||
{{ t('globals.delete') }}
|
||||
</QTooltip>
|
||||
</QIcon>
|
||||
</QTd>
|
||||
</QTr>
|
||||
</template>
|
||||
</QTable>
|
||||
</template>
|
||||
</VnPaginate>
|
||||
<h5 v-if="!hasAccount" class="text-center">
|
||||
{{ t('account.mailForwarding.accountNotEnabled') }}
|
||||
</h5>
|
||||
</div>
|
||||
<QDialog ref="createMailAliasDialogRef">
|
||||
<AccountMailAliasCreateForm @on-submit-create-alias="createMailAlias" />
|
||||
</QDialog>
|
||||
<QPageSticky position="bottom-right" :offset="[18, 18]">
|
||||
<QBtn fab icon="add" color="primary" @click="openCreateMailAliasForm()">
|
||||
<QTooltip>{{ t('warehouses.add') }}</QTooltip>
|
||||
</QBtn>
|
||||
</QPageSticky>
|
||||
</QPage>
|
||||
</template>
|
||||
|
||||
<i18n>
|
||||
es:
|
||||
Unsubscribed from alias!: ¡Desuscrito del alias!
|
||||
Subscribed to alias!: ¡Suscrito al alias!
|
||||
User will be removed from alias: El usuario será borrado del alias
|
||||
Are you sure you want to continue?: ¿Seguro que quieres continuar?
|
||||
</i18n>
|
|
@ -0,0 +1,51 @@
|
|||
<script setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
import VnSelect from 'src/components/common/VnSelect.vue';
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import VnRow from 'components/ui/VnRow.vue';
|
||||
import FormPopup from 'components/FormPopup.vue';
|
||||
|
||||
const emit = defineEmits(['onSubmitCreateAlias']);
|
||||
|
||||
const { t } = useI18n();
|
||||
const route = useRoute();
|
||||
|
||||
const aliasFormData = reactive({
|
||||
mailAlias: null,
|
||||
account: route.params.id,
|
||||
});
|
||||
|
||||
const aliasOptions = ref([]);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FetchData
|
||||
url="MailAliases"
|
||||
:filter="{ fields: ['id', 'alias'], order: 'alias ASC' }"
|
||||
auto-load
|
||||
@on-fetch="(data) => (aliasOptions = data)"
|
||||
/>
|
||||
<FormPopup
|
||||
model="ZoneWarehouse"
|
||||
@on-submit="emit('onSubmitCreateAlias', aliasFormData)"
|
||||
>
|
||||
<template #form-inputs>
|
||||
<VnRow class="row q-gutter-md q-mb-md">
|
||||
<div class="col">
|
||||
<VnSelect
|
||||
:label="t('account.card.alias')"
|
||||
v-model="aliasFormData.mailAlias"
|
||||
:options="aliasOptions"
|
||||
option-value="id"
|
||||
option-label="alias"
|
||||
hide-selected
|
||||
:required="true"
|
||||
/>
|
||||
</div>
|
||||
</VnRow>
|
||||
</template>
|
||||
</FormPopup>
|
||||
</template>
|
|
@ -0,0 +1,159 @@
|
|||
<script setup>
|
||||
import { ref, onMounted, watch, computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
import VnRow from 'components/ui/VnRow.vue';
|
||||
|
||||
import axios from 'axios';
|
||||
import { useStateStore } from 'stores/useStateStore';
|
||||
import useNotify from 'src/composables/useNotify.js';
|
||||
|
||||
const { t } = useI18n();
|
||||
const route = useRoute();
|
||||
const stateStore = useStateStore();
|
||||
const { notify } = useNotify();
|
||||
|
||||
const initialData = ref({});
|
||||
const formData = ref({
|
||||
forwardTo: null,
|
||||
account: null,
|
||||
});
|
||||
|
||||
const hasAccount = ref(false);
|
||||
const hasData = ref(false);
|
||||
const loading = ref(false);
|
||||
const hasDataChanged = computed(
|
||||
() =>
|
||||
formData.value.forwardTo !== initialData.value.forwardTo ||
|
||||
initialData.value.hasData !== hasData.value
|
||||
);
|
||||
|
||||
const fetchAccountExistence = async () => {
|
||||
try {
|
||||
const { data } = await axios.get(`Accounts/${route.params.id}/exists`);
|
||||
return data.exists;
|
||||
} catch (error) {
|
||||
console.error('Error fetching account existence', error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const fetchMailForwards = async () => {
|
||||
try {
|
||||
const response = await axios.get(`MailForwards/${route.params.id}`);
|
||||
return response.data;
|
||||
} catch (err) {
|
||||
console.error('Error fetching mail forwards', err);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const deleteMailForward = async () => {
|
||||
try {
|
||||
await axios.delete(`MailForwards/${route.params.id}`);
|
||||
formData.value.forwardTo = null;
|
||||
initialData.value.forwardTo = null;
|
||||
initialData.value.hasData = hasData.value;
|
||||
notify(t('globals.dataSaved'), 'positive');
|
||||
} catch (err) {
|
||||
console.error('Error deleting mail forward', err);
|
||||
}
|
||||
};
|
||||
|
||||
const updateMailForward = async () => {
|
||||
try {
|
||||
await axios.patch('MailForwards', formData.value);
|
||||
initialData.value = { ...formData.value };
|
||||
initialData.value.hasData = hasData.value;
|
||||
} catch (err) {
|
||||
console.error('Error creating mail forward', err);
|
||||
}
|
||||
};
|
||||
|
||||
const onSubmit = async () => {
|
||||
if (hasData.value) await updateMailForward();
|
||||
else await deleteMailForward();
|
||||
};
|
||||
|
||||
const setInitialData = async () => {
|
||||
loading.value = true;
|
||||
initialData.value.account = route.params.id;
|
||||
formData.value.account = route.params.id;
|
||||
hasAccount.value = await fetchAccountExistence(route.params.id);
|
||||
if (!hasAccount.value) {
|
||||
loading.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await fetchMailForwards(route.params.id);
|
||||
const forwardTo = result ? result.forwardTo : null;
|
||||
formData.value.forwardTo = forwardTo;
|
||||
initialData.value.forwardTo = forwardTo;
|
||||
|
||||
initialData.value.hasData = hasAccount.value && !!forwardTo;
|
||||
hasData.value = hasAccount.value && !!forwardTo;
|
||||
loading.value = false;
|
||||
};
|
||||
|
||||
watch(
|
||||
() => route.params.id,
|
||||
() => setInitialData()
|
||||
);
|
||||
|
||||
onMounted(async () => await setInitialData());
|
||||
</script>
|
||||
<template>
|
||||
<div class="flex justify-center">
|
||||
<QSpinner v-if="loading" color="primary" size="md" />
|
||||
<QForm
|
||||
v-else-if="hasAccount"
|
||||
@submit="onSubmit()"
|
||||
class="full-width"
|
||||
style="max-width: 800px"
|
||||
>
|
||||
<Teleport to="#st-actions" v-if="stateStore?.isSubToolbarShown()">
|
||||
<div>
|
||||
<QBtnGroup push class="q-gutter-x-sm">
|
||||
<slot name="moreActions" />
|
||||
<QBtn
|
||||
color="primary"
|
||||
icon="restart_alt"
|
||||
flat
|
||||
@click="reset()"
|
||||
:label="t('globals.reset')"
|
||||
/>
|
||||
<QBtn
|
||||
color="primary"
|
||||
icon="save"
|
||||
@click="onSubmit()"
|
||||
:disable="!hasDataChanged"
|
||||
:label="t('globals.save')"
|
||||
/>
|
||||
</QBtnGroup>
|
||||
</div>
|
||||
</Teleport>
|
||||
<QCard class="q-pa-lg">
|
||||
<VnRow class="row q-mb-md">
|
||||
<QCheckbox
|
||||
v-model="hasData"
|
||||
:label="t('account.mailForwarding.enableMailForwarding')"
|
||||
:toggle-indeterminate="false"
|
||||
/>
|
||||
</VnRow>
|
||||
<VnRow v-if="hasData" class="row q-gutter-md q-mb-md">
|
||||
<VnInput
|
||||
v-model="formData.forwardTo"
|
||||
:label="t('account.mailForwarding.forwardingMail')"
|
||||
:info="t('account.mailForwarding.mailInputInfo')"
|
||||
>
|
||||
</VnInput>
|
||||
</VnRow>
|
||||
</QCard>
|
||||
</QForm>
|
||||
<h5 v-else class="text-center">
|
||||
{{ t('account.mailForwarding.accountNotEnabled') }}
|
||||
</h5>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,49 @@
|
|||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import FormModel from 'components/FormModel.vue';
|
||||
import VnSelect from 'src/components/common/VnSelect.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
const route = useRoute();
|
||||
|
||||
const rolesOptions = ref([]);
|
||||
const formModelRef = ref();
|
||||
</script>
|
||||
<template>
|
||||
<FetchData
|
||||
url="VnRoles"
|
||||
:filter="{ fields: ['id', 'name'], order: 'name ASC' }"
|
||||
auto-load
|
||||
@on-fetch="(data) => (rolesOptions = data)"
|
||||
/>
|
||||
<FormModel
|
||||
ref="formModelRef"
|
||||
model="AccountPrivileges"
|
||||
:url="`VnUsers/${route.params.id}`"
|
||||
:url-create="`VnUsers/${route.params.id}/privileges`"
|
||||
auto-load
|
||||
@on-data-saved="formModelRef.fetch()"
|
||||
>
|
||||
<template #form="{ data }">
|
||||
<div class="q-gutter-y-sm">
|
||||
<QCheckbox
|
||||
v-model="data.hasGrant"
|
||||
:label="t('account.card.privileges.delegate')"
|
||||
/>
|
||||
<VnSelect
|
||||
:label="t('account.card.role')"
|
||||
v-model="data.roleFk"
|
||||
:options="rolesOptions"
|
||||
option-value="id"
|
||||
option-label="name"
|
||||
hide-selected
|
||||
:required="true"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</FormModel>
|
||||
</template>
|
|
@ -0,0 +1,102 @@
|
|||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import CardSummary from 'components/ui/CardSummary.vue';
|
||||
import VnLv from 'src/components/ui/VnLv.vue';
|
||||
|
||||
import { useArrayData } from 'src/composables/useArrayData';
|
||||
|
||||
const route = useRoute();
|
||||
const { t } = useI18n();
|
||||
|
||||
const $props = defineProps({
|
||||
id: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
});
|
||||
const { store } = useArrayData('Account');
|
||||
const account = ref(store.data);
|
||||
|
||||
const entityId = computed(() => $props.id || route.params.id);
|
||||
const filter = {
|
||||
where: { id: entityId },
|
||||
fields: ['id', 'nickname', 'name', 'role'],
|
||||
include: { relation: 'role', scope: { fields: ['id', 'name'] } },
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<CardSummary
|
||||
data-key="AccountSummary"
|
||||
ref="AccountSummary"
|
||||
url="VnUsers/preview"
|
||||
:filter="filter"
|
||||
@on-fetch="(data) => (account = data)"
|
||||
>
|
||||
<template #header>{{ account.id }} - {{ account.nickname }}</template>
|
||||
<template #body>
|
||||
<QCard class="vn-one">
|
||||
<QCardSection class="q-pa-none">
|
||||
<router-link
|
||||
:to="{ name: 'AccountBasicData', params: { id: entityId } }"
|
||||
class="header header-link"
|
||||
>
|
||||
{{ t('globals.pageTitles.basicData') }}
|
||||
<QIcon name="open_in_new" />
|
||||
</router-link>
|
||||
</QCardSection>
|
||||
<VnLv :label="t('account.card.nickname')" :value="account.nickname" />
|
||||
<VnLv :label="t('account.card.role')" :value="account.role.name" />
|
||||
</QCard>
|
||||
</template>
|
||||
</CardSummary>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.q-dialog__inner--minimized > div {
|
||||
max-width: 80%;
|
||||
}
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
gap: 15px;
|
||||
}
|
||||
.multimedia-container {
|
||||
flex: 1 0 21%;
|
||||
}
|
||||
.multimedia {
|
||||
transition: all 0.5s;
|
||||
opacity: 1;
|
||||
height: 250px;
|
||||
|
||||
.q-img {
|
||||
object-fit: cover;
|
||||
background-color: black;
|
||||
}
|
||||
video {
|
||||
object-fit: cover;
|
||||
background-color: black;
|
||||
}
|
||||
}
|
||||
|
||||
.multimedia:hover {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.close-button {
|
||||
top: 1%;
|
||||
right: 10%;
|
||||
}
|
||||
|
||||
.zindex {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.change-state {
|
||||
width: 10%;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,104 @@
|
|||
<script setup>
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
|
||||
import VnPaginate from 'components/ui/VnPaginate.vue';
|
||||
|
||||
import { useArrayData } from 'composables/useArrayData';
|
||||
|
||||
const props = defineProps({
|
||||
dataKey: { type: String, required: true },
|
||||
});
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const paginateRef = ref(null);
|
||||
|
||||
const arrayData = useArrayData(props.dataKey);
|
||||
const store = arrayData.store;
|
||||
|
||||
const data = computed(() => {
|
||||
const dataCopy = store.data;
|
||||
return dataCopy.sort((a, b) => a.role?.name.localeCompare(b.role?.name));
|
||||
});
|
||||
|
||||
const filter = computed(() => ({
|
||||
where: {
|
||||
prindicpalType: 'USER',
|
||||
principalId: route.params.id,
|
||||
},
|
||||
include: {
|
||||
relation: 'role',
|
||||
scope: {
|
||||
fields: ['id', 'name', 'description'],
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
const urlPath = 'RoleMappings';
|
||||
|
||||
const columns = computed(() => [
|
||||
{
|
||||
name: 'name',
|
||||
},
|
||||
]);
|
||||
|
||||
watch(
|
||||
() => route.params.id,
|
||||
() => {
|
||||
store.url = urlPath;
|
||||
store.filter = filter.value;
|
||||
store.limit = 0;
|
||||
fetchSubRoles();
|
||||
}
|
||||
);
|
||||
|
||||
const fetchSubRoles = () => paginateRef.value.fetch();
|
||||
|
||||
const redirectToRoleSummary = (id) =>
|
||||
router.push({ name: 'RoleSummary', params: { id } });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<QPage class="column items-center q-pa-md">
|
||||
<div class="full-width" style="max-width: 400px">
|
||||
<VnPaginate
|
||||
ref="paginateRef"
|
||||
:data-key="dataKey"
|
||||
:filter="filter"
|
||||
:url="urlPath"
|
||||
:limit="0"
|
||||
auto-load
|
||||
>
|
||||
<template #body>
|
||||
<QTable :rows="data" :columns="columns" hide-header>
|
||||
<template #body="{ row }">
|
||||
<QTr
|
||||
@click="redirectToRoleSummary(row.role?.id)"
|
||||
class="cursor-pointer"
|
||||
>
|
||||
<QTd>
|
||||
<div class="column">
|
||||
<span>{{ row.role?.name }}</span>
|
||||
<span class="color-vn-label">{{
|
||||
row.role?.description
|
||||
}}</span>
|
||||
</div>
|
||||
</QTd>
|
||||
</QTr>
|
||||
</template>
|
||||
</QTable>
|
||||
</template>
|
||||
</VnPaginate>
|
||||
</div>
|
||||
</QPage>
|
||||
</template>
|
||||
|
||||
<i18n>
|
||||
es:
|
||||
Role removed. Changes will take a while to fully propagate.: Rol eliminado. Los cambios tardaran un tiempo en propagarse completamente.
|
||||
Role added! Changes will take a while to fully propagate.: ¡Rol añadido! Los cambios tardaran un tiempo en propagarse completamente.
|
||||
El rol va a ser eliminado: Role will be removed
|
||||
¿Seguro que quieres continuar?: Are you sure you want to continue?
|
||||
</i18n>
|
|
@ -15,24 +15,75 @@ account:
|
|||
privileges: Privileges
|
||||
mailAlias: Mail Alias
|
||||
mailForwarding: Mail Forwarding
|
||||
accountCreate: New user
|
||||
aliasUsers: Users
|
||||
card:
|
||||
name: Name
|
||||
nickname: User
|
||||
role: Rol
|
||||
role: Role
|
||||
email: Email
|
||||
alias: Alias
|
||||
lang: Language
|
||||
roleFk: Role
|
||||
newUser: New user
|
||||
ticketTracking: Ticket tracking
|
||||
privileges:
|
||||
delegate: Can delegate privileges
|
||||
enabled: Account enabled!
|
||||
disabled: Account disabled!
|
||||
willActivated: User will activated
|
||||
willDeactivated: User will be deactivated
|
||||
activated: User activated!
|
||||
deactivated: User deactivated!
|
||||
actions:
|
||||
setPassword: Set password
|
||||
disableAccount:
|
||||
name: Disable account
|
||||
title: La cuenta será deshabilitada
|
||||
subtitle: ¿Seguro que quieres continuar?
|
||||
disableUser: Disable user
|
||||
sync: Sync
|
||||
delete: Delete
|
||||
title: The account will be disabled
|
||||
subtitle: Are you sure you want to continue?
|
||||
success: 'Account disabled!'
|
||||
enableAccount:
|
||||
name: Enable account
|
||||
title: The account will be enabled
|
||||
subtitle: Are you sure you want to continue?
|
||||
success: 'Account enabled!'
|
||||
deactivateUser:
|
||||
name: Deactivate user
|
||||
title: The user will be deactivated
|
||||
subtitle: Are you sure you want to continue?
|
||||
success: 'User deactivated!'
|
||||
activateUser:
|
||||
name: Activate user
|
||||
title: The user will be disabled
|
||||
subtitle: Are you sure you want to continue?
|
||||
success: 'User activated!'
|
||||
sync:
|
||||
name: Sync
|
||||
title: The account will be sync
|
||||
subtitle: Are you sure you want to continue?
|
||||
success: 'User synchronized!'
|
||||
checkbox: Synchronize password
|
||||
message: Do you want to synchronize user?
|
||||
tooltip: If password is not specified, just user attributes are synchronized
|
||||
delete:
|
||||
name: Delete
|
||||
title: The account will be deleted
|
||||
subtitle: Are you sure you want to continue?
|
||||
success: ''
|
||||
search: Search user
|
||||
searchInfo: You can search by id, name or nickname
|
||||
create:
|
||||
name: Name
|
||||
nickname: Nickname
|
||||
email: Email
|
||||
role: Role
|
||||
password: Password
|
||||
active: Active
|
||||
mailForwarding:
|
||||
forwardingMail: Forward email
|
||||
accountNotEnabled: Account not enabled
|
||||
enableMailForwarding: Enable mail forwarding
|
||||
mailInputInfo: All emails will be forwarded to the specified address.
|
||||
role:
|
||||
pageTitles:
|
||||
inheritedRoles: Inherited Roles
|
||||
|
|
|
@ -15,6 +15,7 @@ account:
|
|||
privileges: Privilegios
|
||||
mailAlias: Alias de correo
|
||||
mailForwarding: Reenvío de correo
|
||||
accountCreate: Nuevo usuario
|
||||
aliasUsers: Usuarios
|
||||
card:
|
||||
nickname: Usuario
|
||||
|
@ -22,27 +23,66 @@ account:
|
|||
role: Rol
|
||||
email: Mail
|
||||
alias: Alias
|
||||
lang: dioma
|
||||
lang: Idioma
|
||||
roleFk: Rol
|
||||
enabled: ¡Cuenta habilitada!
|
||||
disabled: ¡Cuenta deshabilitada!
|
||||
willActivated: El usuario será activado
|
||||
willDeactivated: El usuario será desactivado
|
||||
activated: ¡Usuario activado!
|
||||
deactivated: ¡Usuario desactivado!
|
||||
newUser: Nuevo usuario
|
||||
privileges:
|
||||
delegate: Puede delegar privilegios
|
||||
actions:
|
||||
setPassword: Establecer contraseña
|
||||
disableAccount:
|
||||
name: Deshabilitar cuenta
|
||||
title: La cuenta será deshabilitada
|
||||
subtitle: ¿Seguro que quieres continuar?
|
||||
disableUser:
|
||||
success: '¡Cuenta deshabilitada!'
|
||||
enableAccount:
|
||||
name: Habilitar cuenta
|
||||
title: La cuenta será habilitada
|
||||
subtitle: ¿Seguro que quieres continuar?
|
||||
success: '¡Cuenta habilitada!'
|
||||
deactivateUser:
|
||||
name: Desactivar usuario
|
||||
title: El usuario será deshabilitado
|
||||
subtitle: ¿Seguro que quieres continuar?
|
||||
success: '¡Usuario desactivado!'
|
||||
activateUser:
|
||||
name: Activar usuario
|
||||
title: El usuario será activado
|
||||
subtitle: ¿Seguro que quieres continuar?
|
||||
success: '¡Usuario activado!'
|
||||
sync:
|
||||
name: Sincronizar
|
||||
title: El usuario será sincronizado
|
||||
subtitle: ¿Seguro que quieres continuar?
|
||||
success: '¡Usuario sincronizado!'
|
||||
checkbox: Sincronizar contraseña
|
||||
message: ¿Quieres sincronizar el usuario?
|
||||
tooltip: Si la contraseña no se especifica solo se sincronizarán lo atributos del usuario
|
||||
delete:
|
||||
name: Eliminar
|
||||
title: El usuario será eliminado
|
||||
subtitle: ¿Seguro que quieres continuar?
|
||||
|
||||
success: ''
|
||||
search: Buscar usuario
|
||||
searchInfo: Puedes buscar por id, nombre o usuario
|
||||
create:
|
||||
name: Nombre
|
||||
nickname: Nombre mostrado
|
||||
email: Email
|
||||
role: Rol
|
||||
password: Contraseña
|
||||
active: Activo
|
||||
mailForwarding:
|
||||
forwardingMail: Dirección de reenvío
|
||||
accountNotEnabled: Cuenta no habilitada
|
||||
enableMailForwarding: Habilitar redirección de correo
|
||||
mailInputInfo: Todos los correos serán reenviados a la dirección especificada, no se mantendrá copia de los mismos en el buzón del usuario.
|
||||
role:
|
||||
pageTitles:
|
||||
inheritedRoles: Roles heredados
|
||||
|
|
|
@ -33,7 +33,6 @@ function exprBuilder(param, value) {
|
|||
url="Agencies"
|
||||
order="name"
|
||||
:expr-builder="exprBuilder"
|
||||
auto-load
|
||||
>
|
||||
<template #body="{ rows }">
|
||||
<CardList
|
||||
|
|
|
@ -14,7 +14,7 @@ const entityId = computed(() => $props.id || useRoute().params.id);
|
|||
|
||||
<template>
|
||||
<div class="q-pa-md">
|
||||
<CardSummary :url="`Agencies/${entityId}`">
|
||||
<CardSummary :url="`Agencies/${entityId}`" data-key="Agency">
|
||||
<template #header="{ entity: agency }">{{ agency.name }}</template>
|
||||
<template #body="{ entity: agency }">
|
||||
<QCard class="vn-one">
|
||||
|
|
|
@ -10,32 +10,13 @@ import VnInput from 'src/components/common/VnInput.vue';
|
|||
import VnInputDate from 'components/common/VnInputDate.vue';
|
||||
|
||||
import axios from 'axios';
|
||||
import { useSession } from 'src/composables/useSession';
|
||||
// import { useSession } from 'src/composables/useSession';
|
||||
import VnImg from 'src/components/ui/VnImg.vue';
|
||||
|
||||
const route = useRoute();
|
||||
const { t } = useI18n();
|
||||
const { getTokenMultimedia } = useSession();
|
||||
const token = getTokenMultimedia();
|
||||
|
||||
const claimFilter = {
|
||||
fields: [
|
||||
'id',
|
||||
'clientFk',
|
||||
'created',
|
||||
'workerFk',
|
||||
'claimStateFk',
|
||||
'packages',
|
||||
'pickup',
|
||||
],
|
||||
include: [
|
||||
{
|
||||
relation: 'client',
|
||||
scope: {
|
||||
fields: ['name'],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
// const { getTokenMultimedia } = useSession();
|
||||
// const token = getTokenMultimedia();
|
||||
|
||||
const claimStates = ref([]);
|
||||
const claimStatesCopy = ref([]);
|
||||
|
@ -87,11 +68,10 @@ const statesFilter = {
|
|||
/>
|
||||
<FetchData url="ClaimStates" @on-fetch="setClaimStates" auto-load />
|
||||
<FormModel
|
||||
:url="`Claims/${route.params.id}`"
|
||||
model="Claim"
|
||||
:url-update="`Claims/updateClaim/${route.params.id}`"
|
||||
:filter="claimFilter"
|
||||
model="claim"
|
||||
auto-load
|
||||
:reload="true"
|
||||
>
|
||||
<template #form="{ data, validate, filter }">
|
||||
<VnRow class="row q-gutter-md q-mb-md">
|
||||
|
@ -118,9 +98,11 @@ const statesFilter = {
|
|||
>
|
||||
<template #before>
|
||||
<QAvatar color="orange">
|
||||
<QImg
|
||||
<VnImg
|
||||
v-if="data.workerFk"
|
||||
:src="`/api/Images/user/160x160/${data.workerFk}/download?access_token=${token}`"
|
||||
:size="'160x160'"
|
||||
:id="data.workerFk"
|
||||
collection="user"
|
||||
spinner-color="white"
|
||||
/>
|
||||
</QAvatar>
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
import VnCard from 'components/common/VnCard.vue';
|
||||
import ClaimDescriptor from './ClaimDescriptor.vue';
|
||||
import ClaimFilter from '../ClaimFilter.vue';
|
||||
import filter from './ClaimFilter.js';
|
||||
</script>
|
||||
<template>
|
||||
<VnCard
|
||||
|
@ -13,5 +14,6 @@ import ClaimFilter from '../ClaimFilter.vue';
|
|||
search-url="Claims/filter"
|
||||
searchbar-label="Search claim"
|
||||
searchbar-info="You can search by claim id or customer name"
|
||||
:filter="filter"
|
||||
/>
|
||||
</template>
|
||||
|
|
|
@ -12,6 +12,7 @@ import useCardDescription from 'src/composables/useCardDescription';
|
|||
import VnUserLink from 'src/components/ui/VnUserLink.vue';
|
||||
import { getUrl } from 'src/composables/getUrl';
|
||||
import ZoneDescriptorProxy from 'src/pages/Zone/Card/ZoneDescriptorProxy.vue';
|
||||
import filter from './ClaimFilter.js';
|
||||
|
||||
const $props = defineProps({
|
||||
id: {
|
||||
|
@ -29,49 +30,6 @@ const entityId = computed(() => {
|
|||
return $props.id || route.params.id;
|
||||
});
|
||||
|
||||
const filter = {
|
||||
include: [
|
||||
{
|
||||
relation: 'client',
|
||||
scope: {
|
||||
include: [
|
||||
{ relation: 'salesPersonUser' },
|
||||
{
|
||||
relation: 'claimsRatio',
|
||||
scope: {
|
||||
fields: ['claimingRate'],
|
||||
limit: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
relation: 'claimState',
|
||||
},
|
||||
{
|
||||
relation: 'ticket',
|
||||
scope: {
|
||||
include: [
|
||||
{ relation: 'zone' },
|
||||
{
|
||||
relation: 'address',
|
||||
scope: {
|
||||
include: { relation: 'province' },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
relation: 'worker',
|
||||
scope: {
|
||||
include: { relation: 'user' },
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const STATE_COLOR = {
|
||||
pending: 'warning',
|
||||
incomplete: 'info',
|
||||
|
@ -101,7 +59,7 @@ onMounted(async () => {
|
|||
:title="data.title"
|
||||
:subtitle="data.subtitle"
|
||||
@on-fetch="setData"
|
||||
data-key="claimData"
|
||||
data-key="Claim"
|
||||
>
|
||||
<template #menu="{ entity }">
|
||||
<ClaimDescriptorMenu :claim="entity" />
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
export default {
|
||||
fields: [
|
||||
'id',
|
||||
'clientFk',
|
||||
'created',
|
||||
'workerFk',
|
||||
'claimStateFk',
|
||||
'packages',
|
||||
'pickup',
|
||||
'ticketFk',
|
||||
],
|
||||
include: [
|
||||
{
|
||||
relation: 'client',
|
||||
scope: {
|
||||
include: [
|
||||
{ relation: 'salesPersonUser' },
|
||||
{
|
||||
relation: 'claimsRatio',
|
||||
scope: {
|
||||
fields: ['claimingRate'],
|
||||
limit: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
relation: 'claimState',
|
||||
},
|
||||
{
|
||||
relation: 'ticket',
|
||||
scope: {
|
||||
include: [
|
||||
{ relation: 'zone' },
|
||||
{
|
||||
relation: 'address',
|
||||
scope: {
|
||||
include: { relation: 'province' },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
relation: 'worker',
|
||||
scope: {
|
||||
include: { relation: 'user' },
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
|
@ -185,6 +185,7 @@ async function changeState(value) {
|
|||
:url="`Claims/${entityId}/getSummary`"
|
||||
:entity-id="entityId"
|
||||
@on-fetch="getClaimDms"
|
||||
data-key="claimSummary"
|
||||
>
|
||||
<template #header="{ entity: { claim } }">
|
||||
{{ claim.id }} - {{ claim.client.name }} ({{ claim.client.id }})
|
||||
|
|
|
@ -3,17 +3,15 @@ import { ref } from 'vue';
|
|||
import { useRoute } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import { useSession } from 'src/composables/useSession';
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import FormModel from 'components/FormModel.vue';
|
||||
import VnRow from 'components/ui/VnRow.vue';
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
import VnImg from 'src/components/ui/VnImg.vue';
|
||||
import VnSelect from 'src/components/common/VnSelect.vue';
|
||||
|
||||
const route = useRoute();
|
||||
const { t } = useI18n();
|
||||
const { getTokenMultimedia } = useSession();
|
||||
const token = getTokenMultimedia();
|
||||
|
||||
const workers = ref([]);
|
||||
const businessTypes = ref([]);
|
||||
|
@ -115,10 +113,11 @@ const contactChannels = ref([]);
|
|||
>
|
||||
<template #prepend>
|
||||
<QAvatar color="orange">
|
||||
<QImg
|
||||
:src="`/api/Images/user/160x160/${data.salesPersonFk}/download?access_token=${token}`"
|
||||
spinner-color="white"
|
||||
<VnImg
|
||||
v-if="data.salesPersonFk"
|
||||
:id="user.id"
|
||||
collection="user"
|
||||
spinner-color="white"
|
||||
/>
|
||||
</QAvatar>
|
||||
</template>
|
||||
|
|
|
@ -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"
|
||||
/>
|
||||
|
|
|
@ -143,10 +143,6 @@ function handleLocation(data, location) {
|
|||
</VnRow>
|
||||
|
||||
<VnRow>
|
||||
<QCheckbox
|
||||
:label="t('Incoterms authorization')"
|
||||
v-model="data.hasIncoterms"
|
||||
/>
|
||||
<QCheckbox
|
||||
:label="t('Electronic invoice')"
|
||||
v-model="data.hasElectronicInvoice"
|
||||
|
|
|
@ -61,7 +61,11 @@ const creditWarning = computed(() => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<CardSummary ref="summary" :url="`Clients/${entityId}/summary`">
|
||||
<CardSummary
|
||||
ref="summary"
|
||||
:url="`Clients/${entityId}/summary`"
|
||||
data-key="CustomerSummary"
|
||||
>
|
||||
<template #body="{ entity }">
|
||||
<QCard class="vn-one">
|
||||
<VnTitle
|
||||
|
@ -306,10 +310,8 @@ const creditWarning = computed(() => {
|
|||
:value="entity.recommendedCredit"
|
||||
/>
|
||||
</QCard>
|
||||
<QCard>
|
||||
<div class="header">
|
||||
{{ t('Latest tickets') }}
|
||||
</div>
|
||||
<QCard class="vn-one">
|
||||
<VnTitle :text="t('Latest tickets')" />
|
||||
<CustomerSummaryTable />
|
||||
</QCard>
|
||||
</template>
|
||||
|
|
|
@ -28,7 +28,6 @@ const isLoading = ref(false);
|
|||
const name = ref(null);
|
||||
const usersPreviewRef = ref(null);
|
||||
const user = ref([]);
|
||||
const userPasswords = ref(0);
|
||||
|
||||
const dataChanges = computed(() => {
|
||||
return (
|
||||
|
@ -45,7 +44,6 @@ const showChangePasswordDialog = () => {
|
|||
component: CustomerChangePassword,
|
||||
componentProps: {
|
||||
id: route.params.id,
|
||||
userPasswords: userPasswords.value,
|
||||
promise: usersPreviewRef.value.fetch(),
|
||||
},
|
||||
});
|
||||
|
@ -97,11 +95,6 @@ const onSubmit = async () => {
|
|||
@on-fetch="(data) => (canChangePassword = data)"
|
||||
auto-load
|
||||
/>
|
||||
<FetchData
|
||||
@on-fetch="(data) => (userPasswords = data[0])"
|
||||
auto-load
|
||||
url="UserPasswords"
|
||||
/>
|
||||
|
||||
<Teleport to="#st-actions" v-if="stateStore?.isSubToolbarShown()">
|
||||
<QBtnGroup push class="q-gutter-x-sm">
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -22,7 +22,7 @@ const balanceDueTotal = ref(0);
|
|||
const selected = ref([]);
|
||||
|
||||
const tableColumnComponents = {
|
||||
client: {
|
||||
clientFk: {
|
||||
component: QBtn,
|
||||
props: () => ({ flat: true, class: 'link', noCaps: true }),
|
||||
event: () => {},
|
||||
|
@ -40,7 +40,7 @@ const tableColumnComponents = {
|
|||
props: () => ({ flat: true, class: 'link', noCaps: true }),
|
||||
event: () => {},
|
||||
},
|
||||
department: {
|
||||
departmentName: {
|
||||
component: 'span',
|
||||
props: () => {},
|
||||
event: () => {},
|
||||
|
@ -102,12 +102,12 @@ const columns = computed(() => [
|
|||
align: 'left',
|
||||
field: 'clientName',
|
||||
label: t('Client'),
|
||||
name: 'client',
|
||||
name: 'clientFk',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
align: 'left',
|
||||
field: 'isWorker',
|
||||
field: ({ isWorker }) => Boolean(isWorker),
|
||||
label: t('Is worker'),
|
||||
name: 'isWorker',
|
||||
},
|
||||
|
@ -122,7 +122,7 @@ const columns = computed(() => [
|
|||
align: 'left',
|
||||
field: 'departmentName',
|
||||
label: t('Department'),
|
||||
name: 'department',
|
||||
name: 'departmentName',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
|
@ -204,48 +204,24 @@ const viewAddObservation = (rowsSelected) => {
|
|||
});
|
||||
};
|
||||
|
||||
const departments = ref(new Map());
|
||||
|
||||
const onFetch = async (data) => {
|
||||
const salesPersonFks = data.map((item) => item.salesPersonFk);
|
||||
const departmentNames = salesPersonFks.map(async (salesPersonFk) => {
|
||||
try {
|
||||
const { data: workerDepartment } = await axios.get(
|
||||
`WorkerDepartments/${salesPersonFk}`
|
||||
);
|
||||
const { data: department } = await axios.get(
|
||||
`Departments/${workerDepartment.departmentFk}`
|
||||
);
|
||||
departments.value.set(salesPersonFk, department.name);
|
||||
} catch (error) {
|
||||
console.error('Err: ', error);
|
||||
}
|
||||
});
|
||||
const recoveryData = await axios.get('Recoveries');
|
||||
|
||||
const recoveries = recoveryData.data.map(({ clientFk, finished }) => ({
|
||||
clientFk,
|
||||
finished,
|
||||
}));
|
||||
|
||||
await Promise.all(departmentNames);
|
||||
|
||||
data.forEach((item) => {
|
||||
item.departmentName = departments.value.get(item.salesPersonFk);
|
||||
item.isWorker = item.businessTypeFk === 'worker';
|
||||
const recovery = recoveries.find(({ clientFk }) => clientFk === item.clientFk);
|
||||
item.finished = recovery?.finished === null;
|
||||
});
|
||||
|
||||
for (const element of data) element.isWorker = element.businessTypeFk === 'worker';
|
||||
|
||||
balanceDueTotal.value = data.reduce((acc, { amount = 0 }) => acc + amount, 0);
|
||||
};
|
||||
|
||||
function exprBuilder(param, value) {
|
||||
switch (param) {
|
||||
case 'clientFk':
|
||||
return { [`d.${param}`]: value?.id };
|
||||
return { [`d.${param}`]: value };
|
||||
case 'creditInsurance':
|
||||
case 'amount':
|
||||
case 'workerFk':
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
|
@ -16,11 +15,26 @@ const props = defineProps({
|
|||
},
|
||||
});
|
||||
|
||||
const salespersons = ref();
|
||||
const countries = ref();
|
||||
const authors = ref();
|
||||
const departments = ref();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FetchData
|
||||
:filter="{ where: { role: 'salesPerson' } }"
|
||||
@on-fetch="(data) => (salespersons = data)"
|
||||
auto-load
|
||||
url="Workers/activeWithInheritedRole"
|
||||
/>
|
||||
<FetchData @on-fetch="(data) => (countries = data)" auto-load url="Countries" />
|
||||
<FetchData
|
||||
@on-fetch="(data) => (authors = data)"
|
||||
auto-load
|
||||
url="Workers/activeWithInheritedRole"
|
||||
/>
|
||||
<FetchData @on-fetch="(data) => (departments = data)" auto-load url="Departments" />
|
||||
|
||||
<VnFilterPanel :data-key="props.dataKey" :search-button="true">
|
||||
<template #tags="{ tag, formatFn }">
|
||||
|
@ -50,7 +64,6 @@ const countries = ref();
|
|||
/>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
|
||||
<QItem class="q-mb-sm">
|
||||
<QItemSection>
|
||||
<VnSelect
|
||||
|
@ -73,6 +86,52 @@ const countries = ref();
|
|||
/>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
<QItem class="q-mb-sm">
|
||||
<QItemSection v-if="departments">
|
||||
<VnSelect
|
||||
:input-debounce="0"
|
||||
:label="t('Departments')"
|
||||
:options="departments"
|
||||
dense
|
||||
emit-value
|
||||
hide-selected
|
||||
map-options
|
||||
option-label="name"
|
||||
option-value="id"
|
||||
outlined
|
||||
rounded
|
||||
use-input
|
||||
v-model="params.departmentFk"
|
||||
@update:model-value="searchFn()"
|
||||
/>
|
||||
</QItemSection>
|
||||
<QItemSection v-else>
|
||||
<QSkeleton class="full-width" type="QInput" />
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
<QItem class="q-mb-sm">
|
||||
<QItemSection v-if="departments">
|
||||
<VnSelect
|
||||
:input-debounce="0"
|
||||
:label="t('Departments')"
|
||||
:options="departments"
|
||||
dense
|
||||
emit-value
|
||||
hide-selected
|
||||
map-options
|
||||
option-label="name"
|
||||
option-value="id"
|
||||
outlined
|
||||
rounded
|
||||
use-input
|
||||
v-model="params.departmentFk"
|
||||
@update:model-value="searchFn()"
|
||||
/>
|
||||
</QItemSection>
|
||||
<QItemSection v-else>
|
||||
<QSkeleton class="full-width" type="QInput" />
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
|
||||
<QItem class="q-mb-sm">
|
||||
<QItemSection v-if="countries">
|
||||
|
|
|
@ -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>
|
|
@ -9,6 +9,7 @@ import useNotify from 'src/composables/useNotify';
|
|||
|
||||
import VnRow from 'components/ui/VnRow.vue';
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
import FetchData from 'src/components/FetchData.vue';
|
||||
|
||||
const { dialogRef } = useDialogPluginComponent();
|
||||
const { notify } = useNotify();
|
||||
|
@ -19,15 +20,12 @@ const $props = defineProps({
|
|||
type: String,
|
||||
required: true,
|
||||
},
|
||||
userPasswords: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
promise: {
|
||||
type: Function,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
const userPasswords = ref({});
|
||||
|
||||
const closeButton = ref(null);
|
||||
const isLoading = ref(false);
|
||||
|
@ -60,6 +58,11 @@ const onSubmit = async () => {
|
|||
|
||||
<template>
|
||||
<QDialog ref="dialogRef">
|
||||
<FetchData
|
||||
@on-fetch="(data) => (userPasswords = data[0])"
|
||||
auto-load
|
||||
url="UserPasswords"
|
||||
/>
|
||||
<QCard class="q-pa-lg">
|
||||
<QCardSection>
|
||||
<QForm @submit.prevent="onSubmit">
|
||||
|
@ -71,7 +74,7 @@ const onSubmit = async () => {
|
|||
<QIcon name="close" size="sm" />
|
||||
</span>
|
||||
|
||||
<VnRow class="row q-gutter-md q-mb-md">
|
||||
<VnRow class="row q-gutter-md q-mb-md" style="flex-direction: column">
|
||||
<div class="col">
|
||||
<VnInput
|
||||
:label="t('New password')"
|
||||
|
@ -84,11 +87,7 @@ const onSubmit = async () => {
|
|||
<QTooltip>
|
||||
{{
|
||||
t('customer.card.passwordRequirements', {
|
||||
length: $props.userPasswords.length,
|
||||
nAlpha: $props.userPasswords.nAlpha,
|
||||
nDigits: $props.userPasswords.nDigits,
|
||||
nPunct: $props.userPasswords.nPunct,
|
||||
nUpper: $props.userPasswords.nUpper,
|
||||
...userPasswords,
|
||||
})
|
||||
}}
|
||||
</QTooltip>
|
||||
|
|
|
@ -162,6 +162,7 @@ const navigateToticketSummary = (id) => {
|
|||
params: { id },
|
||||
});
|
||||
};
|
||||
const commonColumns = (col) => ['date', 'state', 'total'].includes(col);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -171,67 +172,68 @@ const navigateToticketSummary = (id) => {
|
|||
auto-load
|
||||
url="Tickets"
|
||||
/>
|
||||
<QTable
|
||||
:columns="columns"
|
||||
:pagination="{ rowsPerPage: 12 }"
|
||||
:rows="rows"
|
||||
class="full-width q-mt-md"
|
||||
row-key="id"
|
||||
v-if="rows?.length"
|
||||
>
|
||||
<template #body-cell="props">
|
||||
<QTd :props="props" @click="navigateToticketSummary(props.row.id)">
|
||||
<QTr :props="props" class="cursor-pointer">
|
||||
<component
|
||||
:is="tableColumnComponents[props.col.name].component"
|
||||
@click="tableColumnComponents[props.col.name].event(props)"
|
||||
class="rounded-borders q-pa-sm"
|
||||
v-bind="tableColumnComponents[props.col.name].props(props)"
|
||||
>
|
||||
<template
|
||||
v-if="
|
||||
props.col.name === 'id' ||
|
||||
props.col.name === 'nickname' ||
|
||||
props.col.name === 'agency' ||
|
||||
props.col.name === 'route' ||
|
||||
props.col.name === 'packages'
|
||||
"
|
||||
<QCard class="vn-one q-py-sm flex justify-between">
|
||||
<QTable
|
||||
:columns="columns"
|
||||
:pagination="{ rowsPerPage: 12 }"
|
||||
:rows="rows"
|
||||
class="full-width"
|
||||
row-key="id"
|
||||
>
|
||||
<template #body-cell="props">
|
||||
<QTd :props="props" @click="navigateToticketSummary(props.row.id)">
|
||||
<QTr :props="props" class="cursor-pointer">
|
||||
<component
|
||||
:is="tableColumnComponents[props.col.name].component"
|
||||
@click="tableColumnComponents[props.col.name].event(props)"
|
||||
class="rounded-borders"
|
||||
v-bind="tableColumnComponents[props.col.name].props(props)"
|
||||
>
|
||||
{{ props.value }}
|
||||
</template>
|
||||
<template v-if="props.col.name === 'date'">
|
||||
<QBadge class="q-pa-sm" color="warning">
|
||||
{{ props.value }}
|
||||
</QBadge>
|
||||
</template>
|
||||
<template v-if="props.col.name === 'state'">
|
||||
<QBadge :color="setStateColor(props.row)" class="q-pa-sm">
|
||||
{{ props.value }}
|
||||
</QBadge>
|
||||
</template>
|
||||
<template v-if="props.col.name === 'total'">
|
||||
<QBadge
|
||||
:color="setTotalPriceColor(props.row)"
|
||||
class="q-pa-sm"
|
||||
v-if="setTotalPriceColor(props.row)"
|
||||
>
|
||||
{{ toCurrency(props.value) }}
|
||||
</QBadge>
|
||||
<div v-else>{{ toCurrency(props.value) }}</div>
|
||||
</template>
|
||||
<CustomerDescriptorProxy
|
||||
:id="props.row.clientFk"
|
||||
v-if="props.col.name === 'nickname'"
|
||||
/>
|
||||
<RouteDescriptorProxy
|
||||
:id="props.row.routeFk"
|
||||
v-if="props.col.name === 'route'"
|
||||
/>
|
||||
</component>
|
||||
</QTr>
|
||||
</QTd>
|
||||
</template>
|
||||
</QTable>
|
||||
<template v-if="!commonColumns(props.col.name)">
|
||||
<span
|
||||
:class="{
|
||||
link:
|
||||
props.col.name === 'route' ||
|
||||
props.col.name === 'nickname',
|
||||
}"
|
||||
>
|
||||
{{ props.value }}
|
||||
</span>
|
||||
</template>
|
||||
<template v-if="props.col.name === 'date'">
|
||||
<QBadge class="q-pa-sm" color="warning">
|
||||
{{ props.value }}
|
||||
</QBadge>
|
||||
</template>
|
||||
<template v-if="props.col.name === 'state'">
|
||||
<QBadge :color="setStateColor(props.row)" class="q-pa-sm">
|
||||
{{ props.value }}
|
||||
</QBadge>
|
||||
</template>
|
||||
<template v-if="props.col.name === 'total'">
|
||||
<QBadge
|
||||
:color="setTotalPriceColor(props.row)"
|
||||
class="q-pa-sm"
|
||||
v-if="setTotalPriceColor(props.row)"
|
||||
>
|
||||
{{ toCurrency(props.value) }}
|
||||
</QBadge>
|
||||
<div v-else>{{ toCurrency(props.value) }}</div>
|
||||
</template>
|
||||
<CustomerDescriptorProxy
|
||||
:id="props.row.clientFk"
|
||||
v-if="props.col.name === 'nickname'"
|
||||
/>
|
||||
<RouteDescriptorProxy
|
||||
:id="props.row.routeFk"
|
||||
v-if="props.col.name === 'route'"
|
||||
/>
|
||||
</component>
|
||||
</QTr>
|
||||
</QTd>
|
||||
</template>
|
||||
</QTable>
|
||||
</QCard>
|
||||
</template>
|
||||
|
||||
<i18n>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script setup>
|
||||
import DepartmentDescriptor from './DepartmentDescriptor.vue';
|
||||
import DepartmentSummaryDialog from './DepartmentSummaryDialog.vue';
|
||||
import DepartmentSummary from './DepartmentSummary.vue';
|
||||
|
||||
const $props = defineProps({
|
||||
id: {
|
||||
|
@ -15,7 +15,7 @@ const $props = defineProps({
|
|||
<DepartmentDescriptor
|
||||
v-if="$props.id"
|
||||
:id="$props.id"
|
||||
:summary="DepartmentSummaryDialog"
|
||||
:summary="DepartmentSummary"
|
||||
/>
|
||||
</QPopupProxy>
|
||||
</template>
|
||||
|
|
|
@ -32,6 +32,7 @@ onMounted(async () => {
|
|||
:url="`Departments/${entityId}`"
|
||||
class="full-width"
|
||||
style="max-width: 900px"
|
||||
module-name="Department"
|
||||
>
|
||||
<template #header="{ entity }">
|
||||
<div>{{ entity.name }}</div>
|
||||
|
|
|
@ -11,9 +11,9 @@ import VnInput from 'src/components/common/VnInput.vue';
|
|||
import FetchedTags from 'components/ui/FetchedTags.vue';
|
||||
import VnConfirm from 'components/ui/VnConfirm.vue';
|
||||
import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue';
|
||||
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
|
||||
|
||||
import { useQuasar } from 'quasar';
|
||||
import { useStateStore } from 'stores/useStateStore';
|
||||
import { toCurrency } from 'src/filters';
|
||||
import axios from 'axios';
|
||||
import useNotify from 'src/composables/useNotify.js';
|
||||
|
@ -22,7 +22,6 @@ const quasar = useQuasar();
|
|||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const { t } = useI18n();
|
||||
const stateStore = useStateStore();
|
||||
const { notify } = useNotify();
|
||||
|
||||
const rowsSelected = ref([]);
|
||||
|
@ -312,20 +311,22 @@ const lockIconType = (groupingMode, mode) => {
|
|||
auto-load
|
||||
@on-fetch="(data) => (packagingsOptions = data)"
|
||||
/>
|
||||
<Teleport to="#st-actions" v-if="stateStore?.isSubToolbarShown()">
|
||||
<QBtnGroup push style="column-gap: 10px">
|
||||
<slot name="moreBeforeActions" />
|
||||
<QBtn
|
||||
:label="t('globals.remove')"
|
||||
color="primary"
|
||||
icon="delete"
|
||||
flat
|
||||
@click="openRemoveDialog()"
|
||||
:disable="!rowsSelected?.length"
|
||||
:title="t('globals.remove')"
|
||||
/>
|
||||
</QBtnGroup>
|
||||
</Teleport>
|
||||
<VnSubToolbar>
|
||||
<template #st-actions>
|
||||
<QBtnGroup push style="column-gap: 10px">
|
||||
<slot name="moreBeforeActions" />
|
||||
<QBtn
|
||||
:label="t('globals.remove')"
|
||||
color="primary"
|
||||
icon="delete"
|
||||
flat
|
||||
@click="openRemoveDialog()"
|
||||
:disable="!rowsSelected?.length"
|
||||
:title="t('globals.remove')"
|
||||
/>
|
||||
</QBtnGroup>
|
||||
</template>
|
||||
</VnSubToolbar>
|
||||
<VnPaginate
|
||||
ref="entryBuysPaginateRef"
|
||||
data-key="EntryBuys"
|
||||
|
|
|
@ -161,6 +161,7 @@ const fetchEntryBuys = async () => {
|
|||
ref="summaryRef"
|
||||
:url="`Entries/${entityId}/getEntry`"
|
||||
@on-fetch="(data) => setEntryData(data)"
|
||||
data-key="EntrySummary"
|
||||
>
|
||||
<template #header-left>
|
||||
<router-link
|
||||
|
|
|
@ -16,14 +16,15 @@ import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
|
|||
|
||||
import { useStateStore } from 'stores/useStateStore';
|
||||
import { toDate, toCurrency } from 'src/filters';
|
||||
import { useSession } from 'composables/useSession';
|
||||
// import { useSession } from 'composables/useSession';
|
||||
import { dashIfEmpty } from 'src/filters';
|
||||
import { useArrayData } from 'composables/useArrayData';
|
||||
import RightMenu from 'src/components/common/RightMenu.vue';
|
||||
import VnImg from 'src/components/ui/VnImg.vue';
|
||||
|
||||
const router = useRouter();
|
||||
const { getTokenMultimedia } = useSession();
|
||||
const token = getTokenMultimedia();
|
||||
// const { getTokenMultimedia } = useSession();
|
||||
// const token = getTokenMultimedia();
|
||||
const stateStore = useStateStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
|
@ -695,14 +696,7 @@ onUnmounted(() => (stateStore.rightDrawer = false));
|
|||
</template>
|
||||
<template #body-cell-picture="{ row }">
|
||||
<QTd>
|
||||
<QImg
|
||||
:src="`/api/Images/catalog/50x50/${row.itemFk}/download?access_token=${token}`"
|
||||
spinner-color="primary"
|
||||
:ratio="1"
|
||||
height="50px"
|
||||
width="50px"
|
||||
class="image"
|
||||
/>
|
||||
<VnImg :id="row.itemFk" size="50x50" class="image" />
|
||||
</QTd>
|
||||
</template>
|
||||
<template #body-cell-itemFk="{ row }">
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -106,6 +106,7 @@ const ticketsColumns = ref([
|
|||
ref="summary"
|
||||
:url="`InvoiceOuts/${entityId}/summary`"
|
||||
:entity-id="entityId"
|
||||
data-key="InvoiceOutSummary"
|
||||
>
|
||||
<template #header="{ entity: { invoiceOut } }">
|
||||
<div>{{ invoiceOut.ref }} - {{ invoiceOut.client?.socialName }}</div>
|
||||
|
|
|
@ -13,7 +13,6 @@ import ItemDescriptorImage from 'src/pages/Item/Card/ItemDescriptorImage.vue';
|
|||
|
||||
import { useState } from 'src/composables/useState';
|
||||
import useCardDescription from 'src/composables/useCardDescription';
|
||||
import { useSession } from 'src/composables/useSession';
|
||||
import { getUrl } from 'src/composables/getUrl';
|
||||
import axios from 'axios';
|
||||
import { dashIfEmpty } from 'src/filters';
|
||||
|
@ -42,14 +41,12 @@ const quasar = useQuasar();
|
|||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const { t } = useI18n();
|
||||
const { getTokenMultimedia } = useSession();
|
||||
const state = useState();
|
||||
const user = state.getUser();
|
||||
|
||||
const entityId = computed(() => {
|
||||
return $props.id || route.params.id;
|
||||
});
|
||||
const image = ref(null);
|
||||
const regularizeStockFormDialog = ref(null);
|
||||
const item = ref(null);
|
||||
const available = ref(null);
|
||||
|
@ -67,17 +64,10 @@ const warehouseFk = computed({
|
|||
});
|
||||
|
||||
onMounted(async () => {
|
||||
await getItemAvatar();
|
||||
warehouseFk.value = user.value.warehouseFk;
|
||||
salixUrl.value = await getUrl('');
|
||||
});
|
||||
|
||||
const getItemAvatar = async () => {
|
||||
const token = getTokenMultimedia();
|
||||
const timeStamp = `timestamp=${Date.now()}`;
|
||||
image.value = `/api/Images/catalog/200x200/${entityId.value}/download?access_token=${token}&${timeStamp}`;
|
||||
};
|
||||
|
||||
const data = ref(useCardDescription());
|
||||
const setData = (entity) => {
|
||||
if (!entity) return;
|
||||
|
|
|
@ -3,8 +3,7 @@ import { ref, onMounted } from 'vue';
|
|||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import EditPictureForm from 'components/EditPictureForm.vue';
|
||||
|
||||
import { useSession } from 'src/composables/useSession';
|
||||
import VnImg from 'src/components/ui/VnImg.vue';
|
||||
import axios from 'axios';
|
||||
|
||||
const $props = defineProps({
|
||||
|
@ -27,19 +26,12 @@ const $props = defineProps({
|
|||
});
|
||||
|
||||
const { t } = useI18n();
|
||||
const { getTokenMultimedia } = useSession();
|
||||
|
||||
const image = ref(null);
|
||||
const editPhotoFormDialog = ref(null);
|
||||
const showEditPhotoForm = ref(false);
|
||||
const warehouseName = ref(null);
|
||||
|
||||
const getItemAvatar = async () => {
|
||||
const token = getTokenMultimedia();
|
||||
const timeStamp = `timestamp=${Date.now()}`;
|
||||
image.value = `/api/Images/catalog/200x200/${$props.entityId}/download?access_token=${token}&${timeStamp}`;
|
||||
};
|
||||
|
||||
const toggleEditPictureForm = () => {
|
||||
showEditPhotoForm.value = !showEditPhotoForm.value;
|
||||
};
|
||||
|
@ -62,14 +54,17 @@ const getWarehouseName = async (warehouseFk) => {
|
|||
};
|
||||
|
||||
onMounted(async () => {
|
||||
getItemAvatar();
|
||||
getItemConfigs();
|
||||
});
|
||||
|
||||
const handlePhotoUpdated = (evt = false) => {
|
||||
image.value.reload(evt);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative-position">
|
||||
<QImg :src="image" spinner-color="primary" style="min-height: 256px">
|
||||
<VnImg ref="image" :id="$props.entityId" @refresh="handlePhotoUpdated(true)">
|
||||
<template #error>
|
||||
<div class="absolute-full picture text-center q-pa-md flex flex-center">
|
||||
<div>
|
||||
|
@ -82,7 +77,7 @@ onMounted(async () => {
|
|||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</QImg>
|
||||
</VnImg>
|
||||
<QBtn
|
||||
v-if="showEditButton"
|
||||
color="primary"
|
||||
|
@ -97,7 +92,7 @@ onMounted(async () => {
|
|||
collection="catalog"
|
||||
:id="entityId"
|
||||
@close-form="toggleEditPictureForm()"
|
||||
@on-photo-uploaded="getItemAvatar()"
|
||||
@on-photo-uploaded="handlePhotoUpdated"
|
||||
/>
|
||||
</QDialog>
|
||||
</QBtn>
|
||||
|
|
|
@ -33,7 +33,6 @@ const user = state.getUser();
|
|||
const fixedPrices = ref([]);
|
||||
const fixedPricesOriginalData = ref([]);
|
||||
const warehousesOptions = ref([]);
|
||||
const itemsWithNameOptions = ref([]);
|
||||
const rowsSelected = ref([]);
|
||||
|
||||
const exprBuilder = (param, value) => {
|
||||
|
@ -371,12 +370,6 @@ onUnmounted(() => (stateStore.rightDrawer = false));
|
|||
auto-load
|
||||
@on-fetch="(data) => onWarehousesFetched(data)"
|
||||
/>
|
||||
<FetchData
|
||||
url="Items/withName"
|
||||
:filter="{ fields: ['id', 'name'], order: 'id DESC' }"
|
||||
auto-load
|
||||
@on-fetch="(data) => (itemsWithNameOptions = data)"
|
||||
/>
|
||||
<RightMenu>
|
||||
<template #right-panel>
|
||||
<ItemFixedPriceFilter
|
||||
|
@ -419,7 +412,7 @@ onUnmounted(() => (stateStore.rightDrawer = false));
|
|||
<template #body-cell-itemId="props">
|
||||
<QTd>
|
||||
<VnSelect
|
||||
:options="itemsWithNameOptions"
|
||||
url="Items/withName"
|
||||
hide-selected
|
||||
option-label="id"
|
||||
option-value="id"
|
||||
|
@ -562,7 +555,7 @@ onUnmounted(() => (stateStore.rightDrawer = false));
|
|||
</QTd>
|
||||
</template>
|
||||
</QTable>
|
||||
<QPageSticky v-if="rowsSelected.length > 0" :offset="[20, 20]">
|
||||
<QPageSticky v-if="rowsSelected.length" :offset="[20, 20]">
|
||||
<QBtn @click="openEditTableCellDialog()" color="primary" fab icon="edit" />
|
||||
<QTooltip>
|
||||
{{ t('Edit fixed price(s)') }}
|
||||
|
|
|
@ -16,17 +16,15 @@ import ItemListFilter from './ItemListFilter.vue';
|
|||
|
||||
import { useStateStore } from 'stores/useStateStore';
|
||||
import { toDateFormat } from 'src/filters/date.js';
|
||||
import { useSession } from 'composables/useSession';
|
||||
import { dashIfEmpty } from 'src/filters';
|
||||
import { useSummaryDialog } from 'src/composables/useSummaryDialog';
|
||||
import { useVnConfirm } from 'composables/useVnConfirm';
|
||||
import axios from 'axios';
|
||||
import RightMenu from 'src/components/common/RightMenu.vue';
|
||||
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
|
||||
import VnImg from 'src/components/ui/VnImg.vue';
|
||||
|
||||
const router = useRouter();
|
||||
const { getTokenMultimedia } = useSession();
|
||||
const token = getTokenMultimedia();
|
||||
const stateStore = useStateStore();
|
||||
const { t } = useI18n();
|
||||
const { viewSummary } = useSummaryDialog();
|
||||
|
@ -491,10 +489,9 @@ onUnmounted(() => (stateStore.rightDrawer = false));
|
|||
</template>
|
||||
<template #body-cell-picture="{ row }">
|
||||
<QTd>
|
||||
<QImg
|
||||
:src="`/api/Images/catalog/50x50/${row.id}/download?access_token=${token}`"
|
||||
spinner-color="primary"
|
||||
:ratio="1"
|
||||
<VnImg
|
||||
size="50x50"
|
||||
:id="row.id"
|
||||
height="50px"
|
||||
width="50px"
|
||||
class="image"
|
||||
|
|
|
@ -3,14 +3,15 @@ import { computed, ref } from 'vue';
|
|||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute } from 'vue-router';
|
||||
import axios from 'axios';
|
||||
|
||||
import VnInput from 'components/common/VnInput.vue';
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
|
||||
import VnSelect from 'components/common/VnSelect.vue';
|
||||
import VnFilterPanelChip from 'components/ui/VnFilterPanelChip.vue';
|
||||
import { useValidator } from 'src/composables/useValidator';
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const route = useRoute();
|
||||
const props = defineProps({
|
||||
dataKey: {
|
||||
|
@ -21,32 +22,34 @@ const props = defineProps({
|
|||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
tagValue: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const categoryList = ref(null);
|
||||
const selectedCategoryFk = ref(null);
|
||||
const typeList = ref(null);
|
||||
const selectedTypeFk = ref(null);
|
||||
|
||||
const validationsStore = useValidator();
|
||||
const selectedOrder = ref(null);
|
||||
const selectedOrderField = ref(null);
|
||||
const moreFields = ref([]);
|
||||
const moreFieldsOrder = ref([]);
|
||||
const createValue = (val, done) => {
|
||||
if (val.length > 2) {
|
||||
if (!tagOptions.value.includes(val)) {
|
||||
done(tagOptions.value, 'add-unique');
|
||||
}
|
||||
tagValues.value.push({ value: val });
|
||||
}
|
||||
};
|
||||
const resetCategory = () => {
|
||||
selectedCategoryFk.value = null;
|
||||
typeList.value = null;
|
||||
};
|
||||
|
||||
const selectedOrder = ref(null);
|
||||
const orderList = [
|
||||
{ way: 'ASC', name: 'Ascendant' },
|
||||
{ way: 'DESC', name: 'Descendant' },
|
||||
];
|
||||
|
||||
const selectedOrderField = ref(null);
|
||||
const OrderFields = [
|
||||
{ field: 'relevancy DESC, name', name: 'Relevancy', priority: 999 },
|
||||
{ field: 'showOrder, price', name: 'Color and price', priority: 999 },
|
||||
{ field: 'name', name: 'Name', priority: 999 },
|
||||
{ field: 'price', name: 'Price', priority: 999 },
|
||||
];
|
||||
|
||||
const clearFilter = (key) => {
|
||||
if (key === 'categoryFk') {
|
||||
resetCategory();
|
||||
|
@ -72,21 +75,6 @@ const loadTypes = async (categoryFk) => {
|
|||
typeList.value = data;
|
||||
};
|
||||
|
||||
const onFilterInit = async ({ params }) => {
|
||||
if (params.typeFk) {
|
||||
selectedTypeFk.value = params.typeFk;
|
||||
}
|
||||
if (params.categoryFk) {
|
||||
await loadTypes(params.categoryFk);
|
||||
selectedCategoryFk.value = params.categoryFk;
|
||||
}
|
||||
if (params.orderBy) {
|
||||
orderByParam.value = JSON.parse(params.orderBy);
|
||||
selectedOrder.value = orderByParam.value?.way;
|
||||
selectedOrderField.value = orderByParam.value?.field;
|
||||
}
|
||||
};
|
||||
|
||||
const selectedCategory = computed(() =>
|
||||
(categoryList.value || []).find(
|
||||
(category) => category?.id === selectedCategoryFk.value
|
||||
|
@ -109,10 +97,7 @@ function exprBuilder(param, value) {
|
|||
|
||||
const selectedTag = ref(null);
|
||||
const tagValues = ref([{}]);
|
||||
const tagOptions = ref(null);
|
||||
const isButtonDisabled = computed(
|
||||
() => !selectedTag.value || tagValues.value.some((item) => !item.value)
|
||||
);
|
||||
const tagOptions = ref([]);
|
||||
|
||||
const applyTagFilter = (params, search) => {
|
||||
if (!tagValues.value?.length) {
|
||||
|
@ -125,12 +110,12 @@ const applyTagFilter = (params, search) => {
|
|||
}
|
||||
params.tagGroups.push(
|
||||
JSON.stringify({
|
||||
values: tagValues.value,
|
||||
values: tagValues.value.filter((obj) => Object.keys(obj).length > 0),
|
||||
tagSelection: {
|
||||
...selectedTag.value,
|
||||
orgShowField: selectedTag.value.name,
|
||||
orgShowField: selectedTag?.value?.name,
|
||||
},
|
||||
tagFk: selectedTag.value.tagFk,
|
||||
tagFk: selectedTag?.value?.tagFk,
|
||||
})
|
||||
);
|
||||
search();
|
||||
|
@ -147,20 +132,52 @@ const removeTagChip = (selection, params, search) => {
|
|||
search();
|
||||
};
|
||||
|
||||
const orderByParam = ref(null);
|
||||
|
||||
const onOrderFieldChange = (value, params, search) => {
|
||||
const orderBy = Object.assign({}, orderByParam.value, { field: value.field });
|
||||
params.orderBy = JSON.stringify(orderBy);
|
||||
search();
|
||||
const onOrderChange = (value, params) => {
|
||||
const tagObj = JSON.parse(params.orderBy);
|
||||
tagObj.way = value.name;
|
||||
params.orderBy = JSON.stringify(tagObj);
|
||||
};
|
||||
|
||||
const onOrderChange = (value, params, search) => {
|
||||
const orderBy = Object.assign({}, orderByParam.value, { way: value.way });
|
||||
params.orderBy = JSON.stringify(orderBy);
|
||||
search();
|
||||
const onOrderFieldChange = (value, params) => {
|
||||
const tagObj = JSON.parse(params.orderBy); // esto donde va
|
||||
const fields = {
|
||||
Relevancy: (value) => value + ' DESC, name',
|
||||
ColorAndPrice: 'showOrder, price',
|
||||
Name: 'name',
|
||||
Price: 'price',
|
||||
};
|
||||
let tagField = fields[value];
|
||||
if (!tagField) return;
|
||||
|
||||
if (typeof tagField === 'function') tagField = tagField(value);
|
||||
tagObj.field = tagField;
|
||||
params.orderBy = JSON.stringify(tagObj);
|
||||
switch (value) {
|
||||
case 'Relevancy':
|
||||
tagObj.field = value + ' DESC, name';
|
||||
params.orderBy = JSON.stringify(tagObj);
|
||||
console.log('params: ', params);
|
||||
break;
|
||||
case 'ColorAndPrice':
|
||||
tagObj.field = 'showOrder, price';
|
||||
params.orderBy = JSON.stringify(tagObj);
|
||||
console.log('params: ', params);
|
||||
break;
|
||||
case 'Name':
|
||||
tagObj.field = 'name';
|
||||
params.orderBy = JSON.stringify(tagObj);
|
||||
console.log('params: ', params);
|
||||
break;
|
||||
case 'Price':
|
||||
tagObj.field = 'price';
|
||||
params.orderBy = JSON.stringify(tagObj);
|
||||
console.log('params: ', params);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
const _moreFields = ['ASC', 'DESC'];
|
||||
const _moreFieldsTypes = ['Relevancy', 'ColorAndPrice', 'Name', 'Price'];
|
||||
const setCategoryList = (data) => {
|
||||
categoryList.value = (data || [])
|
||||
.filter((category) => category.display)
|
||||
|
@ -168,6 +185,8 @@ const setCategoryList = (data) => {
|
|||
...category,
|
||||
icon: `vn:${(category.icon || '').split('-')[1]}`,
|
||||
}));
|
||||
moreFields.value = useLang(_moreFields);
|
||||
moreFieldsOrder.value = useLang(_moreFieldsTypes);
|
||||
};
|
||||
|
||||
const getCategoryClass = (category, params) => {
|
||||
|
@ -175,6 +194,20 @@ const getCategoryClass = (category, params) => {
|
|||
return 'active';
|
||||
}
|
||||
};
|
||||
|
||||
const useLang = (values) => {
|
||||
const { models } = validationsStore;
|
||||
const properties = models.Item?.properties || {};
|
||||
return values.map((name) => {
|
||||
let prop = properties[name];
|
||||
const label = t(`params.${name}`);
|
||||
return {
|
||||
name,
|
||||
label,
|
||||
type: prop ? prop.type : null,
|
||||
};
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -182,9 +215,9 @@ const getCategoryClass = (category, params) => {
|
|||
<VnFilterPanel
|
||||
:data-key="props.dataKey"
|
||||
:hidden-tags="['orderFk', 'orderBy']"
|
||||
:unremovable-params="['orderFk', 'orderBy']"
|
||||
:expr-builder="exprBuilder"
|
||||
:custom-tags="['tagGroups']"
|
||||
@init="onFilterInit"
|
||||
@remove="clearFilter"
|
||||
>
|
||||
<template #tags="{ tag, formatFn }">
|
||||
|
@ -274,40 +307,29 @@ const getCategoryClass = (category, params) => {
|
|||
<QItem class="q-my-md">
|
||||
<QItemSection>
|
||||
<VnSelect
|
||||
:label="t('params.order')"
|
||||
:label="t('Order')"
|
||||
v-model="selectedOrder"
|
||||
:options="orderList || []"
|
||||
option-value="way"
|
||||
option-label="name"
|
||||
:options="moreFields"
|
||||
option-label="label"
|
||||
dense
|
||||
outlined
|
||||
rounded
|
||||
:emit-value="false"
|
||||
use-input
|
||||
:is-clearable="false"
|
||||
@update:model-value="
|
||||
(value) => onOrderChange(value, params, searchFn)
|
||||
"
|
||||
@update:model-value="(value) => onOrderChange(value, params)"
|
||||
/>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
<QItem class="q-mb-md">
|
||||
<QItemSection>
|
||||
<VnSelect
|
||||
:label="t('params.order')"
|
||||
:label="t('Order by')"
|
||||
v-model="selectedOrderField"
|
||||
:options="OrderFields || []"
|
||||
option-value="field"
|
||||
option-label="name"
|
||||
:options="moreFieldsOrder"
|
||||
option-label="label"
|
||||
option-value="name"
|
||||
dense
|
||||
outlined
|
||||
rounded
|
||||
:emit-value="false"
|
||||
use-input
|
||||
:is-clearable="false"
|
||||
@update:model-value="
|
||||
(value) => onOrderFieldChange(value, params, searchFn)
|
||||
"
|
||||
@update:model-value="(value) => onOrderFieldChange(value, params)"
|
||||
/>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
|
@ -333,15 +355,30 @@ const getCategoryClass = (category, params) => {
|
|||
:key="value"
|
||||
class="q-mt-md filter-value"
|
||||
>
|
||||
<VnInput
|
||||
v-if="selectedTag?.isFree"
|
||||
v-model="value.value"
|
||||
:label="t('params.value')"
|
||||
is-outlined
|
||||
class="filter-input"
|
||||
<FetchData
|
||||
v-if="selectedTag"
|
||||
:url="`Tags/${selectedTag}/filterValue`"
|
||||
limit="30"
|
||||
auto-load
|
||||
@on-fetch="(data) => (tagOptions = data)"
|
||||
/>
|
||||
<VnSelect
|
||||
v-else
|
||||
v-if="!selectedTag"
|
||||
:label="t('params.value')"
|
||||
v-model="value.value"
|
||||
:options="tagValue || []"
|
||||
option-value="value"
|
||||
option-label="value"
|
||||
dense
|
||||
outlined
|
||||
rounded
|
||||
emit-value
|
||||
use-input
|
||||
class="filter-input"
|
||||
@new-value="createValue"
|
||||
/>
|
||||
<VnSelect
|
||||
v-else-if="selectedTag === 1"
|
||||
:label="t('params.value')"
|
||||
v-model="value.value"
|
||||
:options="tagOptions || []"
|
||||
|
@ -352,18 +389,18 @@ const getCategoryClass = (category, params) => {
|
|||
rounded
|
||||
emit-value
|
||||
use-input
|
||||
:disable="!selectedTag"
|
||||
class="filter-input"
|
||||
@new-value="createValue"
|
||||
/>
|
||||
<VnInput
|
||||
v-else
|
||||
:label="t('params.value')"
|
||||
v-model="value.value"
|
||||
dense
|
||||
outlined
|
||||
rounded
|
||||
class="filter-input"
|
||||
/>
|
||||
|
||||
<FetchData
|
||||
v-if="selectedTag && !selectedTag.isFree"
|
||||
:url="`Tags/${selectedTag?.id}/filterValue`"
|
||||
limit="30"
|
||||
auto-load
|
||||
@on-fetch="(data) => (tagOptions = data)"
|
||||
/>
|
||||
|
||||
<QIcon
|
||||
name="delete"
|
||||
class="filter-icon"
|
||||
|
@ -388,7 +425,6 @@ const getCategoryClass = (category, params) => {
|
|||
rounded
|
||||
type="button"
|
||||
unelevated
|
||||
:disable="isButtonDisabled"
|
||||
@click.stop="applyTagFilter(params, searchFn)"
|
||||
/>
|
||||
</QItemSection>
|
||||
|
@ -453,6 +489,12 @@ en:
|
|||
tag: Tag
|
||||
value: Value
|
||||
order: Order
|
||||
ASC: Ascendant
|
||||
DESC: Descendant
|
||||
Relevancy: Relevancy
|
||||
ColorAndPrice: Color and price
|
||||
Name: Name
|
||||
Price: Price
|
||||
es:
|
||||
params:
|
||||
type: Tipo
|
||||
|
@ -460,6 +502,14 @@ es:
|
|||
tag: Etiqueta
|
||||
value: Valor
|
||||
order: Orden
|
||||
ASC: Ascendiente
|
||||
DESC: Descendiente
|
||||
Relevancy: Relevancia
|
||||
ColorAndPrice: Color y precio
|
||||
Name: Nombre
|
||||
Price: Precio
|
||||
Order: Orden
|
||||
Order by: Ordenar por
|
||||
Plant: Planta
|
||||
Flower: Flor
|
||||
Handmade: Confección
|
||||
|
|
|
@ -3,16 +3,14 @@ import { ref } from 'vue';
|
|||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import VnLv from 'components/ui/VnLv.vue';
|
||||
import VnImg from 'src/components/ui/VnImg.vue';
|
||||
import OrderCatalogItemDialog from 'pages/Order/Card/OrderCatalogItemDialog.vue';
|
||||
import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue';
|
||||
|
||||
import { useSession } from 'composables/useSession';
|
||||
import toCurrency from '../../../filters/toCurrency';
|
||||
|
||||
const DEFAULT_PRICE_KG = 0;
|
||||
|
||||
const { getTokenMultimedia } = useSession();
|
||||
const token = getTokenMultimedia();
|
||||
const { t } = useI18n();
|
||||
|
||||
defineProps({
|
||||
|
@ -29,14 +27,7 @@ const dialog = ref(null);
|
|||
<div class="container order-catalog-item overflow-hidden">
|
||||
<QCard class="card shadow-6">
|
||||
<div class="img-wrapper">
|
||||
<QImg
|
||||
:src="`/api/Images/catalog/200x200/${item.id}/download?access_token=${token}`"
|
||||
spinner-color="primary"
|
||||
:ratio="1"
|
||||
height="192"
|
||||
width="192"
|
||||
class="image"
|
||||
/>
|
||||
<VnImg :id="item.id" class="image" />
|
||||
<div v-if="item.hex" class="item-color-container">
|
||||
<div
|
||||
class="item-color"
|
||||
|
@ -59,7 +50,10 @@ const dialog = ref(null);
|
|||
</template>
|
||||
<div class="footer">
|
||||
<div class="price">
|
||||
<p>{{ item.available }} {{ t('to') }} {{ item.price }}</p>
|
||||
<p>
|
||||
{{ item.available }} {{ t('to') }}
|
||||
{{ toCurrency(item.price) }}
|
||||
</p>
|
||||
<QIcon name="add_circle" class="icon">
|
||||
<QTooltip>{{ t('globals.add') }}</QTooltip>
|
||||
<QPopupProxy ref="dialog">
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script setup>
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { reactive, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import axios from 'axios';
|
||||
|
@ -16,6 +16,7 @@ const route = useRoute();
|
|||
const state = useState();
|
||||
const ORDER_MODEL = 'order';
|
||||
|
||||
const router = useRouter();
|
||||
const isNew = Boolean(!route.params.id);
|
||||
const initialFormState = reactive({
|
||||
clientFk: null,
|
||||
|
@ -26,22 +27,19 @@ const initialFormState = reactive({
|
|||
const clientList = ref([]);
|
||||
const agencyList = ref([]);
|
||||
const addressList = ref([]);
|
||||
const clientId = ref(null);
|
||||
|
||||
const onClientsFetched = async (data) => {
|
||||
try {
|
||||
clientList.value = data;
|
||||
initialFormState.clientFk = Number(route.query?.clientFk) || null;
|
||||
const onClientsFetched = (data) => {
|
||||
clientList.value = data;
|
||||
initialFormState.clientFk = Number(route.query?.clientFk) || null;
|
||||
clientId.value = initialFormState.clientFk;
|
||||
|
||||
if (initialFormState.clientFk) {
|
||||
const { defaultAddressFk } = clientList.value.find(
|
||||
(client) => client.id === initialFormState.clientFk
|
||||
);
|
||||
|
||||
if (defaultAddressFk) await fetchAddressList(defaultAddressFk);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error fetching clients', err);
|
||||
}
|
||||
const client = clientList.value.find(
|
||||
(client) => client.id === initialFormState.clientFk
|
||||
);
|
||||
if (!client?.defaultAddressFk)
|
||||
throw new Error(t(`No default address found for the client`));
|
||||
fetchAddressList(client.defaultAddressFk);
|
||||
};
|
||||
|
||||
const fetchAddressList = async (addressId) => {
|
||||
|
@ -55,7 +53,6 @@ const fetchAddressList = async (addressId) => {
|
|||
},
|
||||
});
|
||||
addressList.value = data;
|
||||
// Set address by default
|
||||
if (addressList.value?.length === 1) {
|
||||
state.get(ORDER_MODEL).addressFk = addressList.value[0].id;
|
||||
}
|
||||
|
@ -121,6 +118,21 @@ const orderFilter = {
|
|||
},
|
||||
],
|
||||
};
|
||||
|
||||
const onClientChange = async (clientId) => {
|
||||
try {
|
||||
const { data } = await axios.get(`Clients/${clientId}`);
|
||||
console.log('info cliente: ', data);
|
||||
|
||||
await fetchAddressList(data.defaultAddressFk);
|
||||
} catch (error) {
|
||||
console.error('Error al cambiar el cliente:', error);
|
||||
}
|
||||
};
|
||||
|
||||
async function onDataSaved(data) {
|
||||
await router.push({ path: `/order/${data}/catalog` });
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -134,13 +146,15 @@ const orderFilter = {
|
|||
<div class="q-pa-md">
|
||||
<FormModel
|
||||
:url="!isNew ? `Orders/${route.params.id}` : null"
|
||||
:url-create="isNew ? 'Orders/new' : null"
|
||||
url-create="Orders/new"
|
||||
@on-data-saved="onDataSaved"
|
||||
:model="ORDER_MODEL"
|
||||
:form-initial-data="isNew ? initialFormState : null"
|
||||
:observe-form-changes="!isNew"
|
||||
:mapper="isNew ? orderMapper : null"
|
||||
:filter="orderFilter"
|
||||
@on-fetch="fetchOrderDetails"
|
||||
auto-load
|
||||
>
|
||||
<template #form="{ data }">
|
||||
<VnRow class="row q-gutter-md q-mb-md">
|
||||
|
@ -151,9 +165,7 @@ const orderFilter = {
|
|||
option-value="id"
|
||||
option-label="name"
|
||||
hide-selected
|
||||
@update:model-value="
|
||||
(client) => fetchAddressList(client.defaultAddressFk)
|
||||
"
|
||||
@update:model-value="onClientChange"
|
||||
>
|
||||
<template #option="scope">
|
||||
<QItem v-bind="scope.itemProps">
|
||||
|
@ -170,12 +182,10 @@ const orderFilter = {
|
|||
v-model="data.addressFk"
|
||||
:options="addressList"
|
||||
option-value="id"
|
||||
option-label="nickname"
|
||||
option-label="street"
|
||||
hide-selected
|
||||
:disable="!addressList?.length"
|
||||
@update:model-value="
|
||||
() => fetchAgencyList(data.landed, data.addressFk)
|
||||
"
|
||||
@update:model-value="onAddressChange"
|
||||
>
|
||||
<template #option="scope">
|
||||
<QItem v-bind="scope.itemProps">
|
||||
|
@ -216,3 +226,8 @@ const orderFilter = {
|
|||
</FormModel>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<i18n>
|
||||
es:
|
||||
No default address found for the client: No hay ninguna dirección asociada a este cliente.
|
||||
</i18n>
|
||||
|
|
|
@ -51,7 +51,11 @@ const detailsColumns = ref([
|
|||
|
||||
<template>
|
||||
<div class="q-pa-md">
|
||||
<CardSummary ref="summary" :url="`Orders/${entityId}/summary`">
|
||||
<CardSummary
|
||||
ref="summary"
|
||||
:url="`Orders/${entityId}/summary`"
|
||||
data-key="OrderSummary"
|
||||
>
|
||||
<template #header="{ entity }">
|
||||
{{ t('order.summary.basket') }} #{{ entity?.id }} -
|
||||
{{ entity?.client?.name }} ({{ entity?.clientFk }})
|
||||
|
|
|
@ -4,7 +4,6 @@ import { useRoute } from 'vue-router';
|
|||
import { onMounted, onUnmounted, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import VnPaginate from 'components/ui/VnPaginate.vue';
|
||||
import VnSearchbar from 'components/ui/VnSearchbar.vue';
|
||||
import OrderCatalogItem from 'pages/Order/Card/OrderCatalogItem.vue';
|
||||
import OrderCatalogFilter from 'pages/Order/Card/OrderCatalogFilter.vue';
|
||||
|
||||
|
@ -35,38 +34,31 @@ function extractTags(items) {
|
|||
});
|
||||
});
|
||||
tags.value = resultTags;
|
||||
extractValueTags(items);
|
||||
}
|
||||
|
||||
const tagValue = ref([]);
|
||||
|
||||
function extractValueTags(items) {
|
||||
const resultValueTags = items.flatMap((x) =>
|
||||
Object.keys(x)
|
||||
.filter((k) => /^value\d+$/.test(k))
|
||||
.map((v) => x[v])
|
||||
.filter((v) => v)
|
||||
.sort()
|
||||
);
|
||||
tagValue.value = resultValueTags;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Teleport to="#searchbar" v-if="stateStore.isHeaderMounted()">
|
||||
<VnSearchbar
|
||||
data-key="OrderCatalogList"
|
||||
url="Orders/CatalogFilter"
|
||||
:limit="50"
|
||||
:user-params="catalogParams"
|
||||
:static-params="['orderFk', 'orderBy']"
|
||||
:redirect="false"
|
||||
/>
|
||||
</Teleport>
|
||||
<Teleport v-if="stateStore.isHeaderMounted()" to="#actions-append">
|
||||
<div class="row q-gutter-x-sm">
|
||||
<QBtn
|
||||
flat
|
||||
@click.stop="stateStore.toggleRightDrawer()"
|
||||
round
|
||||
dense
|
||||
icon="menu"
|
||||
>
|
||||
<QTooltip bottom anchor="bottom right">
|
||||
{{ t('globals.collapseMenu') }}
|
||||
</QTooltip>
|
||||
</QBtn>
|
||||
</div>
|
||||
</Teleport>
|
||||
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
|
||||
<QScrollArea class="fit text-grey-8">
|
||||
<OrderCatalogFilter data-key="OrderCatalogList" :tags="tags" />
|
||||
<OrderCatalogFilter
|
||||
data-key="OrderCatalogList"
|
||||
:tag-value="tagValue"
|
||||
:tags="tags"
|
||||
/>
|
||||
</QScrollArea>
|
||||
</QDrawer>
|
||||
<QPage class="column items-center q-pa-md">
|
||||
|
|
|
@ -7,19 +7,17 @@ import { useQuasar } from 'quasar';
|
|||
import VnPaginate from 'components/ui/VnPaginate.vue';
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import VnLv from 'components/ui/VnLv.vue';
|
||||
import CardList from 'components/ui/CardList.vue';
|
||||
import FetchedTags from 'components/ui/FetchedTags.vue';
|
||||
import VnConfirm from 'components/ui/VnConfirm.vue';
|
||||
import VnImg from 'components/ui/VnImg.vue';
|
||||
|
||||
import { toCurrency, toDate } from 'src/filters';
|
||||
import { useSession } from 'composables/useSession';
|
||||
import axios from 'axios';
|
||||
import ItemDescriptorProxy from '../Item/Card/ItemDescriptorProxy.vue';
|
||||
|
||||
const route = useRoute();
|
||||
const { t } = useI18n();
|
||||
const { getTokenMultimedia } = useSession();
|
||||
const quasar = useQuasar();
|
||||
const token = getTokenMultimedia();
|
||||
const orderSummary = ref({
|
||||
total: null,
|
||||
vat: null,
|
||||
|
@ -61,6 +59,56 @@ async function confirmOrder() {
|
|||
type: 'positive',
|
||||
});
|
||||
}
|
||||
|
||||
const detailsColumns = ref([
|
||||
{
|
||||
name: 'img',
|
||||
label: '',
|
||||
field: (row) => row?.item?.id,
|
||||
},
|
||||
{
|
||||
name: 'item',
|
||||
label: t('order.summary.item'),
|
||||
field: (row) => row?.item?.id,
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
label: t('globals.description'),
|
||||
field: (row) => row?.item?.name,
|
||||
},
|
||||
{
|
||||
name: 'warehouse',
|
||||
label: t('warehouse'),
|
||||
field: (row) => row?.warehouse?.name,
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
name: 'shipped',
|
||||
label: t('shipped'),
|
||||
field: (row) => toDate(row?.shipped),
|
||||
},
|
||||
{
|
||||
name: 'quantity',
|
||||
label: t('order.summary.quantity'),
|
||||
field: (row) => row?.quantity,
|
||||
},
|
||||
{
|
||||
name: 'price',
|
||||
label: t('order.summary.price'),
|
||||
field: (row) => toCurrency(row?.price),
|
||||
},
|
||||
{
|
||||
name: 'amount',
|
||||
label: t('order.summary.amount'),
|
||||
field: (row) => toCurrency(row?.quantity * row?.price),
|
||||
},
|
||||
{
|
||||
name: 'actions',
|
||||
label: '',
|
||||
field: (row) => row?.id,
|
||||
},
|
||||
]);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -83,30 +131,33 @@ async function confirmOrder() {
|
|||
auto-load
|
||||
/>
|
||||
<QPage :key="componentKey" class="column items-center q-pa-md">
|
||||
<div class="vn-card-list">
|
||||
<div class="order-list full-width">
|
||||
<div v-if="!orderSummary.total" class="no-result">
|
||||
{{ t('globals.noResults') }}
|
||||
</div>
|
||||
<QCard v-else class="order-lines-summary q-pa-lg">
|
||||
<p class="header text-right block">
|
||||
{{ t('summary') }}
|
||||
</p>
|
||||
<VnLv
|
||||
v-if="orderSummary.vat && orderSummary.total"
|
||||
:label="t('subtotal')"
|
||||
:value="toCurrency(orderSummary.total - orderSummary.vat)"
|
||||
/>
|
||||
<VnLv
|
||||
v-if="orderSummary.vat"
|
||||
:label="t('VAT')"
|
||||
:value="toCurrency(orderSummary?.vat)"
|
||||
/>
|
||||
<VnLv
|
||||
v-if="orderSummary.total"
|
||||
:label="t('total')"
|
||||
:value="toCurrency(orderSummary?.total)"
|
||||
/>
|
||||
</QCard>
|
||||
|
||||
<QDrawer side="right" :width="270" show-if-above>
|
||||
<QCard class="order-lines-summary q-pa-lg">
|
||||
<p class="header text-right block">
|
||||
{{ t('summary') }}
|
||||
</p>
|
||||
<VnLv
|
||||
v-if="orderSummary.vat && orderSummary.total"
|
||||
:label="t('subtotal')"
|
||||
:value="toCurrency(orderSummary.total - orderSummary.vat)"
|
||||
/>
|
||||
<VnLv
|
||||
v-if="orderSummary.vat"
|
||||
:label="t('VAT')"
|
||||
:value="toCurrency(orderSummary?.vat)"
|
||||
/>
|
||||
<VnLv
|
||||
v-if="orderSummary.total"
|
||||
:label="t('total')"
|
||||
:value="toCurrency(orderSummary?.total)"
|
||||
/>
|
||||
</QCard>
|
||||
</QDrawer>
|
||||
<VnPaginate
|
||||
data-key="OrderLines"
|
||||
url="OrderRows"
|
||||
|
@ -125,74 +176,71 @@ async function confirmOrder() {
|
|||
}"
|
||||
>
|
||||
<template #body="{ rows }">
|
||||
<div class="catalog-list q-mt-xl">
|
||||
<CardList
|
||||
v-for="row in rows"
|
||||
:key="row.id"
|
||||
:id="row.id"
|
||||
:title="row?.item?.name"
|
||||
class="cursor-inherit"
|
||||
<div class="q-pa-md">
|
||||
<QTable
|
||||
:columns="detailsColumns"
|
||||
:rows="rows"
|
||||
flat
|
||||
class="full-width"
|
||||
style="text-align: center"
|
||||
>
|
||||
<template #title>
|
||||
<div class="flex items-center">
|
||||
<div class="image-wrapper q-mr-md">
|
||||
<QImg
|
||||
:src="`/api/Images/catalog/50x50/${row?.item?.id}/download?access_token=${token}`"
|
||||
spinner-color="primary"
|
||||
:ratio="1"
|
||||
height="50"
|
||||
width="50"
|
||||
class="image"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="title text-primary text-weight-bold text-h5"
|
||||
<template #header="props">
|
||||
<QTr class="tr-header" :props="props">
|
||||
<QTh
|
||||
v-for="col in props.cols"
|
||||
:key="col.name"
|
||||
:props="props"
|
||||
style="text-align: center"
|
||||
>
|
||||
{{ row?.item?.name }}
|
||||
{{ t(col.label) }}
|
||||
</QTh>
|
||||
</QTr>
|
||||
</template>
|
||||
<template #body-cell-img="{ value }">
|
||||
<QTd>
|
||||
<div class="image-wrapper">
|
||||
<VnImg :id="value" class="rounded" />
|
||||
</div>
|
||||
<QChip class="q-chip-color" outline size="sm">
|
||||
{{ t('ID') }}: {{ row.id }}
|
||||
</QChip>
|
||||
</div>
|
||||
</QTd>
|
||||
</template>
|
||||
<template #list-items>
|
||||
<div class="q-mb-sm">
|
||||
<span class="text-uppercase subname">
|
||||
{{ row.item.subName }}
|
||||
<template #body-cell-item="{ value }">
|
||||
<QTd class="item">
|
||||
<span class="link">
|
||||
<QBtn flat>
|
||||
{{ value }}
|
||||
</QBtn>
|
||||
<ItemDescriptorProxy :id="value" />
|
||||
</span>
|
||||
<FetchedTags :item="row.item" :max-length="5" />
|
||||
</div>
|
||||
<VnLv :label="t('item')" :value="String(row.item.id)" />
|
||||
<VnLv
|
||||
:label="t('warehouse')"
|
||||
:value="row.warehouse.name"
|
||||
/>
|
||||
<VnLv
|
||||
:label="t('shipped')"
|
||||
:value="toDate(row.shipped)"
|
||||
/>
|
||||
<VnLv
|
||||
:label="t('quantity')"
|
||||
:value="String(row.quantity)"
|
||||
/>
|
||||
<VnLv
|
||||
:label="t('price')"
|
||||
:value="toCurrency(row.price)"
|
||||
/>
|
||||
<VnLv
|
||||
:label="t('amount')"
|
||||
:value="toCurrency(row.price * row.quantity)"
|
||||
/>
|
||||
</QTd>
|
||||
</template>
|
||||
<template #actions v-if="!order?.isConfirmed">
|
||||
<QBtn
|
||||
:label="t('remove')"
|
||||
@click.stop="confirmRemove(row)"
|
||||
color="primary"
|
||||
style="margin-top: 15px"
|
||||
/>
|
||||
<template #body-cell-description="{ row, value }">
|
||||
<QTd>
|
||||
<div
|
||||
class="row column full-width justify-between items-start"
|
||||
>
|
||||
{{ value }}
|
||||
<div v-if="value" class="subName">
|
||||
{{ value.toUpperCase() }}
|
||||
</div>
|
||||
</div>
|
||||
<FetchedTags :item="row.item" :max-length="6" />
|
||||
</QTd>
|
||||
</template>
|
||||
</CardList>
|
||||
|
||||
<template #body-cell-actions="{ value }">
|
||||
<QTd>
|
||||
<QIcon
|
||||
name="delete"
|
||||
color="primary"
|
||||
size="sm"
|
||||
class="cursor-pointer"
|
||||
@click.stop="confirmRemove(value)"
|
||||
>
|
||||
<QTooltip>{{ t('Remove thermograph') }}</QTooltip>
|
||||
</QIcon>
|
||||
</QTd>
|
||||
</template>
|
||||
</QTable>
|
||||
</div>
|
||||
</template>
|
||||
</VnPaginate>
|
||||
|
@ -239,14 +287,7 @@ async function confirmOrder() {
|
|||
.image-wrapper {
|
||||
height: 50px;
|
||||
width: 50px;
|
||||
|
||||
.image {
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.subname {
|
||||
color: var(--vn-label-color);
|
||||
margin-left: 30%;
|
||||
}
|
||||
|
||||
.no-result {
|
||||
|
@ -255,6 +296,11 @@ async function confirmOrder() {
|
|||
color: var(--vn-label-color);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.subName {
|
||||
text-transform: uppercase;
|
||||
color: var(--vn-label-color);
|
||||
}
|
||||
</style>
|
||||
<i18n>
|
||||
en:
|
||||
|
|
|
@ -30,6 +30,7 @@ const filter = {
|
|||
:url="`Parkings/${entityId}`"
|
||||
:filter="filter"
|
||||
@on-fetch="(data) => (parking = data)"
|
||||
data-key="Parking"
|
||||
>
|
||||
<template #header>{{ parking.code }}</template>
|
||||
<template #body>
|
||||
|
|
|
@ -240,4 +240,5 @@ es:
|
|||
From: Desde
|
||||
To: Hasta
|
||||
Served: Servida
|
||||
Days Onward: Días en adelante
|
||||
</i18n>
|
||||
|
|
|
@ -3,14 +3,15 @@ import { computed, onMounted, onUnmounted, ref } from 'vue';
|
|||
import { useRoute } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useStateStore } from 'stores/useStateStore';
|
||||
import CardSummary from 'components/ui/CardSummary.vue';
|
||||
import VnLv from 'components/ui/VnLv.vue';
|
||||
import { QIcon } from 'quasar';
|
||||
import { dashIfEmpty, toCurrency, toDate, toHour } from 'src/filters';
|
||||
import { openBuscaman } from 'src/utils/buscaman';
|
||||
import CardSummary from 'components/ui/CardSummary.vue';
|
||||
import WorkerDescriptorProxy from 'pages/Worker/Card/WorkerDescriptorProxy.vue';
|
||||
import CustomerDescriptorProxy from 'pages/Customer/Card/CustomerDescriptorProxy.vue';
|
||||
import TicketDescriptorProxy from 'pages/Ticket/Card/TicketDescriptorProxy.vue';
|
||||
import { openBuscaman } from 'src/utils/buscaman';
|
||||
import VnLv from 'components/ui/VnLv.vue';
|
||||
import VnTitle from 'src/components/common/VnTitle.vue';
|
||||
|
||||
const $props = defineProps({
|
||||
id: {
|
||||
|
@ -122,13 +123,20 @@ const ticketColumns = ref([
|
|||
ref="summary"
|
||||
:url="`Routes/${entityId}/summary`"
|
||||
:entity-id="entityId"
|
||||
data-key="RouteSummary"
|
||||
>
|
||||
<template #header="{ entity }">
|
||||
<span>{{ `${entity?.route.id} - ${entity?.route?.description}` }}</span>
|
||||
</template>
|
||||
<template #body="{ entity }">
|
||||
<QCard class="vn-max">
|
||||
<VnTitle
|
||||
:url="`#/route/${entityId}/basic-data`"
|
||||
:text="t('globals.pageTitles.basicData')"
|
||||
/>
|
||||
</QCard>
|
||||
|
||||
<QCard class="vn-one">
|
||||
<VnLv :label="t('ID')" :value="entity?.route.id" />
|
||||
<VnLv
|
||||
:label="t('route.summary.date')"
|
||||
:value="toDate(entity?.route.created)"
|
||||
|
@ -153,24 +161,6 @@ const ticketColumns = ref([
|
|||
:label="t('route.summary.cost')"
|
||||
:value="toCurrency(entity.route?.cost)"
|
||||
/>
|
||||
</QCard>
|
||||
<QCard class="vn-one">
|
||||
<VnLv
|
||||
:label="t('route.summary.started')"
|
||||
:value="toHour(entity?.route.started)"
|
||||
/>
|
||||
<VnLv
|
||||
:label="t('route.summary.finished')"
|
||||
:value="toHour(entity?.route.finished)"
|
||||
/>
|
||||
<VnLv
|
||||
:label="t('route.summary.kmStart')"
|
||||
:value="dashIfEmpty(entity?.route?.kmStart)"
|
||||
/>
|
||||
<VnLv
|
||||
:label="t('route.summary.kmEnd')"
|
||||
:value="dashIfEmpty(entity?.route?.kmEnd)"
|
||||
/>
|
||||
<VnLv
|
||||
:label="t('route.summary.volume')"
|
||||
:value="`${dashIfEmpty(entity?.route?.m3)} / ${dashIfEmpty(
|
||||
|
@ -192,19 +182,32 @@ const ticketColumns = ref([
|
|||
/>
|
||||
</QCard>
|
||||
<QCard class="vn-one">
|
||||
<div class="header">
|
||||
{{ t('globals.description') }}
|
||||
</div>
|
||||
<p>
|
||||
{{ dashIfEmpty(entity?.route?.description) }}
|
||||
</p>
|
||||
<VnLv
|
||||
:label="t('route.summary.started')"
|
||||
:value="toHour(entity?.route.started)"
|
||||
/>
|
||||
<VnLv
|
||||
:label="t('route.summary.finished')"
|
||||
:value="toHour(entity?.route.finished)"
|
||||
/>
|
||||
<VnLv
|
||||
:label="t('route.summary.kmStart')"
|
||||
:value="dashIfEmpty(entity?.route?.kmStart)"
|
||||
/>
|
||||
<VnLv
|
||||
:label="t('route.summary.kmEnd')"
|
||||
:value="dashIfEmpty(entity?.route?.kmEnd)"
|
||||
/>
|
||||
<VnLv
|
||||
:label="t('globals.description')"
|
||||
:value="dashIfEmpty(entity?.route?.description)"
|
||||
/>
|
||||
</QCard>
|
||||
|
||||
<QCard class="vn-max">
|
||||
<a class="header" :href="`#/route/${entityId}/tickets`">
|
||||
{{ t('route.summary.tickets') }}
|
||||
<QIcon name="open_in_new" color="primary" />
|
||||
</a>
|
||||
<VnTitle
|
||||
:url="`#/route/${entityId}/tickets`"
|
||||
:text="t('route.summary.tickets')"
|
||||
/>
|
||||
<QTable
|
||||
:columns="ticketColumns"
|
||||
:rows="entity?.tickets"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script setup>
|
||||
import { onBeforeMount, computed, ref } from 'vue';
|
||||
import { onBeforeMount, onMounted, computed, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { Notify } from 'quasar';
|
||||
import axios from 'axios';
|
||||
|
@ -10,10 +10,12 @@ import CmrFilter from './CmrFilter.vue';
|
|||
import TicketDescriptorProxy from 'pages/Ticket/Card/TicketDescriptorProxy.vue';
|
||||
import CustomerDescriptorProxy from 'pages/Customer/Card/CustomerDescriptorProxy.vue';
|
||||
import RightMenu from 'src/components/common/RightMenu.vue';
|
||||
import { useStateStore } from 'src/stores/useStateStore';
|
||||
|
||||
const { t } = useI18n();
|
||||
const { getTokenMultimedia } = useSession();
|
||||
const token = getTokenMultimedia();
|
||||
const state = useStateStore();
|
||||
const selected = ref([]);
|
||||
const warehouses = ref([]);
|
||||
|
||||
|
@ -81,6 +83,9 @@ onBeforeMount(async () => {
|
|||
const { data } = await axios.get('Warehouses');
|
||||
warehouses.value = data;
|
||||
});
|
||||
|
||||
onMounted(() => (state.rightDrawer = true));
|
||||
|
||||
function getApiUrl() {
|
||||
return new URL(window.location).origin;
|
||||
}
|
||||
|
@ -109,13 +114,7 @@ function downloadPdfs() {
|
|||
</RightMenu>
|
||||
<div class="column items-center">
|
||||
<div class="list">
|
||||
<VnPaginate
|
||||
data-key="CmrList"
|
||||
:url="`Routes/cmrs`"
|
||||
order="cmrFk DESC"
|
||||
limit="null"
|
||||
auto-load
|
||||
>
|
||||
<VnPaginate data-key="CmrList" :url="`Routes/cmrs`" order="cmrFk DESC">
|
||||
<template #body="{ rows }">
|
||||
<QTable
|
||||
:columns="columns"
|
||||
|
|
|
@ -67,6 +67,7 @@ const filter = {
|
|||
},
|
||||
},
|
||||
],
|
||||
where: { id: entityId },
|
||||
};
|
||||
|
||||
const openAddStopDialog = () => {
|
||||
|
@ -84,7 +85,7 @@ const openAddStopDialog = () => {
|
|||
<CardSummary
|
||||
data-key="RoadmapSummary"
|
||||
ref="summary"
|
||||
:url="`Roadmaps/${entityId}`"
|
||||
:url="`Roadmaps`"
|
||||
:filter="filter"
|
||||
>
|
||||
<template #header-left>
|
||||
|
|
|
@ -39,7 +39,7 @@ const selectedRows = ref([]);
|
|||
const columns = computed(() => [
|
||||
{
|
||||
name: 'ID',
|
||||
label: t('ID'),
|
||||
label: 'Id',
|
||||
field: (row) => row.routeFk,
|
||||
sortable: true,
|
||||
align: 'left',
|
||||
|
@ -117,7 +117,9 @@ const columns = computed(() => [
|
|||
|
||||
const refreshKey = ref(0);
|
||||
|
||||
const total = computed(() => selectedRows.value.reduce((item) => item?.price || 0, 0));
|
||||
const total = computed(() => {
|
||||
return selectedRows.value.reduce((sum, item) => sum + item.price, 0);
|
||||
});
|
||||
|
||||
const openDmsUploadDialog = async () => {
|
||||
dmsDialog.value.rowsToCreateInvoiceIn = selectedRows.value
|
||||
|
@ -212,7 +214,6 @@ function navigateToRouteSummary(event, row) {
|
|||
data-key="RouteAutonomousList"
|
||||
url="AgencyTerms/filter"
|
||||
:limit="20"
|
||||
auto-load
|
||||
>
|
||||
<template #body="{ rows }">
|
||||
<div class="q-pa-md">
|
||||
|
@ -306,6 +307,13 @@ function navigateToRouteSummary(event, row) {
|
|||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
th:last-child,
|
||||
td:last-child {
|
||||
background-color: var(--vn-section-color);
|
||||
position: sticky;
|
||||
right: 0;
|
||||
}
|
||||
</style>
|
||||
<i18n>
|
||||
es:
|
||||
|
|
|
@ -1,40 +1,50 @@
|
|||
<script setup>
|
||||
import VnPaginate from 'components/ui/VnPaginate.vue';
|
||||
import { useStateStore } from 'stores/useStateStore';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue';
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import { dashIfEmpty, toHour } from 'src/filters';
|
||||
import VnSelect from 'components/common/VnSelect.vue';
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import { useValidator } from 'composables/useValidator';
|
||||
import { useSession } from 'composables/useSession';
|
||||
import { useSummaryDialog } from 'src/composables/useSummaryDialog';
|
||||
import { useArrayData } from 'composables/useArrayData';
|
||||
import { useQuasar } from 'quasar';
|
||||
|
||||
import axios from 'axios';
|
||||
import RouteSearchbar from 'pages/Route/Card/RouteSearchbar.vue';
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import TableVisibleColumns from 'src/components/common/TableVisibleColumns.vue';
|
||||
import RouteSummary from 'pages/Route/Card/RouteSummary.vue';
|
||||
import RouteFilter from 'pages/Route/Card/RouteFilter.vue';
|
||||
import RouteListTicketsDialog from 'pages/Route/Card/RouteListTicketsDialog.vue';
|
||||
import RightMenu from 'src/components/common/RightMenu.vue';
|
||||
|
||||
import VnPaginate from 'components/ui/VnPaginate.vue';
|
||||
import VnSelect from 'components/common/VnSelect.vue';
|
||||
import VnInputDate from 'components/common/VnInputDate.vue';
|
||||
import VnInput from 'components/common/VnInput.vue';
|
||||
import VnInputTime from 'components/common/VnInputTime.vue';
|
||||
import axios from 'axios';
|
||||
import RouteSearchbar from 'pages/Route/Card/RouteSearchbar.vue';
|
||||
import TableVisibleColumns from 'src/components/common/TableVisibleColumns.vue';
|
||||
import RouteFilter from 'pages/Route/Card/RouteFilter.vue';
|
||||
import RouteSummary from 'pages/Route/Card/RouteSummary.vue';
|
||||
import VnLv from 'src/components/ui/VnLv.vue';
|
||||
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
|
||||
import { useSession } from 'composables/useSession';
|
||||
import { useSummaryDialog } from 'src/composables/useSummaryDialog';
|
||||
import RouteListTicketsDialog from 'pages/Route/Card/RouteListTicketsDialog.vue';
|
||||
import { useQuasar } from 'quasar';
|
||||
import { useArrayData } from 'composables/useArrayData';
|
||||
import RightMenu from 'src/components/common/RightMenu.vue';
|
||||
|
||||
const stateStore = useStateStore();
|
||||
const { t } = useI18n();
|
||||
const { validate } = useValidator();
|
||||
const { viewSummary } = useSummaryDialog();
|
||||
const quasar = useQuasar();
|
||||
const session = useSession();
|
||||
const { viewSummary } = useSummaryDialog();
|
||||
const paginate = ref();
|
||||
const visibleColumns = ref([]);
|
||||
const selectedRows = ref([]);
|
||||
const workers = ref([]);
|
||||
const agencyList = ref([]);
|
||||
const vehicleList = ref([]);
|
||||
const allColumnNames = ref([]);
|
||||
const confirmationDialog = ref(false);
|
||||
const startingDate = ref(null);
|
||||
const refreshKey = ref(0);
|
||||
|
||||
const columns = computed(() => [
|
||||
{
|
||||
name: 'ID',
|
||||
label: t('ID'),
|
||||
name: 'Id',
|
||||
label: t('Id'),
|
||||
field: (row) => row.id,
|
||||
sortable: true,
|
||||
align: 'center',
|
||||
|
@ -109,14 +119,12 @@ const columns = computed(() => [
|
|||
align: 'right',
|
||||
},
|
||||
]);
|
||||
|
||||
const arrayData = useArrayData('EntryLatestBuys', {
|
||||
url: 'Buys/latestBuysFilter',
|
||||
order: ['itemFk DESC'],
|
||||
});
|
||||
const refreshKey = ref(0);
|
||||
const workers = ref([]);
|
||||
const agencyList = ref([]);
|
||||
const vehicleList = ref([]);
|
||||
|
||||
const updateRoute = async (route) => {
|
||||
try {
|
||||
return await axios.patch(`Routes/${route.id}`, route);
|
||||
|
@ -124,9 +132,6 @@ const updateRoute = async (route) => {
|
|||
return err;
|
||||
}
|
||||
};
|
||||
const allColumnNames = ref([]);
|
||||
const confirmationDialog = ref(false);
|
||||
const startingDate = ref(null);
|
||||
|
||||
const cloneRoutes = () => {
|
||||
axios.post('Routes/clone', {
|
||||
|
@ -135,6 +140,7 @@ const cloneRoutes = () => {
|
|||
});
|
||||
refreshKey.value++;
|
||||
startingDate.value = null;
|
||||
paginate.value.fetch();
|
||||
};
|
||||
|
||||
const showRouteReport = () => {
|
||||
|
@ -154,15 +160,13 @@ const showRouteReport = () => {
|
|||
window.open(url, '_blank');
|
||||
};
|
||||
|
||||
const markAsServed = () => {
|
||||
selectedRows.value.forEach((row) => {
|
||||
if (row?.id) {
|
||||
axios.patch(`Routes/${row?.id}`, { isOk: true });
|
||||
}
|
||||
function markAsServed() {
|
||||
selectedRows.value.forEach(async (row) => {
|
||||
if (row?.id) await axios.patch(`Routes/${row?.id}`, { isOk: true });
|
||||
});
|
||||
refreshKey.value++;
|
||||
startingDate.value = null;
|
||||
};
|
||||
}
|
||||
|
||||
const openTicketsDialog = (id) => {
|
||||
if (!id) {
|
||||
|
@ -179,11 +183,9 @@ const openTicketsDialog = (id) => {
|
|||
};
|
||||
|
||||
onMounted(async () => {
|
||||
stateStore.rightDrawer = true;
|
||||
allColumnNames.value = columns.value.map((col) => col.name);
|
||||
await arrayData.fetch({ append: false });
|
||||
});
|
||||
onUnmounted(() => (stateStore.rightDrawer = false));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -210,6 +212,10 @@ onUnmounted(() => (stateStore.rightDrawer = false));
|
|||
<QBtn flat :label="t('Cancel')" v-close-popup class="text-primary" />
|
||||
<QBtn color="primary" v-close-popup @click="cloneRoutes">
|
||||
{{ t('globals.clone') }}
|
||||
<VnLv
|
||||
:label="t('route.summary.packages')"
|
||||
:value="getTotalPackages(entity.tickets)"
|
||||
/>
|
||||
</QBtn>
|
||||
</QCardActions>
|
||||
</QCard>
|
||||
|
@ -228,7 +234,7 @@ onUnmounted(() => (stateStore.rightDrawer = false));
|
|||
class="LeftIcon"
|
||||
:all-columns="allColumnNames"
|
||||
table-code="routesList"
|
||||
labels-traductions-path="globals"
|
||||
labels-traductions-path="route.columnLabels"
|
||||
@on-config-saved="visibleColumns = [...$event]"
|
||||
/>
|
||||
</template>
|
||||
|
@ -256,7 +262,7 @@ onUnmounted(() => (stateStore.rightDrawer = false));
|
|||
color="primary"
|
||||
class="q-mr-sm"
|
||||
:disable="!selectedRows?.length"
|
||||
@click="markAsServed"
|
||||
@click="markAsServed()"
|
||||
>
|
||||
<QTooltip>{{ t('Mark as served') }}</QTooltip>
|
||||
</QBtn>
|
||||
|
@ -269,7 +275,6 @@ onUnmounted(() => (stateStore.rightDrawer = false));
|
|||
url="Routes/filter"
|
||||
:order="['created ASC', 'started ASC', 'id ASC']"
|
||||
:limit="20"
|
||||
auto-load
|
||||
>
|
||||
<template #body="{ rows }">
|
||||
<div class="q-pa-md route-table">
|
||||
|
@ -500,7 +505,6 @@ en:
|
|||
hourStarted: Started hour
|
||||
hourFinished: Finished hour
|
||||
es:
|
||||
ID: ID
|
||||
Worker: Trabajador
|
||||
Agency: Agencia
|
||||
Vehicle: Vehículo
|
||||
|
@ -521,4 +525,6 @@ es:
|
|||
Summary: Resumen
|
||||
Route is closed: La ruta está cerrada
|
||||
Route is not served: La ruta no está servida
|
||||
hourStarted: Hora de inicio
|
||||
hourFinished: Hora de fin
|
||||
</i18n>
|
||||
|
|
|
@ -128,8 +128,8 @@ function confirmRemove() {
|
|||
.onOk(() => refreshKey.value++);
|
||||
}
|
||||
|
||||
function navigateToRoadmapSummary(event, row) {
|
||||
router.push({ name: 'RoadmapSummary', params: { id: row.id } });
|
||||
function navigateToRoadmapSummary(_, { id }) {
|
||||
router.push({ name: 'RoadmapSummary', params: { id } });
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -193,7 +193,6 @@ function navigateToRoadmapSummary(event, row) {
|
|||
url="Roadmaps"
|
||||
:limit="20"
|
||||
:filter="filter"
|
||||
auto-load
|
||||
>
|
||||
<template #body="{ rows }">
|
||||
<div class="q-pa-md">
|
||||
|
|
|
@ -141,7 +141,7 @@ const setOrderedPriority = async () => {
|
|||
};
|
||||
|
||||
const sortRoutes = async () => {
|
||||
await axios.get(`Routes/${route.params?.id}/guessPriority/`);
|
||||
await axios.patch(`Routes/${route.params?.id}/guessPriority/`);
|
||||
refreshKey.value++;
|
||||
};
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Por como esta useArrayData, fetch no acepta los datos por paramtros. Solo
{ append = false, updateRouter = true }