forked from verdnatura/salix-front
Merge branch 'dev' into 6464-fixNegativeBases
This commit is contained in:
commit
d0c3f61b73
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "salix-front",
|
||||
"version": "24.26.2",
|
||||
"version": "24.28.1",
|
||||
"description": "Salix frontend",
|
||||
"productName": "Salix",
|
||||
"author": "Verdnatura",
|
||||
|
|
|
@ -87,7 +87,7 @@ const $props = defineProps({
|
|||
const emit = defineEmits(['onFetch', 'onDataSaved']);
|
||||
const modelValue = computed(
|
||||
() => $props.model ?? `formModel_${route?.meta?.title ?? route.name}`
|
||||
);
|
||||
).value;
|
||||
const componentIsRendered = ref(false);
|
||||
const arrayData = useArrayData(modelValue);
|
||||
const isLoading = ref(false);
|
||||
|
@ -119,9 +119,10 @@ onMounted(async () => {
|
|||
// Podemos enviarle al form la estructura de data inicial sin necesidad de fetchearla
|
||||
state.set(modelValue, $props.formInitialData);
|
||||
|
||||
if ($props.autoLoad && !$props.formInitialData && $props.url) await fetch();
|
||||
else if (arrayData.store.data) updateAndEmit('onFetch', arrayData.store.data);
|
||||
|
||||
if (!$props.formInitialData) {
|
||||
if ($props.autoLoad && $props.url) await fetch();
|
||||
else if (arrayData.store.data) updateAndEmit('onFetch', arrayData.store.data);
|
||||
}
|
||||
if ($props.observeFormChanges) {
|
||||
watch(
|
||||
() => formData.value,
|
||||
|
@ -245,7 +246,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">
|
||||
|
|
|
@ -78,7 +78,6 @@ const inputRules = [
|
|||
<template v-if="$slots.prepend" #prepend>
|
||||
<slot name="prepend" />
|
||||
</template>
|
||||
|
||||
<template #append>
|
||||
<slot name="append" v-if="$slots.append && !$attrs.disabled" />
|
||||
<QIcon
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -115,13 +115,13 @@ const emit = defineEmits(['onFetch']);
|
|||
</QBtn>
|
||||
</RouterLink>
|
||||
<QBtn
|
||||
v-if="$slots.menu"
|
||||
color="white"
|
||||
dense
|
||||
flat
|
||||
icon="more_vert"
|
||||
round
|
||||
size="md"
|
||||
:class="{ invisible: !$slots.menu }"
|
||||
>
|
||||
<QTooltip>
|
||||
{{ t('components.cardDescriptor.moreOptions') }}
|
||||
|
|
|
@ -159,9 +159,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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -421,6 +421,7 @@ entry:
|
|||
buyingValue: Buying value
|
||||
freightValue: Freight value
|
||||
comissionValue: Commission value
|
||||
description: Description
|
||||
packageValue: Package value
|
||||
isIgnored: Is ignored
|
||||
price2: Grouping
|
||||
|
@ -991,6 +992,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,6 +107,7 @@ globals:
|
|||
aliasUsers: Usuarios
|
||||
subRoles: Subroles
|
||||
inheritedRoles: Roles heredados
|
||||
workers: Trabajadores
|
||||
created: Fecha creación
|
||||
worker: Trabajador
|
||||
now: Ahora
|
||||
|
@ -419,6 +420,7 @@ entry:
|
|||
buyingValue: Coste
|
||||
freightValue: Porte
|
||||
comissionValue: Comisión
|
||||
description: Descripción
|
||||
packageValue: Embalaje
|
||||
isIgnored: Ignorado
|
||||
price2: Grouping
|
||||
|
@ -976,6 +978,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,48 @@
|
|||
<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 VnRow from 'components/ui/VnRow.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,134 @@
|
|||
<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 { useSession } from 'src/composables/useSession';
|
||||
import FetchData from 'src/components/FetchData.vue';
|
||||
|
||||
const $props = defineProps({
|
||||
id: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
|
||||
const route = useRoute();
|
||||
const { t } = useI18n();
|
||||
const { getTokenMultimedia } = useSession();
|
||||
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'] } },
|
||||
};
|
||||
function getAccountAvatar() {
|
||||
const token = getTokenMultimedia();
|
||||
return `/api/Images/user/160x160/${entityId.value}/download?access_token=${token}`;
|
||||
}
|
||||
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>
|
||||
<QImg :src="getAccountAvatar()" 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>
|
||||
</QImg>
|
||||
</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,101 @@
|
|||
<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
|
||||
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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -317,10 +317,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">
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -135,14 +135,19 @@ watch;
|
|||
<template #icons>
|
||||
<QCardActions class="q-gutter-x-md">
|
||||
<QIcon
|
||||
v-if="currentEntry.isExcludedFromAvailable"
|
||||
v-if="currentEntry?.isExcludedFromAvailable"
|
||||
name="vn:inventory"
|
||||
color="primary"
|
||||
size="xs"
|
||||
>
|
||||
<QTooltip>{{ t('Inventory entry') }}</QTooltip>
|
||||
</QIcon>
|
||||
<QIcon v-if="currentEntry.isRaid" name="vn:net" color="primary" size="xs">
|
||||
<QIcon
|
||||
v-if="currentEntry?.isRaid"
|
||||
name="vn:net"
|
||||
color="primary"
|
||||
size="xs"
|
||||
>
|
||||
<QTooltip>{{ t('Virtual entry') }}</QTooltip>
|
||||
</QIcon>
|
||||
</QCardActions>
|
||||
|
|
|
@ -167,7 +167,7 @@ const columns = computed(() => [
|
|||
},
|
||||
},
|
||||
{
|
||||
label: t('globals.description'),
|
||||
label: t('entry.latestBuys.description'),
|
||||
field: 'description',
|
||||
name: 'description',
|
||||
align: 'left',
|
||||
|
@ -653,6 +653,15 @@ onUnmounted(() => (stateStore.rightDrawer = false));
|
|||
<EntryLatestBuysFilter data-key="EntryLatestBuys" />
|
||||
</template>
|
||||
</RightMenu>
|
||||
<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>
|
||||
<QPage class="column items-center q-pa-md">
|
||||
<QTable
|
||||
:rows="rows"
|
||||
|
|
|
@ -184,13 +184,6 @@ const suppliersOptions = ref([]);
|
|||
@click="removeTag(index, params, searchFn)"
|
||||
/>
|
||||
</QItem>
|
||||
<QItem class="q-mt-lg">
|
||||
<QIcon
|
||||
name="add_circle"
|
||||
class="filter-icon"
|
||||
@click="tagValues.push({})"
|
||||
/>
|
||||
</QItem>
|
||||
</template>
|
||||
</ItemsFilterPanel>
|
||||
</template>
|
||||
|
|
|
@ -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: {
|
||||
|
@ -127,8 +128,14 @@ const ticketColumns = ref([
|
|||
<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 +160,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 +181,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,7 +1,8 @@
|
|||
<script setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import { onBeforeMount, computed, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { Notify } from 'quasar';
|
||||
import axios from 'axios';
|
||||
import VnPaginate from 'components/ui/VnPaginate.vue';
|
||||
import { useSession } from 'src/composables/useSession';
|
||||
import { toDate } from 'filters/index';
|
||||
|
@ -29,7 +30,6 @@ const columns = computed(() => [
|
|||
field: (row) => row.hasCmrDms,
|
||||
align: 'center',
|
||||
sortable: true,
|
||||
headerStyle: 'padding-left: 35px',
|
||||
},
|
||||
{
|
||||
name: 'ticketFk',
|
||||
|
@ -62,7 +62,6 @@ const columns = computed(() => [
|
|||
field: (row) => toDate(row.shipped),
|
||||
align: 'center',
|
||||
sortable: true,
|
||||
headerStyle: 'padding-left: 33px',
|
||||
},
|
||||
{
|
||||
name: 'warehouseFk',
|
||||
|
@ -77,6 +76,11 @@ const columns = computed(() => [
|
|||
field: (row) => row.cmrFk,
|
||||
},
|
||||
]);
|
||||
|
||||
onBeforeMount(async () => {
|
||||
const { data } = await axios.get('Warehouses');
|
||||
warehouses.value = data;
|
||||
});
|
||||
function getApiUrl() {
|
||||
return new URL(window.location).origin;
|
||||
}
|
||||
|
@ -105,13 +109,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"
|
||||
|
@ -187,7 +185,6 @@ function downloadPdfs() {
|
|||
</QPageSticky>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.list {
|
||||
padding-top: 15px;
|
||||
|
@ -204,4 +201,10 @@ function downloadPdfs() {
|
|||
#false {
|
||||
background-color: $negative;
|
||||
}
|
||||
:deep(.q-table th) {
|
||||
max-width: 80px;
|
||||
}
|
||||
:deep(.q-table th:nth-child(3)) {
|
||||
max-width: 100px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -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,51 @@
|
|||
<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 +120,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 +133,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 +141,7 @@ const cloneRoutes = () => {
|
|||
});
|
||||
refreshKey.value++;
|
||||
startingDate.value = null;
|
||||
paginate.value.fetch();
|
||||
};
|
||||
|
||||
const showRouteReport = () => {
|
||||
|
@ -154,15 +161,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 +184,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 +213,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 +235,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 +263,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 +276,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 +506,6 @@ en:
|
|||
hourStarted: Started hour
|
||||
hourFinished: Finished hour
|
||||
es:
|
||||
ID: ID
|
||||
Worker: Trabajador
|
||||
Agency: Agencia
|
||||
Vehicle: Vehículo
|
||||
|
@ -521,4 +526,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>
|
||||
|
|
|
@ -129,7 +129,7 @@ function confirmRemove() {
|
|||
}
|
||||
|
||||
function navigateToRoadmapSummary(event, row) {
|
||||
router.push({ name: 'RoadmapSummary', params: { id: row.id } });
|
||||
router.push({ name: 'RoadmapSummary', params: { id: 1 } });
|
||||
}
|
||||
</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++;
|
||||
};
|
||||
|
||||
|
|
|
@ -17,6 +17,10 @@ const $props = defineProps({
|
|||
required: false,
|
||||
default: null,
|
||||
},
|
||||
summary: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
|
||||
const route = useRoute();
|
||||
|
@ -106,6 +110,7 @@ const getEntryQueryParams = (supplier) => {
|
|||
:filter="filter"
|
||||
@on-fetch="setData"
|
||||
data-key="supplier"
|
||||
:summary="$props.summary"
|
||||
>
|
||||
<template #header-extra-action>
|
||||
<QBtn
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<script setup>
|
||||
import SupplierDescriptor from './SupplierDescriptor.vue';
|
||||
import SupplierSummary from './SupplierSummary.vue';
|
||||
|
||||
const $props = defineProps({
|
||||
id: {
|
||||
|
@ -11,6 +12,6 @@ const $props = defineProps({
|
|||
|
||||
<template>
|
||||
<QPopupProxy>
|
||||
<SupplierDescriptor v-if="$props.id" :id="$props.id" />
|
||||
<SupplierDescriptor v-if="$props.id" :id="$props.id" :summary="SupplierSummary" />
|
||||
</QPopupProxy>
|
||||
</template>
|
||||
|
|
|
@ -24,7 +24,7 @@ const agenciesOptions = ref([]);
|
|||
<FormModel
|
||||
:url="`Travels/${route.params.id}`"
|
||||
:url-update="`Travels/${route.params.id}`"
|
||||
model="travel"
|
||||
model="Travel"
|
||||
auto-load
|
||||
>
|
||||
<template #form="{ data }">
|
||||
|
|
|
@ -1,7 +1,40 @@
|
|||
<script setup>
|
||||
import VnCard from 'components/common/VnCard.vue';
|
||||
import TravelDescriptor from './TravelDescriptor.vue';
|
||||
|
||||
const filter = {
|
||||
fields: [
|
||||
'id',
|
||||
'ref',
|
||||
'shipped',
|
||||
'landed',
|
||||
'totalEntries',
|
||||
'warehouseInFk',
|
||||
'warehouseOutFk',
|
||||
'cargoSupplierFk',
|
||||
'agencyModeFk',
|
||||
],
|
||||
include: [
|
||||
{
|
||||
relation: 'warehouseIn',
|
||||
scope: {
|
||||
fields: ['name'],
|
||||
},
|
||||
},
|
||||
{
|
||||
relation: 'warehouseOut',
|
||||
scope: {
|
||||
fields: ['name'],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<VnCard data-key="Travel" base-url="Travels" :descriptor="TravelDescriptor" />
|
||||
<VnCard
|
||||
data-key="Travel"
|
||||
:filter="filter"
|
||||
base-url="Travels"
|
||||
:descriptor="TravelDescriptor"
|
||||
/>
|
||||
</template>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { computed } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
|
@ -7,7 +7,6 @@ import CardDescriptor from 'components/ui/CardDescriptor.vue';
|
|||
import VnLv from 'src/components/ui/VnLv.vue';
|
||||
import TravelDescriptorMenuItems from './TravelDescriptorMenuItems.vue';
|
||||
|
||||
import useCardDescription from 'src/composables/useCardDescription';
|
||||
import { toDate } from 'src/filters';
|
||||
|
||||
const $props = defineProps({
|
||||
|
@ -52,23 +51,15 @@ const filter = {
|
|||
const entityId = computed(() => {
|
||||
return $props.id || route.params.id;
|
||||
});
|
||||
|
||||
const data = ref(useCardDescription());
|
||||
|
||||
const setData = (entity) => {
|
||||
data.value = useCardDescription(entity.ref, entity.id);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<CardDescriptor
|
||||
module="Travel"
|
||||
:url="`Travels/${entityId}`"
|
||||
:title="data.title"
|
||||
:subtitle="data.subtitle"
|
||||
title="ref"
|
||||
:filter="filter"
|
||||
@on-fetch="setData"
|
||||
data-key="travelData"
|
||||
data-key="Travel"
|
||||
>
|
||||
<template #header-extra-action>
|
||||
<QBtn
|
||||
|
|
|
@ -32,10 +32,11 @@ const cloneTravel = () => {
|
|||
redirectToCreateView(stringifiedTravelData);
|
||||
};
|
||||
|
||||
const cloneTravelWithEntries = () => {
|
||||
const cloneTravelWithEntries = async () => {
|
||||
try {
|
||||
axios.post(`Travels/${$props.travel.id}/cloneWithEntries`);
|
||||
const { data } = await axios.post(`Travels/${$props.travel.id}/cloneWithEntries`);
|
||||
notify('globals.dataSaved', 'positive');
|
||||
router.push({ name: 'TravelBasicData', params: { id: data.id } });
|
||||
} catch (err) {
|
||||
console.err('Error cloning travel with entries');
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@ import VnLv from 'src/components/ui/VnLv.vue';
|
|||
import VnTitle from 'src/components/common/VnTitle.vue';
|
||||
import EntryDescriptorProxy from 'src/pages/Entry/Card/EntryDescriptorProxy.vue';
|
||||
import FetchData from 'src/components/FetchData.vue';
|
||||
import TravelDescriptorMenuItems from './TravelDescriptorMenuItems.vue';
|
||||
|
||||
import { toDate, toCurrency } from 'src/filters';
|
||||
import axios from 'axios';
|
||||
|
@ -222,6 +221,8 @@ async function setTravelData(travelData) {
|
|||
console.error(`Error setting travel data`, err);
|
||||
}
|
||||
}
|
||||
|
||||
const getLink = (param) => `#/travel/${entityId.value}/${param}`;
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -240,21 +241,15 @@ async function setTravelData(travelData) {
|
|||
<template #header>
|
||||
<span>{{ travel.ref }} - {{ travel.id }}</span>
|
||||
</template>
|
||||
<template #header-right>
|
||||
<QBtn color="white" dense flat icon="more_vert" round size="md">
|
||||
<QTooltip>
|
||||
{{ t('components.cardDescriptor.moreOptions') }}
|
||||
</QTooltip>
|
||||
<QMenu>
|
||||
<QList>
|
||||
<TravelDescriptorMenuItems :travel="travel" />
|
||||
</QList>
|
||||
</QMenu>
|
||||
</QBtn>
|
||||
</template>
|
||||
|
||||
<template #body>
|
||||
<QCard class="vn-one">
|
||||
<QCardSection class="q-pa-none">
|
||||
<VnTitle
|
||||
:url="getLink('basic-data')"
|
||||
:text="t('travel.pageTitles.basicData')"
|
||||
/>
|
||||
</QCardSection>
|
||||
<VnLv :label="t('globals.shipped')" :value="toDate(travel.shipped)" />
|
||||
<VnLv
|
||||
:label="t('globals.wareHouseOut')"
|
||||
|
@ -268,6 +263,12 @@ async function setTravelData(travelData) {
|
|||
/>
|
||||
</QCard>
|
||||
<QCard class="vn-one">
|
||||
<QCardSection class="q-pa-none">
|
||||
<VnTitle
|
||||
:url="getLink('basic-data')"
|
||||
:text="t('travel.pageTitles.basicData')"
|
||||
/>
|
||||
</QCardSection>
|
||||
<VnLv :label="t('globals.landed')" :value="toDate(travel.landed)" />
|
||||
<VnLv
|
||||
:label="t('globals.wareHouseIn')"
|
||||
|
@ -281,12 +282,18 @@ async function setTravelData(travelData) {
|
|||
/>
|
||||
</QCard>
|
||||
<QCard class="vn-one">
|
||||
<QCardSection class="q-pa-none">
|
||||
<VnTitle
|
||||
:url="getLink('basic-data')"
|
||||
:text="t('travel.pageTitles.basicData')"
|
||||
/>
|
||||
</QCardSection>
|
||||
<VnLv :label="t('globals.agency')" :value="travel.agency?.name" />
|
||||
<VnLv :label="t('globals.reference')" :value="travel.ref" />
|
||||
<VnLv label="m³" :value="travel.m3" />
|
||||
<VnLv :label="t('globals.totalEntries')" :value="travel.totalEntries" />
|
||||
</QCard>
|
||||
<QCard class="full-width" v-if="entriesTableRows.length > 0">
|
||||
<QCard class="full-width">
|
||||
<VnTitle :text="t('travel.summary.entries')" />
|
||||
<QTable
|
||||
:rows="entriesTableRows"
|
||||
|
@ -301,13 +308,15 @@ async function setTravelData(travelData) {
|
|||
</QTh>
|
||||
</QTr>
|
||||
</template>
|
||||
<template #body-cell-isConfirmed="{ col, value }">
|
||||
<template #body-cell-isConfirmed="{ col, row }">
|
||||
<QTd>
|
||||
<QIcon
|
||||
<QCheckbox
|
||||
v-if="col.name === 'isConfirmed'"
|
||||
:name="value ? 'check' : 'close'"
|
||||
:color="value ? 'positive' : 'negative'"
|
||||
size="sm"
|
||||
:label="t('travel.summary.received')"
|
||||
:true-value="1"
|
||||
:false-value="0"
|
||||
v-model="row[col.name]"
|
||||
:disable="true"
|
||||
/>
|
||||
</QTd>
|
||||
</template>
|
||||
|
|
|
@ -53,6 +53,7 @@ const draggedRowIndex = ref(null);
|
|||
const targetRowIndex = ref(null);
|
||||
const entryRowIndex = ref(null);
|
||||
const draggedEntry = ref(null);
|
||||
const travelKgPercentages = ref([]);
|
||||
|
||||
const tableColumnComponents = {
|
||||
id: {
|
||||
|
@ -88,6 +89,10 @@ const tableColumnComponents = {
|
|||
component: 'span',
|
||||
attrs: {},
|
||||
},
|
||||
percentage: {
|
||||
component: 'span',
|
||||
attrs: {},
|
||||
},
|
||||
kg: {
|
||||
component: VnInput,
|
||||
attrs: { dense: true, type: 'number', min: 0, class: 'input-number' },
|
||||
|
@ -179,6 +184,14 @@ const columns = computed(() => [
|
|||
showValue: true,
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
label: '%',
|
||||
field: '',
|
||||
name: 'percentage',
|
||||
align: 'center',
|
||||
showValue: false,
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
label: t('kg'),
|
||||
field: 'kg',
|
||||
|
@ -278,6 +291,8 @@ const saveFieldValue = async (val, field, index) => {
|
|||
await axios.patch(`Travels/${id}`, params);
|
||||
// Actualizar la copia de los datos originales con el nuevo valor
|
||||
originalRowDataCopy.value[index][field] = val;
|
||||
|
||||
await arrayData.fetch({ append: false });
|
||||
} catch (err) {
|
||||
console.error('Error updating travel');
|
||||
}
|
||||
|
@ -302,6 +317,11 @@ onMounted(async () => {
|
|||
|
||||
landedTo.value.setDate(landedTo.value.getDate() + 7);
|
||||
landedTo.value.setHours(23, 59, 59, 59);
|
||||
const { data } = await axios.get('TravelKgPercentages', {
|
||||
params: { filter: JSON.stringify({ order: 'value DESC' }) },
|
||||
});
|
||||
|
||||
travelKgPercentages.value = data;
|
||||
await getData();
|
||||
});
|
||||
|
||||
|
@ -419,6 +439,11 @@ const handleDragScroll = (event) => {
|
|||
stopScroll();
|
||||
}
|
||||
};
|
||||
|
||||
const getColor = (percentage) => {
|
||||
for (const { value, className } of travelKgPercentages.value)
|
||||
if (percentage > value) return className;
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -460,7 +485,7 @@ const handleDragScroll = (event) => {
|
|||
<template #body="props">
|
||||
<QTr
|
||||
:props="props"
|
||||
class="cursor-pointer bg-vn-primary-row"
|
||||
class="cursor-pointer bg-travel"
|
||||
@click="navigateToTravelId(props.row.id)"
|
||||
@dragenter="handleDragEnter($event, props.rowIndex)"
|
||||
@dragover.prevent
|
||||
|
@ -494,18 +519,32 @@ const handleDragScroll = (event) => {
|
|||
: {}
|
||||
"
|
||||
>
|
||||
<template v-if="col.showValue">
|
||||
<span
|
||||
:class="[
|
||||
'text-left',
|
||||
{
|
||||
'supplier-name':
|
||||
col.name === 'cargoSupplierNickname',
|
||||
},
|
||||
]"
|
||||
>{{ col.value }}</span
|
||||
>
|
||||
</template>
|
||||
<QChip
|
||||
v-if="col.name === 'percentage'"
|
||||
:label="
|
||||
props.row.percentageKg
|
||||
? `${props.row.percentageKg}%`
|
||||
: '-'
|
||||
"
|
||||
class="text-left q-py-xs q-px-sm"
|
||||
:color="getColor(props.row.percentageKg)"
|
||||
/>
|
||||
<span
|
||||
v-else-if="col.showValue"
|
||||
:class="[
|
||||
'text-left',
|
||||
{
|
||||
'supplier-name':
|
||||
col.name === 'cargoSupplierNickname',
|
||||
},
|
||||
{
|
||||
link: ['id', 'cargoSupplierNickname'].includes(
|
||||
col.name
|
||||
),
|
||||
},
|
||||
]"
|
||||
v-text="col.value"
|
||||
/>
|
||||
<!-- Main Row Descriptors -->
|
||||
<TravelDescriptorProxy
|
||||
v-if="col.name === 'id'"
|
||||
|
@ -539,11 +578,11 @@ const handleDragScroll = (event) => {
|
|||
}"
|
||||
>
|
||||
<QTd>
|
||||
<QBtn flat color="primary">{{ entry.id }} </QBtn>
|
||||
<QBtn flat class="link">{{ entry.id }} </QBtn>
|
||||
<EntryDescriptorProxy :id="entry.id" />
|
||||
</QTd>
|
||||
<QTd>
|
||||
<QBtn flat color="primary" dense>{{ entry.supplierName }}</QBtn>
|
||||
<QBtn flat class="link" dense>{{ entry.supplierName }}</QBtn>
|
||||
<SupplierDescriptorProxy :id="entry.supplierFk" />
|
||||
</QTd>
|
||||
<QTd />
|
||||
|
@ -556,6 +595,7 @@ const handleDragScroll = (event) => {
|
|||
<QTd>
|
||||
<span>{{ entry.stickers }}</span>
|
||||
</QTd>
|
||||
<QTd />
|
||||
<QTd></QTd>
|
||||
<QTd>
|
||||
<span>{{ entry.loadedkg }}</span>
|
||||
|
@ -574,10 +614,23 @@ const handleDragScroll = (event) => {
|
|||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.q-chip {
|
||||
color: var(--vn-text-color);
|
||||
}
|
||||
|
||||
:deep(.q-table) {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.q-td :deep(input) {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.bg-travel {
|
||||
background-color: var(--vn-page-color);
|
||||
border-bottom: 2px solid $primary;
|
||||
}
|
||||
|
||||
.dashed-border {
|
||||
&.--left {
|
||||
border-left: 1px dashed #ccc;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script setup>
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { reactive, ref, onBeforeMount } from 'vue';
|
||||
import { ref, onBeforeMount } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
|
@ -15,29 +15,19 @@ const { t } = useI18n();
|
|||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const newTravelForm = reactive({
|
||||
ref: null,
|
||||
agencyModeFk: null,
|
||||
shipped: null,
|
||||
landed: null,
|
||||
warehouseOutFk: null,
|
||||
warehouseInFk: null,
|
||||
});
|
||||
|
||||
const agenciesOptions = ref([]);
|
||||
const warehousesOptions = ref([]);
|
||||
const viewAction = ref();
|
||||
|
||||
const newTravelForm = ref({});
|
||||
onBeforeMount(() => {
|
||||
// Esto nos permite decirle a FormModel si queremos observar los cambios o no
|
||||
// Ya que si queremos clonar queremos que nos permita guardar inmediatamente sin realizar cambios en el form
|
||||
viewAction.value = route.query.travelData ? 'clone' : 'create';
|
||||
|
||||
if (route.query.travelData) {
|
||||
const travelData = JSON.parse(route.query.travelData);
|
||||
for (let key in newTravelForm) {
|
||||
newTravelForm[key] = travelData[key];
|
||||
}
|
||||
|
||||
newTravelForm.value = { ...newTravelForm.value, ...travelData };
|
||||
delete newTravelForm.value.id;
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -60,8 +50,8 @@ const redirectToTravelBasicData = (_, { id }) => {
|
|||
<QPage>
|
||||
<VnSubToolbar />
|
||||
<FormModel
|
||||
url-update="Travels"
|
||||
model="travel"
|
||||
url-create="Travels"
|
||||
model="travelCreate"
|
||||
:form-initial-data="newTravelForm"
|
||||
:observe-form-changes="viewAction === 'create'"
|
||||
@on-data-saved="redirectToTravelBasicData"
|
||||
|
|
|
@ -21,7 +21,14 @@ export default {
|
|||
'AccountAcls',
|
||||
'AccountConnections',
|
||||
],
|
||||
card: [],
|
||||
card: [
|
||||
'AccountBasicData',
|
||||
'AccountInheritedRoles',
|
||||
'AccountMailForwarding',
|
||||
'AccountMailAlias',
|
||||
'AccountPrivileges',
|
||||
'AccountLog',
|
||||
],
|
||||
},
|
||||
children: [
|
||||
{
|
||||
|
@ -112,5 +119,81 @@ export default {
|
|||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'AccountCard',
|
||||
path: ':id',
|
||||
component: () => import('src/pages/Account/Card/AccountCard.vue'),
|
||||
redirect: { name: 'AccountSummary' },
|
||||
children: [
|
||||
{
|
||||
name: 'AccountSummary',
|
||||
path: 'summary',
|
||||
meta: {
|
||||
title: 'summary',
|
||||
icon: 'launch',
|
||||
},
|
||||
component: () => import('src/pages/Account/Card/AccountSummary.vue'),
|
||||
},
|
||||
{
|
||||
name: 'AccountBasicData',
|
||||
path: 'basic-data',
|
||||
meta: {
|
||||
title: 'basicData',
|
||||
icon: 'vn:settings',
|
||||
},
|
||||
component: () =>
|
||||
import('src/pages/Account/Card/AccountBasicData.vue'),
|
||||
},
|
||||
{
|
||||
name: 'AccountInheritedRoles',
|
||||
path: 'inherited-roles',
|
||||
meta: {
|
||||
title: 'inheritedRoles',
|
||||
icon: 'group',
|
||||
},
|
||||
component: () =>
|
||||
import('src/pages/Account/Card/AccountInheritedRoles.vue'),
|
||||
},
|
||||
{
|
||||
name: 'AccountMailForwarding',
|
||||
path: 'mail-forwarding',
|
||||
meta: {
|
||||
title: 'mailForwarding',
|
||||
icon: 'forward',
|
||||
},
|
||||
component: () =>
|
||||
import('src/pages/Account/Card/AccountMailForwarding.vue'),
|
||||
},
|
||||
{
|
||||
name: 'AccountMailAlias',
|
||||
path: 'mail-alias',
|
||||
meta: {
|
||||
title: 'mailAlias',
|
||||
icon: 'email',
|
||||
},
|
||||
component: () =>
|
||||
import('src/pages/Account/Card/AccountMailAlias.vue'),
|
||||
},
|
||||
{
|
||||
name: 'AccountPrivileges',
|
||||
path: 'privileges',
|
||||
meta: {
|
||||
title: 'privileges',
|
||||
icon: 'badge',
|
||||
},
|
||||
component: () =>
|
||||
import('src/pages/Account/Card/AccountPrivileges.vue'),
|
||||
},
|
||||
{
|
||||
name: 'AccountLog',
|
||||
path: 'log',
|
||||
meta: {
|
||||
title: 'log',
|
||||
icon: 'history',
|
||||
},
|
||||
component: () => import('src/pages/Account/Card/AccountLog.vue'),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
|
@ -2,8 +2,7 @@ const locationOptions = '[role="listbox"] > div.q-virtual-scroll__content > .q-i
|
|||
describe('VnLocation', () => {
|
||||
const dialogInputs = '.q-dialog label input';
|
||||
describe('Worker Create', () => {
|
||||
const inputLocation =
|
||||
'.q-form .q-card > :nth-child(3) > .q-field > .q-field__inner > .q-field__control > .q-field__control-container';
|
||||
const inputLocation = '.q-form input[aria-label="Location"]';
|
||||
beforeEach(() => {
|
||||
cy.viewport(1280, 720);
|
||||
cy.login('developer');
|
||||
|
@ -25,9 +24,6 @@ describe('VnLocation', () => {
|
|||
cy.get(inputLocation).clear();
|
||||
cy.get(inputLocation).type('ecuador');
|
||||
cy.get(locationOptions).should('have.length.at.least', 1);
|
||||
cy.get(
|
||||
'.q-form .q-card > :nth-child(3) > .q-field > .q-field__inner > .q-field__control > :nth-child(3) > .q-icon'
|
||||
).click();
|
||||
});
|
||||
});
|
||||
describe('Fiscal-data', () => {
|
||||
|
@ -38,9 +34,7 @@ describe('VnLocation', () => {
|
|||
cy.waitForElement('.q-form');
|
||||
});
|
||||
it('Create postCode', function () {
|
||||
cy.get(
|
||||
':nth-child(6) > .q-field > .q-field__inner > .q-field__control > :nth-child(2) > .q-icon'
|
||||
).click();
|
||||
cy.get('.q-form > .q-card > .vn-row:nth-child(6) .--add-icon').click();
|
||||
cy.get('.q-card > h1').should('have.text', 'New postcode');
|
||||
cy.get(dialogInputs).eq(0).clear('12');
|
||||
cy.get(dialogInputs).eq(0).type('1234453');
|
||||
|
|
|
@ -10,7 +10,6 @@ describe('WorkerPda', () => {
|
|||
it('assign pda', () => {
|
||||
cy.get('.q-page-sticky > div > .q-btn > .q-btn__content > .q-icon').click();
|
||||
cy.get(deviceProductionField).type('{downArrow}{enter}');
|
||||
cy.get('.vn-row > #simSerialNumber').type('123{enter}');
|
||||
cy.get('.q-notification__message').should('have.text', 'Data created');
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in New Issue