Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix-front into 7283-itemSectionsMigration

This commit is contained in:
Jorge Penadés 2024-08-09 12:12:59 +02:00
commit 7a2170e7c3
44 changed files with 6949 additions and 9266 deletions

33
.husky/addReferenceTag.js Normal file
View File

@ -0,0 +1,33 @@
const fs = require('fs');
const path = require('path');
function getCurrentBranchName(p = process.cwd()) {
if (!fs.existsSync(p)) return false;
const gitHeadPath = path.join(p, '.git', 'HEAD');
if (!fs.existsSync(gitHeadPath))
return getCurrentBranchName(path.resolve(p, '..'));
const headContent = fs.readFileSync(gitHeadPath, 'utf-8');
return headContent.trim().split('/')[2];
}
const branchName = getCurrentBranchName();
if (branchName) {
const msgPath = `.git/COMMIT_EDITMSG`;
const msg = fs.readFileSync(msgPath, 'utf-8');
const reference = branchName.match(/^\d+/);
const referenceTag = `refs #${reference}`;
if (!msg.includes(referenceTag) && reference) {
const splitedMsg = msg.split(':');
if (splitedMsg.length > 1) {
const finalMsg = splitedMsg[0] + ': ' + referenceTag + splitedMsg.slice(1).join(':');
fs.writeFileSync(msgPath, finalMsg);
}
}
}

8
.husky/commit-msg Executable file
View File

@ -0,0 +1,8 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
echo "Running husky commit-msg hook"
npx --no-install commitlint --edit
echo "Adding reference tag to commit message"
node .husky/addReferenceTag.js

1
commitlint.config.js Normal file
View File

@ -0,0 +1 @@
module.exports = { extends: ['@commitlint/config-conventional'] };

View File

@ -13,7 +13,10 @@
"test:e2e:ci": "cd ../salix && gulp docker && cd ../salix-front && cypress run", "test:e2e:ci": "cd ../salix && gulp docker && cd ../salix-front && cypress run",
"test": "echo \"See package.json => scripts for available tests.\" && exit 0", "test": "echo \"See package.json => scripts for available tests.\" && exit 0",
"test:unit": "vitest", "test:unit": "vitest",
"test:unit:ci": "vitest run" "test:unit:ci": "vitest run",
"commitlint": "commitlint --edit",
"prepare": "npx husky install",
"addReferenceTag": "node .husky/addReferenceTag.js"
}, },
"dependencies": { "dependencies": {
"@quasar/cli": "^2.3.0", "@quasar/cli": "^2.3.0",
@ -29,6 +32,8 @@
"vue-router": "^4.2.1" "vue-router": "^4.2.1"
}, },
"devDependencies": { "devDependencies": {
"@commitlint/cli": "^19.2.1",
"@commitlint/config-conventional": "^19.1.0",
"@intlify/unplugin-vue-i18n": "^0.8.1", "@intlify/unplugin-vue-i18n": "^0.8.1",
"@pinia/testing": "^0.1.2", "@pinia/testing": "^0.1.2",
"@quasar/app-vite": "^1.7.3", "@quasar/app-vite": "^1.7.3",
@ -41,6 +46,7 @@
"eslint-config-prettier": "^8.8.0", "eslint-config-prettier": "^8.8.0",
"eslint-plugin-cypress": "^2.13.3", "eslint-plugin-cypress": "^2.13.3",
"eslint-plugin-vue": "^9.14.1", "eslint-plugin-vue": "^9.14.1",
"husky": "^8.0.0",
"postcss": "^8.4.23", "postcss": "^8.4.23",
"prettier": "^2.8.8", "prettier": "^2.8.8",
"vitest": "^0.31.1" "vitest": "^0.31.1"

File diff suppressed because it is too large Load Diff

View File

@ -100,7 +100,6 @@ const isResetting = ref(false);
const hasChanges = ref(!$props.observeFormChanges); const hasChanges = ref(!$props.observeFormChanges);
const originalData = ref({}); const originalData = ref({});
const formData = computed(() => state.get(modelValue)); const formData = computed(() => state.get(modelValue));
const formUrl = computed(() => $props.url);
const defaultButtons = computed(() => ({ const defaultButtons = computed(() => ({
save: { save: {
color: 'primary', color: 'primary',
@ -148,11 +147,14 @@ if (!$props.url)
(val) => updateAndEmit('onFetch', val) (val) => updateAndEmit('onFetch', val)
); );
watch(formUrl, async () => { watch(
() => [$props.url, $props.filter],
async () => {
originalData.value = null; originalData.value = null;
reset(); reset();
await fetch(); await fetch();
}); }
);
onBeforeRouteLeave((to, from, next) => { onBeforeRouteLeave((to, from, next) => {
if (hasChanges.value && $props.observeFormChanges) if (hasChanges.value && $props.observeFormChanges)

View File

@ -46,7 +46,7 @@ const stateStore = useStateStore();
</div> </div>
</Teleport> </Teleport>
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above> <QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
<QScrollArea class="fit text-grey-8"> <QScrollArea class="fit">
<div id="right-panel"></div> <div id="right-panel"></div>
<slot v-if="!hasContent" name="right-panel" /> <slot v-if="!hasContent" name="right-panel" />
</QScrollArea> </QScrollArea>

View File

@ -17,13 +17,7 @@ const props = defineProps({
descriptor: { type: Object, required: true }, descriptor: { type: Object, required: true },
filterPanel: { type: Object, default: undefined }, filterPanel: { type: Object, default: undefined },
searchDataKey: { type: String, default: undefined }, searchDataKey: { type: String, default: undefined },
searchUrl: { type: String, default: undefined }, searchbarProps: { type: Object, default: undefined },
searchbarLabel: { type: String, default: '' },
searchbarInfo: { type: String, default: '' },
searchCustomRouteRedirect: { type: String, default: undefined },
searchRedirect: { type: Boolean, default: true },
searchMakeFetch: { type: Boolean, default: true },
searchUrlQuery: { type: String, default: undefined },
}); });
const stateStore = useStateStore(); const stateStore = useStateStore();
@ -66,15 +60,7 @@ if (props.baseUrl) {
</QScrollArea> </QScrollArea>
</QDrawer> </QDrawer>
<slot name="searchbar" v-if="props.searchDataKey"> <slot name="searchbar" v-if="props.searchDataKey">
<VnSearchbar <VnSearchbar :data-key="props.searchDataKey" v-bind="props.searchbarProps" />
:data-key="props.searchDataKey"
:url="props.searchUrl"
:label="props.searchbarLabel"
:info="props.searchbarInfo"
:search-url="props.searchUrlQuery"
:custom-route-redirect-name="searchCustomRouteRedirect"
:redirect="searchRedirect"
/>
</slot> </slot>
<slot v-else name="searchbar" /> <slot v-else name="searchbar" />
<RightMenu> <RightMenu>

View File

@ -73,8 +73,7 @@ const containerClasses = computed(() => {
.q-calendar-month__head--workweek, .q-calendar-month__head--workweek,
.q-calendar-month__head--weekday, .q-calendar-month__head--weekday,
// .q-calendar-month__workweek.q-past-day, // .q-calendar-month__workweek.q-past-day,
.q-calendar-month__week :nth-child(6), .q-calendar-month__week :nth-child(n+6):nth-child(-n+7) {
:nth-child(7) {
color: var(--vn-label-color); color: var(--vn-label-color);
} }

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import { onBeforeMount } from 'vue'; import { watch, computed } from 'vue';
import { date } from 'quasar'; import { date } from 'quasar';
import VnPaginate from 'src/components/ui/VnPaginate.vue'; import VnPaginate from 'src/components/ui/VnPaginate.vue';
import VnAvatar from '../ui/VnAvatar.vue'; import VnAvatar from '../ui/VnAvatar.vue';
@ -10,7 +10,8 @@ const $props = defineProps({
where: { type: Object, default: () => {} }, where: { type: Object, default: () => {} },
}); });
const filter = { const filter = computed(() => {
return {
fields: ['smsFk'], fields: ['smsFk'],
include: { include: {
relation: 'sms', relation: 'sms',
@ -32,9 +33,9 @@ const filter = {
}, },
}, },
}, },
...{ where: $props.where },
}; };
});
onBeforeMount(() => (filter.where = $props.where));
function formatNumber(number) { function formatNumber(number) {
if (number.length <= 10) return number; if (number.length <= 10) return number;

View File

@ -3,12 +3,14 @@ import { useRole } from './useRole';
import { useAcl } from './useAcl'; import { useAcl } from './useAcl';
import { useUserConfig } from './useUserConfig'; import { useUserConfig } from './useUserConfig';
import axios from 'axios'; import axios from 'axios';
import { useRouter } from 'vue-router';
import useNotify from './useNotify'; import useNotify from './useNotify';
import { useTokenConfig } from './useTokenConfig'; import { useTokenConfig } from './useTokenConfig';
const TOKEN_MULTIMEDIA = 'tokenMultimedia'; const TOKEN_MULTIMEDIA = 'tokenMultimedia';
const TOKEN = 'token'; const TOKEN = 'token';
export function useSession() { export function useSession() {
const router = useRouter();
const { notify } = useNotify(); const { notify } = useNotify();
let isCheckingToken = false; let isCheckingToken = false;
let intervalId = null; let intervalId = null;
@ -102,6 +104,31 @@ export function useSession() {
startInterval(); startInterval();
} }
async function setLogin(data) {
const {
data: { multimediaToken },
} = await axios.get('VnUsers/ShareToken', {
headers: { Authorization: data.token },
});
if (!multimediaToken) return;
await login({
...data,
created: Date.now(),
tokenMultimedia: multimediaToken.id,
});
notify('login.loginSuccess', 'positive');
const currentRoute = router.currentRoute.value;
if (currentRoute.query?.redirect) {
router.push(currentRoute.query.redirect);
} else {
router.push({ name: 'Dashboard' });
}
}
function isLoggedIn() { function isLoggedIn() {
const localToken = localStorage.getItem(TOKEN); const localToken = localStorage.getItem(TOKEN);
const sessionToken = sessionStorage.getItem(TOKEN); const sessionToken = sessionStorage.getItem(TOKEN);
@ -163,6 +190,7 @@ export function useSession() {
setToken, setToken,
destroy, destroy,
login, login,
setLogin,
isLoggedIn, isLoggedIn,
checkValidity, checkValidity,
setSession, setSession,

View File

@ -90,6 +90,9 @@ globals:
salesPerson: SalesPerson salesPerson: SalesPerson
send: Send send: Send
code: Code code: Code
since: Since
from: From
to: To
pageTitles: pageTitles:
logIn: Login logIn: Login
summary: Summary summary: Summary

View File

@ -90,6 +90,9 @@ globals:
salesPerson: Comercial salesPerson: Comercial
send: Enviar send: Enviar
code: Código code: Código
since: Desde
from: Desde
to: Hasta
pageTitles: pageTitles:
logIn: Inicio de sesión logIn: Inicio de sesión
summary: Resumen summary: Resumen
@ -697,8 +700,6 @@ invoiceOut:
percentageText: '{getPercentage}% {getAddressNumber} de {getNAddresses}' percentageText: '{getPercentage}% {getAddressNumber} de {getNAddresses}'
pdfsNumberText: '{nPdfs} de {totalPdfs} PDFs' pdfsNumberText: '{nPdfs} de {totalPdfs} PDFs'
negativeBases: negativeBases:
from: Desde
to: Hasta
company: Empresa company: Empresa
country: País country: País
clientId: Id cliente clientId: Id cliente
@ -1240,8 +1241,6 @@ components:
# LatestBuysFilter # LatestBuysFilter
salesPersonFk: Comprador salesPersonFk: Comprador
supplierFk: Proveedor supplierFk: Proveedor
from: Desde
to: Hasta
active: Activo active: Activo
visible: Visible visible: Visible
floramondo: Floramondo floramondo: Floramondo

View File

@ -25,9 +25,11 @@ const searchBarDataKeys = {
base-url="MailAliases" base-url="MailAliases"
:descriptor="AliasDescriptor" :descriptor="AliasDescriptor"
:search-data-key="searchBarDataKeys[routeName]" :search-data-key="searchBarDataKeys[routeName]"
:search-custom-route-redirect="customRouteRedirectName" :searchbar-props="{
:search-redirect="!!customRouteRedirectName" redirect: !!customRouteRedirectName,
:searchbar-label="t('mailAlias.search')" customRouteRedirectName,
:searchbar-info="t('mailAlias.searchInfo')" info: t('mailAlias.searchInfo'),
label: t('mailAlias.search'),
}"
/> />
</template> </template>

View File

@ -26,9 +26,11 @@ const searchBarDataKeys = {
data-key="Account" data-key="Account"
:descriptor="AccountDescriptor" :descriptor="AccountDescriptor"
:search-data-key="searchBarDataKeys[routeName]" :search-data-key="searchBarDataKeys[routeName]"
:search-custom-route-redirect="customRouteRedirectName" :searchbar-props="{
:search-redirect="!!customRouteRedirectName" redirect: !!customRouteRedirectName,
:searchbar-label="t('account.search')" customRouteRedirectName,
:searchbar-info="t('account.searchInfo')" label: t('account.search'),
info: t('account.searchInfo'),
}"
/> />
</template> </template>

View File

@ -85,7 +85,7 @@ const fetchMailAliases = async () => {
paginateRef.value.fetch(); paginateRef.value.fetch();
}; };
const getAccountData = async () => { const getAccountData = async (reload = true) => {
loading.value = true; loading.value = true;
hasAccount.value = await fetchAccountExistence(); hasAccount.value = await fetchAccountExistence();
if (!hasAccount.value) { if (!hasAccount.value) {
@ -93,7 +93,7 @@ const getAccountData = async () => {
store.data = []; store.data = [];
return; return;
} }
await fetchMailAliases(); reload && (await fetchMailAliases());
loading.value = false; loading.value = false;
}; };
@ -102,13 +102,11 @@ const openCreateMailAliasForm = () => createMailAliasDialogRef.value.show();
watch( watch(
() => route.params.id, () => route.params.id,
() => { () => {
store.url = urlPath;
store.filter = filter.value;
getAccountData(); getAccountData();
} }
); );
onMounted(async () => await getAccountData()); onMounted(async () => await getAccountData(false));
</script> </script>
<template> <template>

View File

@ -23,9 +23,11 @@ const searchBarDataKeys = {
data-key="Role" data-key="Role"
:descriptor="RoleDescriptor" :descriptor="RoleDescriptor"
:search-data-key="searchBarDataKeys[routeName]" :search-data-key="searchBarDataKeys[routeName]"
:search-custom-route-redirect="customRouteRedirectName" :searchbar-props="{
:search-redirect="!!customRouteRedirectName" redirect: !!customRouteRedirectName,
:searchbar-label="t('role.searchRoles')" customRouteRedirectName,
:searchbar-info="t('role.searchInfo')" label: t('role.searchRoles'),
info: t('role.searchInfo'),
}"
/> />
</template> </template>

View File

@ -11,9 +11,11 @@ import filter from './ClaimFilter.js';
:descriptor="ClaimDescriptor" :descriptor="ClaimDescriptor"
:filter-panel="ClaimFilter" :filter-panel="ClaimFilter"
search-data-key="ClaimList" search-data-key="ClaimList"
search-url="Claims/filter"
searchbar-label="Search claim"
searchbar-info="You can search by claim id or customer name"
:filter="filter" :filter="filter"
:searchbar-props="{
url: 'Claims/filter',
label: 'Search claim',
info: 'You can search by claim id or customer name',
}"
/> />
</template> </template>

View File

@ -10,8 +10,10 @@ import CustomerFilter from '../CustomerFilter.vue';
:descriptor="CustomerDescriptor" :descriptor="CustomerDescriptor"
:filter-panel="CustomerFilter" :filter-panel="CustomerFilter"
search-data-key="CustomerList" search-data-key="CustomerList"
search-url="Clients/extendedListFilter" :searchbar-props="{
searchbar-label="Search customer" url: 'Clients/extendedListFilter',
searchbar-info="You can search by customer id or name" label: 'Search customer',
info: 'You can search by customer id or name',
}"
/> />
</template> </template>

View File

@ -1,262 +1,6 @@
<script setup> <script setup>
import { onBeforeMount, ref } from 'vue'; import VnLog from 'src/components/common/VnLog.vue';
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import { useStateStore } from 'stores/useStateStore';
import FetchData from 'components/FetchData.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnInputDate from 'src/components/common/VnInputDate.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
const { t } = useI18n();
const route = useRoute();
const stateStore = useStateStore();
const clientLogs = ref(null);
const urlClientLogsEditors = ref(null);
const urlClientLogsModels = ref(null);
const clientLogsModelsOptions = ref([]);
const clientLogsOptions = ref([]);
const clientLogsEditorsOptions = ref([]);
const radioButtonValue = ref('all');
const insert = ref(false);
const update = ref(false);
const deletes = ref(false);
const select = ref(false);
const neq = ref(null);
const inq = ref([]);
const filterClientLogs = {
fields: [
'id',
'originFk',
'userFk',
'action',
'changedModel',
'oldInstance',
'newInstance',
'creationDate',
'changedModel',
'changedModelId',
'changedModelValue',
'description',
],
include: [
{
relation: 'user',
scope: {
fields: ['nickname', 'name', 'image'],
include: { relation: 'worker', scope: { fields: ['id'] } },
},
},
],
order: ['creationDate DESC', 'id DESC'],
limit: 20,
};
const filterClientLogsEditors = {
fields: ['id', 'nickname', 'name', 'image'],
order: 'nickname',
limit: 30,
};
const filterClientLogsModels = { order: ['changedModel'] };
const urlBase = `ClientLogs/${route.params.id}`;
onBeforeMount(() => {
stateStore.rightDrawer = true;
filterClientLogs.where = {
and: [
{ originFk: `${route.params.id}` },
{ userFk: { neq: radioButtonValue.value } },
{ action: { inq: inq.value } },
],
};
urlClientLogsEditors.value = `${urlBase}/editors`;
urlClientLogsModels.value = `${urlBase}/models`;
});
const getClientLogs = async (value, status) => {
if (status === 'neq') {
neq.value = value;
} else {
setInq(value, status);
}
filterClientLogs.where = {
and: [
{ originFk: `${route.params.id}` },
{ userFk: { neq: neq.value } },
{ action: { inq: inq.value } },
],
};
clientLogs.value?.fetch();
};
const setInq = (value, status) => {
if (status) {
if (!inq.value.includes(value)) {
inq.value.push(value);
}
} else {
inq.value = inq.value.filter((item) => item !== value);
}
};
</script> </script>
<template> <template>
<FetchData <VnLog model="Client" />
:filter="filterClientLogs"
@on-fetch="(data) => (clientLogsOptions = data)"
auto-load
url="ClientLogs"
ref="clientLogs"
/>
<FetchData
:filter="filterClientLogsEditors"
@on-fetch="(data) => (clientLogsEditorsOptions = data)"
auto-load
:url="urlClientLogsEditors"
/>
<FetchData
:filter="filterClientLogsModels"
@on-fetch="(data) => (clientLogsModelsOptions = data)"
auto-load
:url="urlClientLogsModels"
/>
<h5 class="flex justify-center color-vn-label">
{{ t('globals.noResults') }}
</h5>
<QDrawer :width="256" show-if-above side="right" v-model="stateStore.rightDrawer">
<div class="q-mt-sm q-px-md">
<VnInput :label="t('Search')" clearable>
<template #append>
<QIcon name="info" class="cursor-pointer">
<QTooltip>
{{ t('Search by id or concept') }}
</QTooltip>
</QIcon>
</template> </template>
</VnInput>
<VnSelect
:label="t('Entity')"
:options="[]"
class="q-mt-md"
hide-selected
option-label="name"
option-value="id"
/>
<div class="q-mt-lg">
<QRadio
:dark="true"
:label="t('All')"
@update:model-value="getClientLogs($event, 'neq')"
dense
v-model="radioButtonValue"
val="all"
/>
</div>
<div class="q-mt-md">
<QRadio
:dark="true"
:label="t('User')"
@update:model-value="getClientLogs($event, 'neq')"
dense
v-model="radioButtonValue"
val="user"
/>
</div>
<div class="q-mt-md">
<QRadio
:dark="true"
:label="t('System')"
@update:model-value="getClientLogs($event, 'neq')"
dense
v-model="radioButtonValue"
val="system"
/>
</div>
<VnSelect
:label="t('User')"
:options="[]"
class="q-mt-sm"
hide-selected
option-label="name"
option-value="id"
/>
<VnInput :label="t('Changes')" clearable class="q-mt-sm">
<template #append>
<QIcon name="info" class="cursor-pointer">
<QTooltip>
{{ t('Search by changes') }}
</QTooltip>
</QIcon>
</template>
</VnInput>
<div class="q-mt-md">
<QCheckbox
:label="t('Creates')"
@update:model-value="getClientLogs('insert', $event)"
v-model="insert"
/>
</div>
<div>
<QCheckbox
:label="t('Edits')"
@update:model-value="getClientLogs('update', $event)"
v-model="update"
/>
</div>
<div>
<QCheckbox
:label="t('Deletes')"
@update:model-value="getClientLogs('delete', $event)"
v-model="deletes"
/>
</div>
<div>
<QCheckbox
:label="t('Accesses')"
@update:model-value="getClientLogs('select', $event)"
v-model="select"
/>
</div>
<VnInputDate :label="t('Date')" class="q-mt-sm" />
<VnInput :label="t('To')" clearable class="q-mt-md" />
</div>
</QDrawer>
<QPageSticky
:offset="[18, 18]"
v-if="radioButtonValue !== 'all' || insert || update || deletes || select"
>
<QBtn color="primary" fab icon="filter_alt_off" />
<QTooltip>
{{ t('Quit filter') }}
</QTooltip>
</QPageSticky>
</template>
<i18n>
es:
Search: Buscar
Search by id or concept: xxx
Entity: Entidad
All: Todo
User: Usuario
System: Sistema
Changes: Cambios
Search by changes: xxx
Creates: Crea
Edits: Modifica
Deletes: Elimina
Accesses: Accede
Date: Fecha
To: Hasta
Quit filter: Quitar filtro
</i18n>

View File

@ -1,15 +1,18 @@
<script setup> <script setup>
import { computed } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import VnNotes from 'src/components/ui/VnNotes.vue'; import VnNotes from 'src/components/ui/VnNotes.vue';
const route = useRoute(); const route = useRoute();
const noteFilter = { const noteFilter = computed(() => {
return {
order: 'created DESC', order: 'created DESC',
where: { where: {
clientFk: `${route.params.id}`, clientFk: `${route.params.id}`,
}, },
}; };
});
</script> </script>
<template> <template>

View File

@ -1,146 +1,107 @@
<script setup> <script setup>
import axios from 'axios';
import { ref, computed } from 'vue'; import { ref, computed } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRoute, useRouter } from 'vue-router'; import { useRoute } from 'vue-router';
import { toCurrency, toDate } from 'src/filters'; import { toCurrency, toDate } from 'src/filters';
import FetchData from 'components/FetchData.vue'; import VnTable from 'components/VnTable/VnTable.vue';
const { t } = useI18n(); const { t } = useI18n();
const route = useRoute(); const route = useRoute();
const router = useRouter(); const tableRef = ref();
const rows = ref([]); const filter = computed(() => {
return {
const filter = {
where: { clientFk: route.params.id }, where: { clientFk: route.params.id },
order: ['started DESC'],
limit: 20,
}; };
});
const tableColumnComponents = { const componentColumn = (type) => {
since: { return {
component: 'span', columnFilter: {
props: () => {}, component: type,
event: () => {},
}, },
to: { columnCreate: {
component: 'span', component: type,
props: () => {},
event: () => {},
},
amount: {
component: 'span',
props: () => {},
event: () => {},
},
period: {
component: 'span',
props: () => {},
event: () => {},
}, },
}; };
};
const columns = computed(() => [ const columns = computed(() => [
{ {
align: 'left', align: 'left',
field: 'started', label: t('globals.since'),
label: t('Since'), name: 'started',
name: 'since', format: ({ started }) => toDate(started),
format: (value) => toDate(value), create: true,
...componentColumn('date'),
}, },
{ {
align: 'left', align: 'left',
field: 'finished', name: 'finished',
label: t('To'), label: t('globals.to'),
name: 'to', format: ({ finished }) => toDate(finished),
format: (value) => toDate(value), create: true,
...componentColumn('date'),
}, },
{ {
align: 'left', align: 'left',
field: 'amount',
label: t('Amount'),
name: 'amount', name: 'amount',
format: (value) => toCurrency(value), label: t('globals.amount'),
format: ({ amount }) => toCurrency(amount),
create: true,
...componentColumn('number'),
}, },
{ {
align: 'left', align: 'left',
field: 'period',
label: t('Period'),
name: 'period', name: 'period',
label: t('Period'),
create: true,
...componentColumn('number'),
},
{
align: 'left',
name: 'tableActions',
actions: [
{
title: t('Finish that recovery period'),
icon: 'lock',
show: (row) => !row.finished,
action: ({ id }) => setFinished(id),
isPrimary: true,
},
],
}, },
]); ]);
const toCustomerRecoverieCreate = () => { function setFinished(id) {
router.push({ name: 'CustomerRecoverieCreate' }); axios.patch(`Recoveries/${id}`, { finished: Date.vnNow() });
}; tableRef.value.reload();
}
</script> </script>
<template> <template>
<FetchData <VnTable
:filter="filter" ref="tableRef"
@on-fetch="(data) => (rows = data)" data-key="Recoveries"
auto-load
url="Recoveries" url="Recoveries"
/> search-url="recoveries"
:filter="filter"
<div class="full-width flex justify-center"> order="started DESC"
<QPage class="card-width q-pa-lg">
<QTable
:columns="columns" :columns="columns"
:no-data-label="t('globals.noResults')" :use-model="true"
:pagination="{ rowsPerPage: 12 }" :right-search="false"
:rows="rows" :create="{
class="full-width q-mt-md" urlCreate: 'Recoveries',
row-key="id" title: 'New recovery',
> onDataSaved: () => tableRef.reload(),
<template #body-cell="props"> formInitialData: { clientFk: route.params.id, started: Date.vnNew() },
<QTd :props="props"> }"
<QTr :props="props" class="cursor-pointer"> auto-load
<component />
:is="tableColumnComponents[props.col.name].component"
class="col-content"
v-bind="
tableColumnComponents[props.col.name].props(props)
"
@click="
tableColumnComponents[props.col.name].event(props)
"
>
{{ props.value }}
</component>
</QTr>
</QTd>
</template> </template>
</QTable>
</QPage>
</div>
<QPageSticky :offset="[18, 18]">
<QBtn @click.stop="toCustomerRecoverieCreate()" color="primary" fab icon="add" />
<QTooltip>
{{ t('New recoverie') }}
</QTooltip>
</QPageSticky>
</template>
<style lang="scss">
.consignees-card {
border: 2px solid var(--vn-accent-color);
border-radius: 10px;
padding: 10px;
}
.label-color {
color: var(--vn-label-color);
}
</style>
<i18n> <i18n>
es: es:
Since: Desde
To: Hasta
Amount: Importe
Period: Periodo Period: Periodo
New recoverie: Nuevo recobro New recovery: Nuevo recobro
Finish that recovery period: Terminar recobro
</i18n> </i18n>

View File

@ -1,17 +1,16 @@
<script setup> <script setup>
import { computed } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import VnSms from 'src/components/ui/VnSms.vue'; import VnSms from 'src/components/ui/VnSms.vue';
const route = useRoute(); const route = useRoute();
const id = route.params.id; const where = computed(() => {
return {
const where = { clientFk: route.params.id,
clientFk: id,
ticketFk: null, ticketFk: null,
}; };
});
</script> </script>
<template> <template>
<div class="column items-center">
<VnSms url="clientSms" :where="where" /> <VnSms url="clientSms" :where="where" />
</div>
</template> </template>

View File

@ -2,164 +2,81 @@
import { computed, ref } from 'vue'; import { computed, ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import axios from 'axios'; import axios from 'axios';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
import { useValidator } from 'src/composables/useValidator';
import useNotify from 'src/composables/useNotify';
import { useStateStore } from 'stores/useStateStore';
import FetchData from 'components/FetchData.vue';
import VnInput from 'src/components/common/VnInput.vue'; import VnInput from 'src/components/common/VnInput.vue';
import CustomerChangePassword from 'src/pages/Customer/components/CustomerChangePassword.vue'; import CustomerChangePassword from 'src/pages/Customer/components/CustomerChangePassword.vue';
import FormModel from 'components/FormModel.vue';
const { notify } = useNotify();
const { t } = useI18n(); const { t } = useI18n();
const { validate } = useValidator();
const quasar = useQuasar(); const quasar = useQuasar();
const route = useRoute(); const route = useRoute();
const stateStore = useStateStore();
const active = ref(false);
const canChangePassword = ref(0); const canChangePassword = ref(0);
const email = ref(null);
const isLoading = ref(false);
const name = ref(null);
const usersPreviewRef = ref(null);
const user = ref([]);
const dataChanges = computed(() => { const filter = computed(() => {
return ( return {
user.value.active !== active.value || fields: ['active', 'email', 'name'],
user.value.email !== email.value || where: { id: route.params.id },
user.value.name !== name.value };
);
}); });
const filter = { where: { id: `${route.params.id}` } };
const showChangePasswordDialog = () => { const showChangePasswordDialog = () => {
quasar.dialog({ quasar.dialog({
component: CustomerChangePassword, component: CustomerChangePassword,
componentProps: { componentProps: {
id: route.params.id, id: route.params.id,
promise: usersPreviewRef.value.fetch(),
}, },
}); });
}; };
const setInitialData = () => { async function hasCustomerRole() {
if (user.value.length) { const { data } = await axios(`Clients/${route.params.id}/hasCustomerRole`);
active.value = user.value[0].active; canChangePassword.value = data;
email.value = user.value[0].email;
name.value = user.value[0].name;
} }
};
const onSubmit = async () => {
isLoading.value = true;
const payload = {
active: active.value,
email: email.value,
name: name.value,
};
try {
await axios.patch(`Clients/${route.params.id}/updateUser`, payload);
notify('globals.dataSaved', 'positive');
if (usersPreviewRef.value) usersPreviewRef.value.fetch();
} catch (error) {
notify('errors.create', 'negative');
} finally {
isLoading.value = false;
}
};
</script> </script>
<template> <template>
<FetchData <FormModel
url="VnUsers/preview"
:url-update="`Clients/${route.params.id}/updateUser`"
:filter="filter" :filter="filter"
@on-fetch=" model="webAccess"
(data) => { :mapper="
user = data; ({ active, name, email }) => {
setInitialData(); return {
active,
name,
email,
};
} }
" "
@on-fetch="hasCustomerRole()"
auto-load auto-load
ref="usersPreviewRef" >
url="VnUsers/preview" <template #form="{ data, validate }">
/> <QCheckbox :label="t('Enable web access')" v-model="data.active" />
<FetchData <VnInput :label="t('User')" clearable v-model="data.name" />
:url="`Clients/${route.params.id}/hasCustomerRole`"
@on-fetch="(data) => (canChangePassword = data)"
auto-load
/>
<Teleport to="#st-actions" v-if="stateStore?.isSubToolbarShown()">
<QBtnGroup push class="q-gutter-x-sm">
<QBtn
:disabled="isLoading"
:label="t('globals.reset')"
:loading="isLoading"
@click="setInitialData"
color="primary"
flat
icon="restart_alt"
type="reset"
/>
<QBtn
:disabled="isLoading"
:label="t('Change password')"
:loading="isLoading"
@click.stop="showChangePasswordDialog()"
color="primary"
flat
icon="edit"
v-if="canChangePassword"
/>
<QBtn
:disabled="isLoading || !dataChanges"
:label="t('globals.save')"
:loading="isLoading"
@click="onSubmit"
color="primary"
icon="save"
/>
</QBtnGroup>
</Teleport>
<div class="full-width flex justify-center">
<QCard class="card-width q-pa-lg">
<QCardSection>
<QForm>
<QCheckbox :label="t('Enable web access')" v-model="active" />
<div class="q-px-sm">
<VnInput :label="t('User')" clearable v-model="name" />
<VnInput <VnInput
:label="t('Recovery email')" :label="t('Recovery email')"
:rules="validate('client.email')" :rules="validate('client.email')"
clearable clearable
type="email" type="email"
v-model="email" v-model="data.email"
class="q-mt-sm" class="q-mt-sm"
> :info="t('This email is used for user to regain access their account')"
<template #append> />
<QIcon name="info" class="cursor-pointer">
<QTooltip>{{
t(
'This email is used for user to regain access their account'
)
}}</QTooltip>
</QIcon>
</template> </template>
</VnInput> <template #moreActions>
</div> <QBtn
</QForm> :label="t('Change password')"
</QCardSection> color="primary"
</QCard> icon="edit"
</div> :disable="!canChangePassword"
@click="showChangePasswordDialog()"
/>
</template>
</FormModel>
</template> </template>
<i18n> <i18n>

View File

@ -10,8 +10,10 @@ import EntryFilter from '../EntryFilter.vue';
:descriptor="EntryDescriptor" :descriptor="EntryDescriptor"
:filter-panel="EntryFilter" :filter-panel="EntryFilter"
search-data-key="EntryList" search-data-key="EntryList"
search-url="Entries/filter" :searchbar-props="{
searchbar-label="Search entries" url: 'Entries/filter',
searchbar-info="You can search by entry reference" label: 'Search entries',
info: 'You can search by entry reference',
}"
/> />
</template> </template>

View File

@ -10,8 +10,10 @@ import InvoiceOutFilter from '../InvoiceOutFilter.vue';
:descriptor="InvoiceOutDescriptor" :descriptor="InvoiceOutDescriptor"
:filter-panel="InvoiceOutFilter" :filter-panel="InvoiceOutFilter"
search-data-key="InvoiceOutList" search-data-key="InvoiceOutList"
search-url="InvoiceOuts/filter" :searchbar-props="{
searchbar-label="Search invoice" url: 'InvoiceOuts/filter',
searchbar-info="You can search by invoice reference" label: 'Search invoice',
info: 'You can search by invoice reference',
}"
/> />
</template> </template>

View File

@ -10,8 +10,10 @@ import ItemListFilter from '../ItemListFilter.vue';
:descriptor="ItemDescriptor" :descriptor="ItemDescriptor"
:filter-panel="ItemListFilter" :filter-panel="ItemListFilter"
search-data-key="ItemList" search-data-key="ItemList"
search-url="Items/filter" :searchbar-props="{
searchbar-label="searchbar.label" url: 'Items/filter',
searchbar-info="searchbar.info" label: 'searchbar.labelr',
info: 'searchbar.info',
}"
/> />
</template> </template>

View File

@ -28,35 +28,10 @@ async function onSubmit() {
}; };
try { try {
const { data } = await axios.post('Accounts/login', params); const { data } = await axios.post('Accounts/login', params);
if (!data) return; if (!data) return;
const {
data: { multimediaToken },
} = await axios.get('VnUsers/ShareToken', {
headers: { Authorization: data.token },
});
if (!multimediaToken) return; data.keepLogin = keepLogin.value;
await session.setLogin(data);
const login = {
...data,
created: Date.now(),
tokenMultimedia: multimediaToken.id,
keepLogin: keepLogin.value,
};
await session.login(login);
quasar.notify({
message: t('login.loginSuccess'),
type: 'positive',
});
const currentRoute = router.currentRoute.value;
if (currentRoute.query && currentRoute.query.redirect) {
router.push(currentRoute.query.redirect);
} else {
router.push({ name: 'Dashboard' });
}
} catch (res) { } catch (res) {
if (res.response?.data?.error?.code === 'REQUIRES_2FA') { if (res.response?.data?.error?.code === 'REQUIRES_2FA') {
Notify.create({ Notify.create({

View File

@ -25,21 +25,10 @@ async function onSubmit() {
try { try {
params.code = code.value; params.code = code.value;
const { data } = await axios.post('VnUsers/validate-auth', params); const { data } = await axios.post('VnUsers/validate-auth', params);
if (!data) return; if (!data) return;
await session.login(data.token, params.keepLogin); data.keepLogin = params.keepLogin;
quasar.notify({ await session.setLogin(data);
message: t('login.loginSuccess'),
type: 'positive',
});
const currentRoute = router.currentRoute.value;
if (currentRoute.query && currentRoute.query.redirect) {
router.push(currentRoute.query.redirect);
} else {
router.push({ name: 'Dashboard' });
}
} catch (e) { } catch (e) {
quasar.notify({ quasar.notify({
message: e.response?.data?.error.message, message: e.response?.data?.error.message,

View File

@ -16,8 +16,10 @@ const filter = {
:descriptor="ParkingDescriptor" :descriptor="ParkingDescriptor"
:filter-panel="ParkingFilter" :filter-panel="ParkingFilter"
search-data-key="ParkingList" search-data-key="ParkingList"
search-url="Parkings" :searchbar-props="{
searchbar-label="parking.searchBar.label" url: 'Parkings',
searchbar-info="parking.searchBar.info" label: 'parking.searchBar.label',
info: 'parking.searchBar.info',
}"
/> />
</template> </template>

View File

@ -8,8 +8,10 @@ import VnCard from 'components/common/VnCard.vue';
base-url="Agencies" base-url="Agencies"
:descriptor="AgencyDescriptor" :descriptor="AgencyDescriptor"
search-data-key="AgencyList" search-data-key="AgencyList"
search-url="Agencies" :searchbar-props="{
searchbar-label="agency.searchBar.label" url: 'Agencies',
searchbar-info="agency.searchBar.info" label: 'agency.searchBar.label',
info: 'agency.searchBar.info',
}"
/> />
</template> </template>

View File

@ -10,8 +10,10 @@ import RoadmapFilter from 'pages/Route/Roadmap/RoadmapFilter.vue';
:descriptor="RoadmapDescriptor" :descriptor="RoadmapDescriptor"
:filter-panel="RoadmapFilter" :filter-panel="RoadmapFilter"
search-data-key="RoadmapList" search-data-key="RoadmapList"
search-url="Roadmaps" :searchbar-props="{
searchbar-label="Search roadmap" url: 'Roadmaps',
searchbar-info="You can search by roadmap id or customer name" label: 'Search roadmap',
info: 'You can search by roadmap id or customer name',
}"
/> />
</template> </template>

View File

@ -10,8 +10,10 @@ import SupplierListFilter from '../SupplierListFilter.vue';
:descriptor="SupplierDescriptor" :descriptor="SupplierDescriptor"
:filter-panel="SupplierListFilter" :filter-panel="SupplierListFilter"
search-data-key="SupplierList" search-data-key="SupplierList"
search-url="Suppliers/filter" :searchbar-props="{
searchbar-label="Search suppliers" url: 'Suppliers/filter',
search-url-query="table" searchUrl: 'table',
label: 'Search suppliers',
}"
/> />
</template> </template>

View File

@ -11,6 +11,7 @@ const { t } = useI18n();
const route = useRoute(); const route = useRoute();
const routeName = computed(() => route.name); const routeName = computed(() => route.name);
const customRouteRedirectName = computed(() => routeName.value);
</script> </script>
<template> <template>
<VnCard <VnCard
@ -19,8 +20,10 @@ const routeName = computed(() => route.name);
:filter-panel="TicketFilter" :filter-panel="TicketFilter"
:descriptor="TicketDescriptor" :descriptor="TicketDescriptor"
search-data-key="TicketList" search-data-key="TicketList"
:search-custom-route-redirect="routeName" :searchbar-props="{
:searchbar-label="t('card.search')" customRouteRedirectName,
:searchbar-info="t('card.searchInfo')" label: t('card.search'),
info: t('card.searchInfo'),
}"
/> />
</template> </template>

View File

@ -35,10 +35,12 @@ const filter = {
data-key="Travel" data-key="Travel"
base-url="Travels" base-url="Travels"
search-data-key="TravelList" search-data-key="TravelList"
searchbar-label="Search travel"
searchbar-info="You can search by travel id or name"
search-url="Travels"
:filter="filter" :filter="filter"
:descriptor="TravelDescriptor" :descriptor="TravelDescriptor"
:searchbar-props="{
url: 'Travels',
label: 'Search travel',
info: 'You can search by travel id or name',
}"
/> />
</template> </template>

View File

@ -139,23 +139,19 @@ const maritalStatus = [
<VnRow> <VnRow>
<VnInput v-model="data.SSN" :label="t('SSN')" clearable /> <VnInput v-model="data.SSN" :label="t('SSN')" clearable />
<VnInput
v-model="data.locker"
type="number"
:label="t('Locker')"
clearable
/>
</VnRow>
<VnRow>
<VnInputDate :label="t('fiDueDate')" v-model="data.fiDueDate" /> <VnInputDate :label="t('fiDueDate')" v-model="data.fiDueDate" />
<VnInput v-model="data.sex" :label="t('sex')" clearable />
</VnRow> </VnRow>
<VnRow> <VnRow>
<VnSelect
:label="t('sex')"
:options="['M', 'F']"
hide-selected
v-model="data.sex"
/>
<VnInputDate :label="t('seniority')" v-model="data.seniority" /> <VnInputDate :label="t('seniority')" v-model="data.seniority" />
<VnInput v-model="data.fi" :label="t('fi')" />
</VnRow> </VnRow>
<VnRow> <VnRow>
<VnInput v-model="data.fi" :label="t('fi')" />
<VnInputDate :label="t('birth')" v-model="data.birth" /> <VnInputDate :label="t('birth')" v-model="data.birth" />
</VnRow> </VnRow>
<VnRow> <VnRow>

View File

@ -171,8 +171,7 @@ watch([year, businessFk], () => refreshData());
ref="WorkerFreelanceRef" ref="WorkerFreelanceRef"
auto-load auto-load
/> />
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above> <Teleport to="#right-panel" v-if="stateStore.isHeaderMounted()">
<QScrollArea class="fit text-grey-8">
<WorkerCalendarFilter <WorkerCalendarFilter
ref="workerCalendarFilterRef" ref="workerCalendarFilterRef"
v-model:business-fk="businessFk" v-model:business-fk="businessFk"
@ -181,8 +180,7 @@ watch([year, businessFk], () => refreshData());
:contract-holidays="contractHolidays" :contract-holidays="contractHolidays"
:year-holidays="yearHolidays" :year-holidays="yearHolidays"
/> />
</QScrollArea> </Teleport>
</QDrawer>
<QPage class="column items-center"> <QPage class="column items-center">
<QCard v-if="workerIsFreelance"> <QCard v-if="workerIsFreelance">
<QCardSection class="text-center"> <QCardSection class="text-center">

View File

@ -5,13 +5,15 @@ import WorkerFilter from '../WorkerFilter.vue';
</script> </script>
<template> <template>
<VnCard <VnCard
data-key="Workers" data-key="Worker"
base-url="Workers" base-url="Workers"
:descriptor="WorkerDescriptor" :descriptor="WorkerDescriptor"
:filter-panel="WorkerFilter" :filter-panel="WorkerFilter"
search-data-key="WorkerList" search-data-key="WorkerList"
search-url="Workers/filter" :searchbar-props="{
searchbar-label="Search worker" url: 'Workers/filter',
searchbar-info="You can search by worker id or name" label: 'Search worker',
info: 'You can search by worker id or name',
}"
/> />
</template> </template>

View File

@ -11,6 +11,7 @@ import VnUserLink from 'src/components/ui/VnUserLink.vue';
import VnTitle from 'src/components/common/VnTitle.vue'; import VnTitle from 'src/components/common/VnTitle.vue';
import RoleDescriptorProxy from 'src/pages/Account/Role/Card/RoleDescriptorProxy.vue'; import RoleDescriptorProxy from 'src/pages/Account/Role/Card/RoleDescriptorProxy.vue';
import VnRow from 'src/components/ui/VnRow.vue'; import VnRow from 'src/components/ui/VnRow.vue';
import DepartmentDescriptorProxy from 'src/pages/Department/Card/DepartmentDescriptorProxy.vue';
const route = useRoute(); const route = useRoute();
const { t } = useI18n(); const { t } = useI18n();
@ -84,10 +85,14 @@ const filter = {
:text="t('worker.summary.basicData')" :text="t('worker.summary.basicData')"
/> />
<VnLv :label="t('worker.card.name')" :value="worker.user?.nickname" /> <VnLv :label="t('worker.card.name')" :value="worker.user?.nickname" />
<VnLv <VnLv :label="t('worker.list.department')">
:label="t('worker.list.department')" <template #value>
:value="worker.department?.department?.name" <span class="link" v-text="worker.department?.department?.name" />
<DepartmentDescriptorProxy
:id="worker.department?.department?.id"
/> />
</template>
</VnLv>
<VnLv :label="t('worker.list.email')" :value="worker.user.email" copy /> <VnLv :label="t('worker.list.email')" :value="worker.user.email" copy />
<VnLv :label="t('worker.summary.boss')" link> <VnLv :label="t('worker.summary.boss')" link>
<template #value> <template #value>

View File

@ -489,7 +489,7 @@ onMounted(async () => {
</QBtnGroup> </QBtnGroup>
</div> </div>
</Teleport> </Teleport>
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="260" class="q-pa-md"> <Teleport to="#right-panel" v-if="stateStore.isHeaderMounted()">
<div class="q-pa-md q-mb-md" style="border: 2px solid #222"> <div class="q-pa-md q-mb-md" style="border: 2px solid #222">
<QCardSection horizontal> <QCardSection horizontal>
<span class="text-weight-bold text-subtitle1 text-center full-width"> <span class="text-weight-bold text-subtitle1 text-center full-width">
@ -515,7 +515,7 @@ onMounted(async () => {
@click-date="onInputChange" @click-date="onInputChange"
@on-moved="getMailStates" @on-moved="getMailStates"
/> />
</QDrawer> </Teleport>
<QPage class="column items-center"> <QPage class="column items-center">
<QTable :columns="columns" :rows="['']" hide-bottom class="full-width"> <QTable :columns="columns" :rows="['']" hide-bottom class="full-width">
<template #header="props"> <template #header="props">

View File

@ -28,11 +28,12 @@ const searchBarDataKeys = {
data-key="Zone" data-key="Zone"
:descriptor="ZoneDescriptor" :descriptor="ZoneDescriptor"
:search-data-key="searchBarDataKeys[routeName]" :search-data-key="searchBarDataKeys[routeName]"
:search-custom-route-redirect="customRouteRedirectName" :filter-panel="ZoneFilterPanel"
:search-redirect="!!customRouteRedirectName" :searchbar-props="{
:search-make-fetch="searchbarMakeFetch" url: 'Zones',
:searchbar-label="t('list.searchZone')" label: t('list.searchZone'),
:searchbar-info="t('list.searchInfo')" info: t('list.searchInfo'),
}"
> >
<template #searchbar> <template #searchbar>
<ZoneSearchbar /> <ZoneSearchbar />

View File

@ -226,11 +226,6 @@ export default {
}, },
{ {
path: 'recoveries', path: 'recoveries',
name: 'RecoveriesCard',
redirect: { name: 'CustomerRecoveries' },
children: [
{
path: '',
name: 'CustomerRecoveries', name: 'CustomerRecoveries',
meta: { meta: {
title: 'recoveries', title: 'recoveries',
@ -239,19 +234,6 @@ export default {
component: () => component: () =>
import('src/pages/Customer/Card/CustomerRecoveries.vue'), import('src/pages/Customer/Card/CustomerRecoveries.vue'),
}, },
{
path: 'create',
name: 'CustomerRecoverieCreate',
meta: {
title: 'recoverie-create',
},
component: () =>
import(
'src/pages/Customer/components/CustomerRecoverieCreate.vue'
),
},
],
},
{ {
path: 'web-access', path: 'web-access',
name: 'CustomerWebAccess', name: 'CustomerWebAccess',

View File

@ -2,7 +2,7 @@ describe('WorkerLocker', () => {
const workerId = 1109; const workerId = 1109;
const lockerCode = '2F'; const lockerCode = '2F';
const input = '.q-card input'; const input = '.q-card input';
const thirdOpt = '[role="listbox"] .q-item:nth-child(3)'; const thirdOpt = '[role="listbox"] .q-item:nth-child(1)';
beforeEach(() => { beforeEach(() => {
cy.viewport(1280, 720); cy.viewport(1280, 720);
cy.login('productionBoss'); cy.login('productionBoss');

View File

@ -25,18 +25,20 @@ describe('Login', () => {
vi.spyOn(axios, 'post').mockResolvedValueOnce({ data: { token: 'token' } }); vi.spyOn(axios, 'post').mockResolvedValueOnce({ data: { token: 'token' } });
vi.spyOn(axios, 'get').mockImplementation((url) => { vi.spyOn(axios, 'get').mockImplementation((url) => {
if (url === 'VnUsers/acls') return Promise.resolve({ data: [] }); if (url === 'VnUsers/acls') return Promise.resolve({ data: [] });
return Promise.resolve({data: { roles: [], user: expectedUser , multimediaToken: {id:'multimediaToken' }}}); return Promise.resolve({
data: {
roles: [],
user: expectedUser,
multimediaToken: { id: 'multimediaToken' },
},
});
}); });
vi.spyOn(vm.quasar, 'notify');
expect(vm.session.getToken()).toEqual(''); expect(vm.session.getToken()).toEqual('');
await vm.onSubmit(); await vm.onSubmit();
expect(vm.session.getToken()).toEqual('token'); expect(vm.session.getToken()).toEqual('token');
expect(vm.quasar.notify).toHaveBeenCalledWith(
expect.objectContaining({ type: 'positive' })
);
await vm.session.destroy(); await vm.session.destroy();
}); });