forked from verdnatura/salix-front
Merge pull request '232201_dev_to_test' (!59) from dev into test
Reviewed-on: verdnatura/salix-front#59 Reviewed-by: Alexandre Riera <alexandre@verdnatura.es>
This commit is contained in:
commit
8f5a3b3876
File diff suppressed because it is too large
Load Diff
52
package.json
52
package.json
|
@ -15,40 +15,42 @@
|
|||
"test:unit:ci": "vitest run"
|
||||
},
|
||||
"dependencies": {
|
||||
"@quasar/extras": "^1.15.11",
|
||||
"axios": "^1.2.1",
|
||||
"pinia": "^2.0.28",
|
||||
"quasar": "^2.11.7",
|
||||
"validator": "^13.7.0",
|
||||
"vue": "^3.2.45",
|
||||
"@quasar/cli": "^2.2.1",
|
||||
"@quasar/extras": "^1.16.4",
|
||||
"axios": "^1.4.0",
|
||||
"chromium": "^3.0.3",
|
||||
"pinia": "^2.1.3",
|
||||
"quasar": "^2.12.0",
|
||||
"validator": "^13.9.0",
|
||||
"vue": "^3.3.4",
|
||||
"vue-i18n": "^9.2.2",
|
||||
"vue-router": "^4.1.6",
|
||||
"vue-router-mock": "^0.1.9"
|
||||
"vue-router": "^4.2.1",
|
||||
"vue-router-mock": "^0.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@intlify/unplugin-vue-i18n": "^0.8.1",
|
||||
"@pinia/testing": "^0.0.14",
|
||||
"@quasar/app-vite": "^1.2.1",
|
||||
"@quasar/quasar-app-extension-testing-unit-vitest": "^0.2.1",
|
||||
"@vue/test-utils": "^2.0.0",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"cypress": "^12.2.0",
|
||||
"eslint": "^8.30.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-cypress": "^2.12.1",
|
||||
"eslint-plugin-vue": "^9.8.0",
|
||||
"postcss": "^8.4.20",
|
||||
"prettier": "^2.8.1",
|
||||
"vitest": "^0.26.3"
|
||||
"@pinia/testing": "^0.1.2",
|
||||
"@quasar/app-vite": "^1.4.3",
|
||||
"@quasar/quasar-app-extension-testing-unit-vitest": "^0.3.0",
|
||||
"@vue/test-utils": "^2.3.2",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"cypress": "^12.13.0",
|
||||
"eslint": "^8.41.0",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"eslint-plugin-cypress": "^2.13.3",
|
||||
"eslint-plugin-vue": "^9.14.1",
|
||||
"postcss": "^8.4.23",
|
||||
"prettier": "^2.8.8",
|
||||
"vitest": "^0.31.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18 || ^16 || ^14.19",
|
||||
"npm": ">= 6.13.4",
|
||||
"node": "^20 || ^18 || ^16",
|
||||
"npm": ">= 8.1.2",
|
||||
"yarn": ">= 1.21.1"
|
||||
},
|
||||
"overrides": {
|
||||
"@vitejs/plugin-vue": "^4.0.0",
|
||||
"vite": "^4.0.3",
|
||||
"vitest": "^0.26.3"
|
||||
"vite": "^4.3.5",
|
||||
"vitest": "^0.31.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,16 +39,20 @@ onMounted(async () => {
|
|||
});
|
||||
|
||||
async function fetch() {
|
||||
const filter = Object.assign({}, $props.filter);
|
||||
if ($props.where) filter.where = $props.where;
|
||||
if ($props.sortBy) filter.order = $props.sortBy;
|
||||
if ($props.limit) filter.limit = $props.limit;
|
||||
try {
|
||||
const filter = Object.assign({}, $props.filter); // eslint-disable-line vue/no-dupe-keys
|
||||
if ($props.where) filter.where = $props.where;
|
||||
if ($props.sortBy) filter.order = $props.sortBy;
|
||||
if ($props.limit) filter.limit = $props.limit;
|
||||
|
||||
const { data } = await axios.get($props.url, {
|
||||
params: { filter },
|
||||
});
|
||||
const { data } = await axios.get($props.url, {
|
||||
params: { filter },
|
||||
});
|
||||
|
||||
emit('onFetch', data);
|
||||
emit('onFetch', data);
|
||||
} catch (e) {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
const render = () => {
|
||||
|
|
|
@ -77,7 +77,7 @@ function reset() {
|
|||
state.set($props.model, originalData.value);
|
||||
hasChanges.value = false;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
function filter(value, update, filterOptions) {
|
||||
update(
|
||||
() => {
|
||||
|
|
|
@ -11,7 +11,7 @@ const props = defineProps({
|
|||
},
|
||||
});
|
||||
|
||||
const item = computed(() => props.item);
|
||||
const item = computed(() => props.item); // eslint-disable-line vue/no-dupe-keys
|
||||
</script>
|
||||
<template>
|
||||
<QItem active-class="text-primary" :to="{ name: item.name }" clickable v-ripple>
|
||||
|
|
|
@ -18,7 +18,7 @@ const props = defineProps({
|
|||
},
|
||||
});
|
||||
|
||||
const item = computed(() => props.item);
|
||||
const item = computed(() => props.item); // eslint-disable-line vue/no-dupe-keys
|
||||
const isOpened = computed(() => {
|
||||
const { matched } = route;
|
||||
const { name } = item.value;
|
||||
|
|
|
@ -0,0 +1,208 @@
|
|||
<script setup>
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useSession } from 'src/composables/useSession';
|
||||
import { useStateStore } from 'stores/useStateStore';
|
||||
import VnPaginate from 'src/components/ui/VnPaginate.vue';
|
||||
import VnLogFilter from 'src/components/common/VnLogFilter.vue';
|
||||
|
||||
import { toDate } from 'src/filters';
|
||||
|
||||
const stateStore = useStateStore();
|
||||
const route = useRoute();
|
||||
const session = useSession();
|
||||
const token = session.getToken();
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = defineProps({
|
||||
model: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
|
||||
const columns = [
|
||||
{
|
||||
name: 'property',
|
||||
label: 'Property',
|
||||
field: (row) => t(`properties.${row.property}`),
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
name: 'before',
|
||||
label: 'Before',
|
||||
field: (row) => formatValue(row.before),
|
||||
},
|
||||
{
|
||||
name: 'after',
|
||||
label: 'After',
|
||||
field: (row) => formatValue(row.after),
|
||||
},
|
||||
];
|
||||
|
||||
function formatValue(value) {
|
||||
if (typeof value === 'boolean') {
|
||||
return value ? t('Yes') : t('No');
|
||||
}
|
||||
|
||||
if (isNaN(value) && !isNaN(Date.parse(value))) {
|
||||
return toDate(value);
|
||||
}
|
||||
|
||||
if (value === undefined) {
|
||||
return t('Nothing');
|
||||
}
|
||||
|
||||
return `"${value}"`;
|
||||
}
|
||||
|
||||
function actionColor(action) {
|
||||
if (action === 'insert') return 'positive';
|
||||
if (action === 'update') return 'positive';
|
||||
if (action === 'delete') return 'negative';
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="column items-center">
|
||||
<QTimeline class="q-pa-md">
|
||||
<QTimelineEntry heading tag="h4"> {{ t('Audit logs') }} </QTimelineEntry>
|
||||
<VnPaginate
|
||||
:data-key="`${props.model}Logs`"
|
||||
:url="`${props.model}s/${route.params.id}/logs`"
|
||||
order="id DESC"
|
||||
:offset="100"
|
||||
:limit="5"
|
||||
auto-load
|
||||
>
|
||||
<template #body="{ rows }">
|
||||
<template v-for="log of rows" :key="log.id">
|
||||
<QTimelineEntry
|
||||
:avatar="`/api/Images/user/160x160/${log.userFk}/download?access_token=${token}`"
|
||||
>
|
||||
<template #subtitle>
|
||||
{{ log.userName }} -
|
||||
{{
|
||||
toDate(log.created, {
|
||||
dateStyle: 'medium',
|
||||
timeStyle: 'short',
|
||||
})
|
||||
}}
|
||||
</template>
|
||||
<template #title>
|
||||
<QChip :color="actionColor(log.action)">
|
||||
{{ t(`actions.${log.action}`) }}
|
||||
</QChip>
|
||||
{{ t(`models.${log.model}`) }}
|
||||
</template>
|
||||
<QTable
|
||||
:rows="log.changes"
|
||||
:columns="columns"
|
||||
row-key="property"
|
||||
hide-pagination
|
||||
dense
|
||||
flat
|
||||
>
|
||||
<template #header="propsLabel">
|
||||
<QTr :props="propsLabel">
|
||||
<QTh
|
||||
v-for="col in propsLabel.cols"
|
||||
:key="col.name"
|
||||
:props="propsLabel"
|
||||
>
|
||||
{{ t(col.label) }}
|
||||
</QTh>
|
||||
</QTr>
|
||||
</template>
|
||||
</QTable>
|
||||
</QTimelineEntry>
|
||||
</template>
|
||||
</template>
|
||||
</VnPaginate>
|
||||
</QTimeline>
|
||||
</div>
|
||||
<Teleport v-if="stateStore.isHeaderMounted()" 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>
|
||||
<QDrawer v-model="stateStore.rightDrawer" show-if-above side="right" :width="300">
|
||||
<QScrollArea class="fit text-grey-8">
|
||||
<VnLogFilter :data-key="`${props.model}Logs`" />
|
||||
</QScrollArea>
|
||||
</QDrawer>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.q-timeline {
|
||||
width: 100%;
|
||||
max-width: 80em;
|
||||
}
|
||||
</style>
|
||||
<i18n>
|
||||
en:
|
||||
actions:
|
||||
insert: Creates
|
||||
update: Updates
|
||||
delete: Deletes
|
||||
models:
|
||||
Claim: Claim
|
||||
ClaimDms: Document
|
||||
ClaimBeginning: Claimed Sales
|
||||
ClaimObservation: Observation
|
||||
properties:
|
||||
id: ID
|
||||
claimFk: Claim ID
|
||||
saleFk: Sale ID
|
||||
quantity: Quantity
|
||||
observation: Observation
|
||||
ticketCreated: Created
|
||||
created: Created
|
||||
isChargedToMana: Charged to mana
|
||||
hasToPickUp: Has to pick Up
|
||||
dmsFk: Document ID
|
||||
text: Description
|
||||
claimStateFk: Claim State
|
||||
workerFk: Worker
|
||||
clientFk: Customer
|
||||
rma: RMA
|
||||
responsibility: Responsibility
|
||||
packages: Packages
|
||||
es:
|
||||
Audit logs: Registros de auditoría
|
||||
Property: Propiedad
|
||||
Before: Antes
|
||||
After: Después
|
||||
Yes: Si
|
||||
Nothing: Nada
|
||||
actions:
|
||||
insert: Crea
|
||||
update: Actualiza
|
||||
delete: Elimina
|
||||
models:
|
||||
Claim: Reclamación
|
||||
ClaimDms: Documento
|
||||
ClaimBeginning: Línea reclamada
|
||||
ClaimObservation: Observación
|
||||
properties:
|
||||
id: ID
|
||||
claimFk: ID reclamación
|
||||
saleFk: ID linea de venta
|
||||
quantity: Cantidad
|
||||
observation: Observación
|
||||
ticketCreated: Creado
|
||||
created: Creado
|
||||
isChargedToMana: Cargado a maná
|
||||
hasToPickUp: Se debe recoger
|
||||
dmsFk: ID documento
|
||||
text: Descripción
|
||||
claimStateFk: Estado de la reclamación
|
||||
workerFk: Trabajador
|
||||
clientFk: Cliente
|
||||
rma: RMA
|
||||
responsibility: Responsabilidad
|
||||
packages: Bultos
|
||||
</i18n>
|
|
@ -36,7 +36,7 @@ const props = defineProps({
|
|||
|
||||
const emit = defineEmits(['onUpdate']);
|
||||
|
||||
const discount = ref(0);
|
||||
const discount = ref(0); // eslint-disable-line vue/no-dupe-keys
|
||||
let canceller;
|
||||
|
||||
onMounted(() => {
|
||||
|
|
|
@ -13,7 +13,6 @@ export function useRole() {
|
|||
name: data.user.name,
|
||||
nickname: data.user.nickname,
|
||||
lang: data.user.lang || 'es',
|
||||
darkMode: data.user.userConfig.darkMode,
|
||||
};
|
||||
state.setUser(userData);
|
||||
state.setRoles(roles);
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import { useState } from './useState';
|
||||
import { useRole } from './useRole';
|
||||
import { useUserConfig } from './useUserConfig';
|
||||
|
||||
|
||||
export function useSession() {
|
||||
function getToken() {
|
||||
|
@ -36,11 +38,10 @@ export function useSession() {
|
|||
}
|
||||
|
||||
async function login(token, keepLogin) {
|
||||
const { fetch } = useRole();
|
||||
|
||||
setToken({ token, keepLogin });
|
||||
|
||||
await fetch();
|
||||
await useRole().fetch();
|
||||
await useUserConfig().fetch();
|
||||
}
|
||||
|
||||
function isLoggedIn() {
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
import axios from 'axios';
|
||||
import { useState } from './useState';
|
||||
|
||||
export function useUserConfig() {
|
||||
const state = useState();
|
||||
|
||||
async function fetch() {
|
||||
const { data } = await axios.get('UserConfigs/getUserConfig');
|
||||
const user = state.getUser().value;
|
||||
user.darkMode = data.darkMode;
|
||||
state.setUser(user);
|
||||
}
|
||||
|
||||
return {
|
||||
fetch,
|
||||
};
|
||||
}
|
|
@ -361,6 +361,7 @@ export default {
|
|||
list: 'List',
|
||||
basicData: 'Basic data',
|
||||
summary: 'Summary',
|
||||
notifications: 'Notifications',
|
||||
},
|
||||
list: {
|
||||
name: 'Name',
|
||||
|
@ -394,6 +395,12 @@ export default {
|
|||
role: 'Role',
|
||||
sipExtension: 'Extension',
|
||||
},
|
||||
notificationsManager: {
|
||||
activeNotifications: 'Active notifications',
|
||||
availableNotifications: 'Available notifications',
|
||||
subscribed: 'Subscribed to the notification',
|
||||
unsubscribed: 'Unsubscribed from the notification',
|
||||
},
|
||||
imageNotFound: 'Image not found',
|
||||
},
|
||||
wagon: {
|
||||
|
|
|
@ -361,6 +361,7 @@ export default {
|
|||
list: 'Listado',
|
||||
basicData: 'Datos básicos',
|
||||
summary: 'Resumen',
|
||||
notifications: 'Notificaciones',
|
||||
},
|
||||
list: {
|
||||
name: 'Nombre',
|
||||
|
@ -394,6 +395,12 @@ export default {
|
|||
role: 'Rol',
|
||||
sipExtension: 'Extensión',
|
||||
},
|
||||
notificationsManager: {
|
||||
activeNotifications: 'Notificaciones activas',
|
||||
availableNotifications: 'Notificaciones disponibles',
|
||||
subscribed: 'Se ha suscrito a la notificación',
|
||||
unsubscribed: 'Se ha dado de baja de la notificación',
|
||||
},
|
||||
imageNotFound: 'No se ha encontrado la imagen',
|
||||
},
|
||||
wagon: {
|
||||
|
|
|
@ -418,7 +418,7 @@ function showImportDialog() {
|
|||
<i18n>
|
||||
en:
|
||||
You are about to remove {count} rows: '
|
||||
You are about to remove <strong>{count}</strong> row |
|
||||
You are about to remove <strong>{count}</strong> row |
|
||||
You are about to remove <strong>{count}</strong> rows'
|
||||
es:
|
||||
Claimed lines: Líneas reclamadas
|
||||
|
@ -435,6 +435,6 @@ es:
|
|||
Discount updated: Descuento actualizado
|
||||
Claimed quantity: Cantidad reclamada
|
||||
You are about to remove {count} rows: '
|
||||
Vas a eliminar <strong>{count}</strong> línea |
|
||||
Vas a eliminar <strong>{count}</strong> línea |
|
||||
Vas a eliminar <strong>{count}</strong> líneas'
|
||||
</i18n>
|
||||
|
|
|
@ -1,201 +1,6 @@
|
|||
<script setup>
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useSession } from 'src/composables/useSession';
|
||||
import { useStateStore } from 'stores/useStateStore';
|
||||
import VnPaginate from 'src/components/ui/VnPaginate.vue';
|
||||
import ClaimLogFilter from './ClaimLogFilter.vue';
|
||||
|
||||
import { toDate } from 'src/filters';
|
||||
|
||||
const stateStore = useStateStore();
|
||||
const route = useRoute();
|
||||
const session = useSession();
|
||||
const token = session.getToken();
|
||||
const { t } = useI18n();
|
||||
|
||||
const columns = [
|
||||
{
|
||||
name: 'property',
|
||||
label: 'Property',
|
||||
field: (row) => t(`properties.${row.property}`),
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
name: 'before',
|
||||
label: 'Before',
|
||||
field: (row) => formatValue(row.before),
|
||||
},
|
||||
{
|
||||
name: 'after',
|
||||
label: 'After',
|
||||
field: (row) => formatValue(row.after),
|
||||
},
|
||||
];
|
||||
|
||||
function formatValue(value) {
|
||||
if (typeof value === 'boolean') {
|
||||
return value ? t('Yes') : t('No');
|
||||
}
|
||||
|
||||
if (isNaN(value) && !isNaN(Date.parse(value))) {
|
||||
return toDate(value);
|
||||
}
|
||||
|
||||
if (value === undefined) {
|
||||
return t('Nothing');
|
||||
}
|
||||
|
||||
return `"${value}"`;
|
||||
}
|
||||
|
||||
function actionColor(action) {
|
||||
if (action === 'insert') return 'positive';
|
||||
if (action === 'update') return 'positive';
|
||||
if (action === 'delete') return 'negative';
|
||||
}
|
||||
import VnLog from 'src/components/common/VnLog.vue';
|
||||
</script>
|
||||
<template>
|
||||
<div class="column items-center">
|
||||
<QTimeline class="q-pa-md">
|
||||
<QTimelineEntry heading tag="h4"> {{ t('Audit logs') }} </QTimelineEntry>
|
||||
<VnPaginate
|
||||
data-key="ClaimLogs"
|
||||
:url="`Claims/${route.params.id}/logs`"
|
||||
order="id DESC"
|
||||
:offset="100"
|
||||
:limit="5"
|
||||
auto-load
|
||||
>
|
||||
<template #body="{ rows }">
|
||||
<template v-for="log of rows" :key="log.id">
|
||||
<QTimelineEntry
|
||||
:avatar="`/api/Images/user/160x160/${log.userFk}/download?access_token=${token}`"
|
||||
>
|
||||
<template #subtitle>
|
||||
{{ log.userName }} -
|
||||
{{
|
||||
toDate(log.created, {
|
||||
dateStyle: 'medium',
|
||||
timeStyle: 'short',
|
||||
})
|
||||
}}
|
||||
</template>
|
||||
<template #title>
|
||||
<QChip :color="actionColor(log.action)">
|
||||
{{ t(`actions.${log.action}`) }}
|
||||
</QChip>
|
||||
{{ t(`models.${log.model}`) }}
|
||||
</template>
|
||||
<QTable
|
||||
:rows="log.changes"
|
||||
:columns="columns"
|
||||
row-key="property"
|
||||
hide-pagination
|
||||
dense
|
||||
flat
|
||||
>
|
||||
<template #header="props">
|
||||
<QTr :props="props">
|
||||
<QTh
|
||||
v-for="col in props.cols"
|
||||
:key="col.name"
|
||||
:props="props"
|
||||
>
|
||||
{{ t(col.label) }}
|
||||
</QTh>
|
||||
</QTr>
|
||||
</template>
|
||||
</QTable>
|
||||
</QTimelineEntry>
|
||||
</template>
|
||||
</template>
|
||||
</VnPaginate>
|
||||
</QTimeline>
|
||||
</div>
|
||||
<Teleport v-if="stateStore.isHeaderMounted()" 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>
|
||||
<QDrawer v-model="stateStore.rightDrawer" show-if-above side="right" :width="300">
|
||||
<QScrollArea class="fit text-grey-8">
|
||||
<ClaimLogFilter data-key="ClaimLogs" />
|
||||
</QScrollArea>
|
||||
</QDrawer>
|
||||
<VnLog model="Claim"></VnLog>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.q-timeline {
|
||||
width: 100%;
|
||||
max-width: 80em;
|
||||
}
|
||||
</style>
|
||||
<i18n>
|
||||
en:
|
||||
actions:
|
||||
insert: Creates
|
||||
update: Updates
|
||||
delete: Deletes
|
||||
models:
|
||||
Claim: Claim
|
||||
ClaimDms: Document
|
||||
ClaimBeginning: Claimed Sales
|
||||
ClaimObservation: Observation
|
||||
properties:
|
||||
id: ID
|
||||
claimFk: Claim ID
|
||||
saleFk: Sale ID
|
||||
quantity: Quantity
|
||||
observation: Observation
|
||||
ticketCreated: Created
|
||||
created: Created
|
||||
isChargedToMana: Charged to mana
|
||||
hasToPickUp: Has to pick Up
|
||||
dmsFk: Document ID
|
||||
text: Description
|
||||
claimStateFk: Claim State
|
||||
workerFk: Worker
|
||||
clientFk: Customer
|
||||
rma: RMA
|
||||
responsibility: Responsibility
|
||||
packages: Packages
|
||||
es:
|
||||
Audit logs: Registros de auditoría
|
||||
Property: Propiedad
|
||||
Before: Antes
|
||||
After: Después
|
||||
Yes: Si
|
||||
Nothing: Nada
|
||||
actions:
|
||||
insert: Crea
|
||||
update: Actualiza
|
||||
delete: Elimina
|
||||
models:
|
||||
Claim: Reclamación
|
||||
ClaimDms: Documento
|
||||
ClaimBeginning: Línea reclamada
|
||||
ClaimObservation: Observación
|
||||
properties:
|
||||
id: ID
|
||||
claimFk: ID reclamación
|
||||
saleFk: ID linea de venta
|
||||
quantity: Cantidad
|
||||
observation: Observación
|
||||
ticketCreated: Creado
|
||||
created: Creado
|
||||
isChargedToMana: Cargado a maná
|
||||
hasToPickUp: Se debe recoger
|
||||
dmsFk: ID documento
|
||||
text: Descripción
|
||||
claimStateFk: Estado de la reclamación
|
||||
workerFk: Trabajador
|
||||
clientFk: Cliente
|
||||
rma: RMA
|
||||
responsibility: Responsabilidad
|
||||
packages: Bultos
|
||||
</i18n>
|
||||
|
|
|
@ -0,0 +1,142 @@
|
|||
<script setup>
|
||||
import axios from 'axios';
|
||||
import { useQuasar } from 'quasar';
|
||||
import { computed, onMounted, onUpdated, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
const $props = defineProps({
|
||||
id: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
const entityId = computed(() => $props.id || route.params.id);
|
||||
|
||||
onMounted(() => fetch());
|
||||
onUpdated(() => fetch());
|
||||
|
||||
const route = useRoute();
|
||||
const { t } = useI18n();
|
||||
const quasar = useQuasar();
|
||||
|
||||
const notifications = ref([]);
|
||||
|
||||
async function fetch() {
|
||||
try {
|
||||
await axios
|
||||
.get(`NotificationSubscriptions/${entityId.value}/getList`)
|
||||
.then(async (res) => {
|
||||
if (res.data) {
|
||||
notifications.value = res.data;
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
async function disableNotification(notification) {
|
||||
await axios
|
||||
.delete(`NotificationSubscriptions/${notification.id}`)
|
||||
.catch(() => (notification.active = true))
|
||||
.then((res) => {
|
||||
if (res.data) {
|
||||
notification.id = null;
|
||||
notification.active = false;
|
||||
quasar.notify({
|
||||
type: 'positive',
|
||||
message: t('worker.notificationsManager.unsubscribed'),
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function toggleNotification(notification) {
|
||||
if (!notification.active) {
|
||||
await disableNotification(notification);
|
||||
} else {
|
||||
await axios
|
||||
.post(`NotificationSubscriptions`, {
|
||||
notificationFk: notification.notificationFk,
|
||||
userFk: entityId.value,
|
||||
})
|
||||
.catch(() => (notification.active = false))
|
||||
.then((res) => {
|
||||
if (res.data) {
|
||||
notification.id = res.data.id;
|
||||
quasar.notify({
|
||||
type: 'positive',
|
||||
message: t('worker.notificationsManager.subscribed'),
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<QPage>
|
||||
<QCard class="q-pa-md">
|
||||
<QList>
|
||||
<div
|
||||
v-show="
|
||||
notifications.filter(
|
||||
(notification) => notification.active == true
|
||||
).length
|
||||
"
|
||||
>
|
||||
<QItemLabel header class="text-h6">
|
||||
{{ t('worker.notificationsManager.activeNotifications') }}
|
||||
</QItemLabel>
|
||||
<QItem>
|
||||
<div
|
||||
v-for="notification in notifications.filter(
|
||||
(notification) => notification.active == true
|
||||
)"
|
||||
:key="notification.id"
|
||||
>
|
||||
<QChip
|
||||
:key="notification.id"
|
||||
:label="notification.name"
|
||||
text-color="white"
|
||||
color="primary"
|
||||
class="q-mr-sm"
|
||||
removable
|
||||
@remove="disableNotification(notification)"
|
||||
/>
|
||||
</div>
|
||||
</QItem>
|
||||
</div>
|
||||
<div v-show="notifications.length">
|
||||
<QItemLabel header class="text-h6">
|
||||
{{ t('worker.notificationsManager.availableNotifications') }}
|
||||
</QItemLabel>
|
||||
<div class="row">
|
||||
<QItem
|
||||
class="col-3"
|
||||
:key="notification.notificationFk"
|
||||
v-for="notification in notifications"
|
||||
>
|
||||
<QItemSection>
|
||||
<QItemLabel>{{ notification.name }}</QItemLabel>
|
||||
<QItemLabel caption>{{
|
||||
notification.description
|
||||
}}</QItemLabel>
|
||||
</QItemSection>
|
||||
<QItemSection side top>
|
||||
<QToggle
|
||||
checked-icon="check"
|
||||
unchecked-icon="close"
|
||||
indeterminate-icon="block"
|
||||
v-model="notification.active"
|
||||
@update:model-value="toggleNotification(notification)"
|
||||
/>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
</div>
|
||||
</div>
|
||||
</QList>
|
||||
</QCard>
|
||||
</QPage>
|
||||
</template>
|
|
@ -10,11 +10,11 @@ import { i18n } from 'src/boot/i18n';
|
|||
import { useState } from 'src/composables/useState';
|
||||
import { useSession } from 'src/composables/useSession';
|
||||
import { useRole } from 'src/composables/useRole';
|
||||
import { useUserConfig } from 'src/composables/useUserConfig';
|
||||
import { toLowerCamel } from 'src/filters';
|
||||
|
||||
const state = useState();
|
||||
const session = useSession();
|
||||
const role = useRole();
|
||||
const { t } = i18n.global;
|
||||
|
||||
const createHistory = process.env.SERVER
|
||||
|
@ -53,12 +53,13 @@ export default route(function (/* { store, ssrContext } */) {
|
|||
if (isLoggedIn()) {
|
||||
const stateRoles = state.getRoles().value;
|
||||
if (stateRoles.length === 0) {
|
||||
await role.fetch();
|
||||
await useRole().fetch();
|
||||
await useUserConfig().fetch();
|
||||
}
|
||||
const matches = to.matched;
|
||||
const hasRequiredRoles = matches.every((route) => {
|
||||
const meta = route.meta;
|
||||
if (meta && meta.roles) return role.hasAny(meta.roles);
|
||||
if (meta && meta.roles) return useRole().hasAny(meta.roles);
|
||||
return true;
|
||||
});
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ export default {
|
|||
redirect: { name: 'WorkerMain' },
|
||||
menus: {
|
||||
main: ['WorkerList'],
|
||||
card: [],
|
||||
// card: ['WorkerNotificationsManager'],
|
||||
},
|
||||
children: [
|
||||
{
|
||||
|
@ -42,9 +42,19 @@ export default {
|
|||
path: 'summary',
|
||||
meta: {
|
||||
title: 'summary',
|
||||
icon: 'launch',
|
||||
},
|
||||
component: () => import('src/pages/Worker/Card/WorkerSummary.vue'),
|
||||
},
|
||||
// {
|
||||
// name: 'WorkerNotificationsManager',
|
||||
// path: 'notifications',
|
||||
// meta: {
|
||||
// title: 'notifications',
|
||||
// icon: 'notifications',
|
||||
// },
|
||||
// component: () => import('src/pages/Worker/Card/WorkerNotificationsManager.vue'),
|
||||
// },
|
||||
],
|
||||
},
|
||||
],
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
describe('WorkerNotificationsManager', () => {
|
||||
beforeEach(() => {
|
||||
const workerId = 1110;
|
||||
cy.viewport(1280, 720);
|
||||
cy.login('salesBoss');
|
||||
cy.visit(`/#/worker/${workerId}/notifications`);
|
||||
});
|
||||
|
||||
it('should unsubscribe 2 notifications, check the unsubscription has been saved, subscribe to other one and should check the data has been saved', () => {
|
||||
cy.get('.q-chip').should('have.length', 3);
|
||||
cy.get('.q-toggle__thumb').eq(0).click();
|
||||
cy.get('.q-notification__message').should('have.text', 'Unsubscribed from the notification');
|
||||
cy.get('.q-chip > .q-icon').eq(0).click();
|
||||
|
||||
cy.reload();
|
||||
|
||||
cy.get('.q-chip').should('have.length', 1);
|
||||
cy.get('.q-toggle__thumb').should('have.length', 3).eq(0).click();
|
||||
cy.get('.q-notification__message').should('have.text', 'Subscribed to the notification');
|
||||
cy.get('.q-toggle__thumb').should('have.length', 3).eq(1).click();
|
||||
cy.get('.q-notification__message').should('have.text', 'Subscribed to the notification');
|
||||
|
||||
cy.reload();
|
||||
|
||||
cy.get('.q-chip').should('have.length', 3);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,75 @@
|
|||
import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest';
|
||||
import { createWrapper } from 'app/test/vitest/helper';
|
||||
import VnLog from 'src/components/common/VnLog.vue';
|
||||
|
||||
describe('VnLog', () => {
|
||||
let vm;
|
||||
beforeAll(() => {
|
||||
vm = createWrapper(VnLog, {
|
||||
global: {
|
||||
stubs: ['FetchData', 'VnPaginate'],
|
||||
mocks: {
|
||||
fetch: vi.fn(),
|
||||
},
|
||||
},
|
||||
propsData: {
|
||||
model: "Claim",
|
||||
},
|
||||
}).vm;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('formatValue()', () => {
|
||||
it('should return Yes if has a true boolean', async () => {
|
||||
const result = vm.formatValue(true);
|
||||
|
||||
expect(result).toEqual('Yes');
|
||||
});
|
||||
it('should return No if has a true boolean', async () => {
|
||||
const result = vm.formatValue(false);
|
||||
|
||||
expect(result).toEqual('No');
|
||||
});
|
||||
it('should return Nothing if has no params', async () => {
|
||||
const result = vm.formatValue();
|
||||
|
||||
expect(result).toEqual('Nothing');
|
||||
});
|
||||
it('should return a string from a string value', async () => {
|
||||
const result = vm.formatValue('Something');
|
||||
|
||||
expect(result).toEqual(`"Something"`);
|
||||
});
|
||||
it('should call to format a date', async () => {
|
||||
vi.mock('src/filters', () => ({
|
||||
toDate: ()=>{
|
||||
return "Date formatted"
|
||||
},
|
||||
}));
|
||||
|
||||
const result = vm.formatValue('01-01-1970');
|
||||
expect(result).toEqual("Date formatted");
|
||||
});
|
||||
});
|
||||
|
||||
describe('actionColor()', () => {
|
||||
it('should return positive if insert', async () => {
|
||||
const result = vm.actionColor('insert');
|
||||
|
||||
expect(result).toEqual('positive');
|
||||
});
|
||||
it('should return positive if update', async () => {
|
||||
const result = vm.actionColor('update');
|
||||
|
||||
expect(result).toEqual('positive');
|
||||
});
|
||||
it('should return negative if delete', async () => {
|
||||
const result = vm.actionColor('delete');
|
||||
|
||||
expect(result).toEqual('negative');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -23,21 +23,18 @@ describe('useRole', () => {
|
|||
name: `T'Challa`,
|
||||
nickname: 'Black Panther',
|
||||
lang: 'en',
|
||||
userConfig: {
|
||||
darkMode: false,
|
||||
},
|
||||
};
|
||||
const expectedUser = {
|
||||
id: 999,
|
||||
name: `T'Challa`,
|
||||
nickname: 'Black Panther',
|
||||
lang: 'en',
|
||||
darkMode: false,
|
||||
};
|
||||
const expectedRoles = ['salesPerson', 'admin'];
|
||||
vi.spyOn(axios, 'get').mockResolvedValue({
|
||||
vi.spyOn(axios, 'get')
|
||||
.mockResolvedValueOnce({
|
||||
data: { roles: rolesData, user: fetchedUser },
|
||||
});
|
||||
})
|
||||
|
||||
vi.spyOn(role.state, 'setUser');
|
||||
vi.spyOn(role.state, 'setRoles');
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest';
|
||||
import { createWrapper, axios } from 'app/test/vitest/helper';
|
||||
import WorkerNotificationsManager from 'src/pages/Worker/Card/WorkerNotificationsManager.vue';
|
||||
|
||||
describe('WorkerNotificationsManager', () => {
|
||||
let vm;
|
||||
const entityId = 1110;
|
||||
|
||||
beforeAll(() => {
|
||||
vm = createWrapper(WorkerNotificationsManager, {
|
||||
propsData: {
|
||||
id: entityId,
|
||||
},
|
||||
}).vm;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('fetch()', () => {
|
||||
it('should fetch notification subscriptions and role mappings', async () => {
|
||||
vi.spyOn(axios, 'get')
|
||||
.mockResolvedValueOnce({
|
||||
data: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Name 1',
|
||||
description: 'Description 1',
|
||||
notificationFk: 1,
|
||||
active: true
|
||||
},
|
||||
],
|
||||
});
|
||||
await vm.fetch();
|
||||
|
||||
expect(axios.get).toHaveBeenCalledWith(`NotificationSubscriptions/${entityId}/getList`);
|
||||
expect(vm.notifications).toEqual([
|
||||
{
|
||||
id: 1,
|
||||
notificationFk: 1,
|
||||
name: 'Name 1',
|
||||
description: 'Description 1',
|
||||
active: true,
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('disableNotification()', () => {
|
||||
it('should disable the notification', async () => {
|
||||
vi.spyOn(axios, 'delete').mockResolvedValue({ data: { count: 1 } });
|
||||
vi.spyOn(vm.quasar, 'notify');
|
||||
const subscriptionId = 1;
|
||||
vm.notifications = [{ id: 1, active: true }];
|
||||
|
||||
await vm.disableNotification(vm.notifications[0]);
|
||||
|
||||
expect(axios.delete).toHaveBeenCalledWith(
|
||||
`NotificationSubscriptions/${subscriptionId}`
|
||||
);
|
||||
expect(vm.notifications[0].id).toBeNull();
|
||||
expect(vm.notifications[0].id).toBeFalsy();
|
||||
expect(vm.quasar.notify).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ type: 'positive' })
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toggleNotification()', () => {
|
||||
it('should activate the notification', async () => {
|
||||
vi.spyOn(axios, 'post').mockResolvedValue({
|
||||
data: { id: 1, notificationFk: 1 },
|
||||
});
|
||||
vm.notifications = [{ id: null, active: true, notificationFk: 1 }];
|
||||
|
||||
await vm.toggleNotification(vm.notifications[0]);
|
||||
|
||||
expect(axios.post).toHaveBeenCalledWith('NotificationSubscriptions', {
|
||||
notificationFk: 1,
|
||||
userFk: entityId,
|
||||
});
|
||||
expect(vm.notifications[0].id).toBe(1);
|
||||
expect(vm.notifications[0].active).toBeTruthy();
|
||||
expect(vm.quasar.notify).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ type: 'positive' })
|
||||
);
|
||||
});
|
||||
|
||||
it('should disable the notification', async () => {
|
||||
vi.spyOn(vm, 'disableNotification');
|
||||
vm.notifications = [{ id: 1, active: false, notificationFk: 1 }];
|
||||
|
||||
await vm.toggleNotification(vm.notifications[0]);
|
||||
|
||||
expect(vm.notifications[0].id).toBe(null);
|
||||
expect(vm.notifications[0].active).toBeFalsy();
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue