forked from verdnatura/salix-front
Merge branch 'dev' into 6896-EndOrderMigration
This commit is contained in:
commit
a61196b04d
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "salix-front",
|
||||
"version": "24.26.2",
|
||||
"version": "24.28.1",
|
||||
"description": "Salix frontend",
|
||||
"productName": "Salix",
|
||||
"author": "Verdnatura",
|
||||
|
|
|
@ -246,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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -306,10 +306,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,13 +172,13 @@ const navigateToticketSummary = (id) => {
|
|||
auto-load
|
||||
url="Tickets"
|
||||
/>
|
||||
<QCard class="vn-one q-py-sm flex justify-between">
|
||||
<QTable
|
||||
:columns="columns"
|
||||
:pagination="{ rowsPerPage: 12 }"
|
||||
:rows="rows"
|
||||
class="full-width q-mt-md"
|
||||
class="full-width"
|
||||
row-key="id"
|
||||
v-if="rows?.length"
|
||||
>
|
||||
<template #body-cell="props">
|
||||
<QTd :props="props" @click="navigateToticketSummary(props.row.id)">
|
||||
|
@ -185,19 +186,19 @@ const navigateToticketSummary = (id) => {
|
|||
<component
|
||||
:is="tableColumnComponents[props.col.name].component"
|
||||
@click="tableColumnComponents[props.col.name].event(props)"
|
||||
class="rounded-borders q-pa-sm"
|
||||
class="rounded-borders"
|
||||
v-bind="tableColumnComponents[props.col.name].props(props)"
|
||||
>
|
||||
<template
|
||||
v-if="
|
||||
props.col.name === 'id' ||
|
||||
props.col.name === 'nickname' ||
|
||||
props.col.name === 'agency' ||
|
||||
<template v-if="!commonColumns(props.col.name)">
|
||||
<span
|
||||
:class="{
|
||||
link:
|
||||
props.col.name === 'route' ||
|
||||
props.col.name === 'packages'
|
||||
"
|
||||
props.col.name === 'nickname',
|
||||
}"
|
||||
>
|
||||
{{ props.value }}
|
||||
</span>
|
||||
</template>
|
||||
<template v-if="props.col.name === 'date'">
|
||||
<QBadge class="q-pa-sm" color="warning">
|
||||
|
@ -232,6 +233,7 @@ const navigateToticketSummary = (id) => {
|
|||
</QTd>
|
||||
</template>
|
||||
</QTable>
|
||||
</QCard>
|
||||
</template>
|
||||
|
||||
<i18n>
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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'),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
|
@ -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