diff --git a/.vscode/settings.json b/.vscode/settings.json index ecc1d50d74..5026b7d3bf 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -13,5 +13,6 @@ ], "[vue]": { "editor.defaultFormatter": "esbenp.prettier-vscode" - } + }, + "cSpell.words": ["axios"] } diff --git a/CHANGELOG.md b/CHANGELOG.md index 243d67a349..3316aa4418 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2352.01] - 2023-12-28 + +### Added +- (carros) => Se añade contador de carros. #6545 +- (Reclamaciones) => Se añade la sección para hacer acciones sobre una reclamación. #5654 +### Changed +### Fixed +- (Reclamaciones) => Se corrige el color de la barra según el tema y el evento de actualziar cantidades #6334 + + ## [2253.01] - 2023-01-05 ### Added diff --git a/package.json b/package.json index 3e26b483be..583233204d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "salix-front", - "version": "23.40.01", + "version": "23.52.01", "description": "Salix frontend", "productName": "Salix", "author": "Verdnatura", @@ -15,7 +15,7 @@ "test:unit:ci": "vitest run" }, "dependencies": { - "@quasar/cli": "^2.2.1", + "@quasar/cli": "^2.3.0", "@quasar/extras": "^1.16.4", "axios": "^1.4.0", "chromium": "^3.0.3", diff --git a/quasar.config.js b/quasar.config.js index cbcbae4dc7..3a7dc1f1e6 100644 --- a/quasar.config.js +++ b/quasar.config.js @@ -66,7 +66,9 @@ module.exports = configure(function (/* ctx */) { // publicPath: '/', // analyze: true, // env: {}, - // rawDefine: {} + rawDefine: { + 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV) + }, // ignorePublicFolder: true, // minify: false, // polyfillModulePreload: true, @@ -89,11 +91,12 @@ module.exports = configure(function (/* ctx */) { vitePlugins: [ [ - VueI18nPlugin, + VueI18nPlugin({ + runtimeOnly: false + }), { // if you want to use Vue I18n Legacy API, you need to set `compositionOnly: false` // compositionOnly: false, - // you need to set i18n resource including paths ! include: path.resolve(__dirname, './src/i18n/**'), }, diff --git a/src/assets/logo.svg b/src/assets/salix.svg similarity index 100% rename from src/assets/logo.svg rename to src/assets/salix.svg diff --git a/src/assets/salix_dark.svg b/src/assets/salix_dark.svg new file mode 100644 index 0000000000..10de3af4fe --- /dev/null +++ b/src/assets/salix_dark.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/assets/logo_icon.svg b/src/assets/salix_icon.svg similarity index 100% rename from src/assets/logo_icon.svg rename to src/assets/salix_icon.svg diff --git a/src/assets/vn.svg b/src/assets/vn.svg new file mode 100644 index 0000000000..23b6df49cc --- /dev/null +++ b/src/assets/vn.svg @@ -0,0 +1,158 @@ + +image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/vn_dark.svg b/src/assets/vn_dark.svg new file mode 100644 index 0000000000..4d53b7b397 --- /dev/null +++ b/src/assets/vn_dark.svg @@ -0,0 +1,161 @@ + +image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/vn_icon.svg b/src/assets/vn_icon.svg new file mode 100644 index 0000000000..738c8157c2 --- /dev/null +++ b/src/assets/vn_icon.svg @@ -0,0 +1,72 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/src/components/CrudModel.vue b/src/components/CrudModel.vue index 2a4982fceb..a5156dc79a 100644 --- a/src/components/CrudModel.vue +++ b/src/components/CrudModel.vue @@ -89,6 +89,7 @@ async function fetch(data) { watch(formData, () => (hasChanges.value = true), { deep: true }); emit('onFetch', data); + return data; } async function reset() { @@ -268,7 +269,7 @@ watch(formUrl, async () => { - + { {{ t('globals.changesToSave') }} - - - + + + + + + + @@ -156,3 +171,12 @@ watch(formUrl, async () => { color="primary" /> + diff --git a/src/components/NavBar.vue b/src/components/NavBar.vue index 7d09b09b87..2368e078e3 100644 --- a/src/components/NavBar.vue +++ b/src/components/NavBar.vue @@ -7,6 +7,7 @@ import { useStateStore } from 'stores/useStateStore'; import { useQuasar } from 'quasar'; import PinnedModules from './PinnedModules.vue'; import UserPanel from 'components/UserPanel.vue'; +import VnBreadcrumbs from './common/VnBreadcrumbs.vue'; const { t } = useI18n(); const session = useSession(); @@ -24,30 +25,20 @@ const pinnedModulesRef = ref(); - - + + {{ t('globals.collapseMenu') }} - + @@ -57,10 +48,7 @@ const pinnedModulesRef = ref(); - - {{ appName }} - - + @@ -112,6 +100,7 @@ const pinnedModulesRef = ref(); + diff --git a/src/components/common/VnBreadcrumbs.vue b/src/components/common/VnBreadcrumbs.vue new file mode 100644 index 0000000000..792671e213 --- /dev/null +++ b/src/components/common/VnBreadcrumbs.vue @@ -0,0 +1,82 @@ + + + + + + + + + + diff --git a/src/components/common/VnSelectFilter.vue b/src/components/common/VnSelectFilter.vue index 55395f260e..2a3ee6161e 100644 --- a/src/components/common/VnSelectFilter.vue +++ b/src/components/common/VnSelectFilter.vue @@ -4,7 +4,7 @@ const emit = defineEmits(['update:modelValue', 'update:options']); const $props = defineProps({ modelValue: { - type: [String, Number], + type: [String, Number, Object], default: null, }, options: { @@ -15,6 +15,10 @@ const $props = defineProps({ type: String, default: '', }, + isClearable: { + type: Boolean, + default: true, + }, }); const { optionLabel, options } = toRefs($props); const myOptions = ref([]); @@ -81,11 +85,10 @@ const value = computed({ map-options use-input @filter="filterHandler" - hide-selected fill-input ref="vnSelectRef" > - + diff --git a/src/components/ui/CardDescriptor.vue b/src/components/ui/CardDescriptor.vue index f63b75de62..eef2e020be 100644 --- a/src/components/ui/CardDescriptor.vue +++ b/src/components/ui/CardDescriptor.vue @@ -1,8 +1,9 @@ diff --git a/src/components/ui/CardList.vue b/src/components/ui/CardList.vue index ed1df7668d..af2398ebdd 100644 --- a/src/components/ui/CardList.vue +++ b/src/components/ui/CardList.vue @@ -32,7 +32,7 @@ const $props = defineProps({ gap: 2%; width: 50%; .label { - width: 30%; + width: 35%; color: var(--vn-label); overflow: hidden; text-overflow: ellipsis; diff --git a/src/components/ui/VnFilterPanel.vue b/src/components/ui/VnFilterPanel.vue index 68b3a76766..55e4328572 100644 --- a/src/components/ui/VnFilterPanel.vue +++ b/src/components/ui/VnFilterPanel.vue @@ -20,6 +20,10 @@ const props = defineProps({ required: false, default: null, }, + showAll: { + type: Boolean, + default: true, + }, }); const emit = defineEmits(['refresh', 'clear']); @@ -29,31 +33,30 @@ const store = arrayData.store; const userParams = ref({}); onMounted(() => { - if (props.params) userParams.value = props.params; - const params = store.userParams; - if (Object.keys(params).length > 0) { - userParams.value = Object.assign({}, params); + if (props.params) userParams.value = JSON.parse(JSON.stringify(props.params)); + if (Object.keys(store.userParams).length > 0) { + userParams.value = JSON.parse(JSON.stringify(store.userParams)); } }); const isLoading = ref(false); async function search() { - const params = userParams.value; - for (const param in params) { - if (params[param] === '' || params[param] === null) { - delete userParams.value[param]; - delete store.userParams[param]; - } - } - isLoading.value = true; - await arrayData.addFilter({ params }); + const params = { ...userParams.value }; + const { params: newParams } = await arrayData.addFilter({ params }); + userParams.value = newParams; + + if (!props.showAll && !Object.values(params).length) store.data = []; + isLoading.value = false; } async function reload() { isLoading.value = true; + const params = Object.values(userParams.value).filter((param) => param); + await arrayData.fetch({ append: false }); + if (!props.showAll && !params.length) store.data = []; isLoading.value = false; emit('refresh'); } @@ -62,6 +65,7 @@ async function clearFilters() { userParams.value = {}; isLoading.value = true; await arrayData.applyFilter({ params: {} }); + if (!props.showAll) store.data = []; isLoading.value = false; emit('clear'); @@ -70,10 +74,11 @@ async function clearFilters() { const tags = computed(() => { const params = []; - for (const param in store.userParams) { + for (const param in userParams.value) { + if (!userParams.value[param]) continue; params.push({ label: param, - value: store.userParams[param], + value: userParams.value[param], }); } @@ -81,8 +86,7 @@ const tags = computed(() => { }); async function remove(key) { - delete userParams.value[key]; - delete store.userParams[key]; + userParams.value[key] = null; await search(); } @@ -192,7 +196,7 @@ function formatValue(value) { -es: +es: No filters applied: No se han aplicado filtros Applied filters: Filtros aplicados Remove filters: Eliminar filtros diff --git a/src/components/ui/VnLinkPhone.vue b/src/components/ui/VnLinkPhone.vue new file mode 100644 index 0000000000..4445b99c9b --- /dev/null +++ b/src/components/ui/VnLinkPhone.vue @@ -0,0 +1,22 @@ + + + + + diff --git a/src/components/ui/VnLogo.vue b/src/components/ui/VnLogo.vue new file mode 100644 index 0000000000..3b955289d9 --- /dev/null +++ b/src/components/ui/VnLogo.vue @@ -0,0 +1,24 @@ + + + + + diff --git a/src/components/ui/VnPaginate.vue b/src/components/ui/VnPaginate.vue index d21d073f23..c757614628 100644 --- a/src/components/ui/VnPaginate.vue +++ b/src/components/ui/VnPaginate.vue @@ -50,6 +50,10 @@ const props = defineProps({ type: Boolean, default: true, }, + exprBuilder: { + type: Function, + default: null, + }, }); const emit = defineEmits(['onFetch', 'onPaginate']); @@ -68,6 +72,7 @@ const arrayData = useArrayData(props.dataKey, { limit: props.limit, order: props.order, userParams: props.userParams, + exprBuilder: props.exprBuilder, }); const store = arrayData.store; diff --git a/src/components/ui/VnRow.vue b/src/components/ui/VnRow.vue new file mode 100644 index 0000000000..c3c951528f --- /dev/null +++ b/src/components/ui/VnRow.vue @@ -0,0 +1,12 @@ + + + + + + diff --git a/src/components/ui/VnSearchbar.vue b/src/components/ui/VnSearchbar.vue index af6999b5b5..693d6fce26 100644 --- a/src/components/ui/VnSearchbar.vue +++ b/src/components/ui/VnSearchbar.vue @@ -105,7 +105,11 @@ async function search() { class="cursor-pointer" /> - + {{ props.info }} diff --git a/src/composables/getUrl.js b/src/composables/getUrl.js index e020d7f18e..f2bd9ddb93 100644 --- a/src/composables/getUrl.js +++ b/src/composables/getUrl.js @@ -1,14 +1,11 @@ import axios from 'axios'; export async function getUrl(route, appName = 'salix') { - let url; const filter = { where: { and: [{ appName: appName }, { environment: process.env.NODE_ENV }] }, }; - await axios.get('Urls/findOne', { params: { filter } }).then((res) => { - url = res.data.url + route; - }); - - return url; + const { data } = await axios.get('Urls/findOne', { params: { filter } }); + const url = data.url; + return route ? url + route : url; } diff --git a/src/composables/useArrayData.js b/src/composables/useArrayData.js index 4535cde0f6..6fcca47220 100644 --- a/src/composables/useArrayData.js +++ b/src/composables/useArrayData.js @@ -2,6 +2,7 @@ import { onMounted, ref, computed } from 'vue'; import { useRouter, useRoute } from 'vue-router'; import axios from 'axios'; import { useArrayDataStore } from 'stores/useArrayDataStore'; +import { buildFilter } from 'filters/filterPanel'; const arrayDataStore = useArrayDataStore(); @@ -29,6 +30,10 @@ export function useArrayData(key, userOptions) { } }); + if (key && userOptions) { + setOptions(); + } + function setOptions() { const allowedOptions = [ 'url', @@ -39,10 +44,11 @@ export function useArrayData(key, userOptions) { 'skip', 'userParams', 'userFilter', + 'exprBuilder', ]; if (typeof userOptions === 'object') { for (const option in userOptions) { - const isEmpty = userOptions[option] == null || userOptions[option] == ''; + const isEmpty = userOptions[option] == null || userOptions[option] === ''; if (isEmpty || !allowedOptions.includes(option)) continue; if (Object.prototype.hasOwnProperty.call(store, option)) { @@ -64,16 +70,27 @@ export function useArrayData(key, userOptions) { skip: store.skip, }; - Object.assign(filter, store.userFilter); - Object.assign(store.filter, filter); + let exprFilter; + let userParams = { ...store.userParams }; + if (store?.exprBuilder) { + const where = buildFilter(userParams, (param, value) => { + const res = store.exprBuilder(param, value); + if (res) delete userParams[param]; + return res; + }); + exprFilter = where ? { where } : null; + } + Object.assign(filter, store.userFilter, exprFilter); + Object.assign(store.filter, filter); const params = { filter: JSON.stringify(store.filter), }; - Object.assign(params, store.userParams); + Object.assign(params, userParams); store.isLoading = true; + const response = await axios.get(store.url, { signal: canceller.signal, params, @@ -97,6 +114,7 @@ export function useArrayData(key, userOptions) { store.isLoading = false; canceller = null; + return response; } function destroy() { @@ -121,9 +139,30 @@ export function useArrayData(key, userOptions) { async function addFilter({ filter, params }) { if (filter) store.userFilter = Object.assign(store.userFilter, filter); - if (params) store.userParams = Object.assign(store.userParams, params); + + let userParams = Object.assign({}, store.userParams, params); + userParams = sanitizerParams(userParams, store?.exprBuilder); + + store.userParams = userParams; await fetch({ append: false }); + return { filter, params }; + } + + function sanitizerParams(params) { + for (const param in params) { + if (params[param] === '' || params[param] === null) { + delete store.userParams[param]; + delete params[param]; + if (store.filter?.where) { + delete store.filter.where[Object.keys(store?.exprBuilder(param))[0]]; + if (Object.keys(store.filter.where).length === 0) { + delete store.filter.where; + } + } + } + } + return params; } async function loadMore() { @@ -136,7 +175,7 @@ export function useArrayData(key, userOptions) { } async function refresh() { - await fetch({ append: false }); + if (Object.values(store.userParams).length) await fetch({ append: false }); } function updateStateParams() { @@ -147,10 +186,11 @@ export function useArrayData(key, userOptions) { if (store.userParams && Object.keys(store.userParams).length !== 0) query.params = JSON.stringify(store.userParams); - router.replace({ - path: route.path, - query: query, - }); + if (router) + router.replace({ + path: route.path, + query: query, + }); } const totalRows = computed(() => (store.data && store.data.length) || 0); diff --git a/src/composables/useCamelCase.js b/src/composables/useCamelCase.js new file mode 100644 index 0000000000..5285b022a3 --- /dev/null +++ b/src/composables/useCamelCase.js @@ -0,0 +1,3 @@ +export function useCamelCase(value) { + return value.replace(/[-_](.)/g, (_, char) => char.toUpperCase()); +} diff --git a/src/composables/useFirstUpper.js b/src/composables/useFirstUpper.js new file mode 100644 index 0000000000..36378c05fc --- /dev/null +++ b/src/composables/useFirstUpper.js @@ -0,0 +1,3 @@ +export function useFirstUpper(str) { + return str && str.charAt(0).toUpperCase() + str.substr(1); +} diff --git a/src/css/app.scss b/src/css/app.scss index 0f04c9ad81..99170202d9 100644 --- a/src/css/app.scss +++ b/src/css/app.scss @@ -45,3 +45,9 @@ body.body--dark { .bg-vn-dark { background-color: var(--vn-dark); } + +.vn-card { + background-color: var(--vn-gray); + color: var(--vn-text); + border-radius: 8px; +} diff --git a/src/css/quasar.variables.scss b/src/css/quasar.variables.scss index 808bf3468d..6a37005617 100644 --- a/src/css/quasar.variables.scss +++ b/src/css/quasar.variables.scss @@ -21,6 +21,7 @@ $positive: #21ba45; $negative: #c10015; $info: #31ccec; $warning: #f2c037; +$vnColor: #8ebb27; // Pendiente de cuadrar con la base de datos $success: $positive; diff --git a/src/filters/filterPanel.js b/src/filters/filterPanel.js new file mode 100644 index 0000000000..ee91e67491 --- /dev/null +++ b/src/filters/filterPanel.js @@ -0,0 +1,94 @@ +/** + * Passes a loopback fields filter to an object. + * + * @param {Object} fields The fields object or array + * @return {Object} The fields as object + */ +function fieldsToObject(fields) { + let fieldsObj = {}; + + if (Array.isArray(fields)) { + for (let field of fields) fieldsObj[field] = true; + } else if (typeof fields == 'object') { + for (let field in fields) { + if (fields[field]) fieldsObj[field] = true; + } + } + + return fieldsObj; +} + +/** + * Merges two loopback fields filters. + * + * @param {Object|Array} src The source fields + * @param {Object|Array} dst The destination fields + * @return {Array} The merged fields as an array + */ +function mergeFields(src, dst) { + let fields = {}; + Object.assign(fields, fieldsToObject(src), fieldsToObject(dst)); + return Object.keys(fields); +} + +/** + * Merges two loopback where filters. + * + * @param {Object|Array} src The source where + * @param {Object|Array} dst The destination where + * @return {Array} The merged wheres + */ +function mergeWhere(src, dst) { + let and = []; + if (src) and.push(src); + if (dst) and.push(dst); + return simplifyOperation(and, 'and'); +} + +/** + * Merges two loopback filters returning the merged filter. + * + * @param {Object} src The source filter + * @param {Object} dst The destination filter + * @return {Object} The result filter + */ +function mergeFilters(src, dst) { + let res = Object.assign({}, dst); + + if (!src) return res; + + if (src.fields) res.fields = mergeFields(src.fields, res.fields); + if (src.where) res.where = mergeWhere(res.where, src.where); + if (src.include) res.include = src.include; + if (src.order) res.order = src.order; + if (src.limit) res.limit = src.limit; + if (src.offset) res.offset = src.offset; + if (src.skip) res.skip = src.skip; + + return res; +} + +function simplifyOperation(operation, operator) { + switch (operation.length) { + case 0: + return undefined; + case 1: + return operation[0]; + default: + return { [operator]: operation }; + } +} + +function buildFilter(params, builderFunc) { + let and = []; + + for (let param in params) { + let value = params[param]; + if (value == null) continue; + let expr = builderFunc(param, value); + if (expr) and.push(expr); + } + return simplifyOperation(and, 'and'); +} + +export { fieldsToObject, mergeFields, mergeWhere, mergeFilters, buildFilter }; diff --git a/src/i18n/en/index.js b/src/i18n/en/index.js index cfd20716ba..ea80c79188 100644 --- a/src/i18n/en/index.js +++ b/src/i18n/en/index.js @@ -36,6 +36,7 @@ export default { summary: { basicData: 'Basic data', }, + microsip: 'Open in MicroSIP', noSelectedRows: `You don't have any line selected`, }, errors: { @@ -68,6 +69,11 @@ export default { twoFactor: 'Two-Factor', }, }, + verifyEmail: { + pageTitles: { + verifyEmail: 'Email verification', + }, + }, dashboard: { pageTitles: { dashboard: 'Dashboard', @@ -269,6 +275,7 @@ export default { development: 'Development', log: 'Audit logs', notes: 'Notes', + action: 'Action', }, list: { customer: 'Customer', @@ -443,6 +450,7 @@ export default { typesList: 'Types List', typeCreate: 'Create type', typeEdit: 'Edit type', + wagonCounter: 'Trolley counter', }, type: { name: 'Name', diff --git a/src/i18n/es/index.js b/src/i18n/es/index.js index 532c1bb3b8..b18dee96ea 100644 --- a/src/i18n/es/index.js +++ b/src/i18n/es/index.js @@ -37,6 +37,7 @@ export default { basicData: 'Datos básicos', }, noSelectedRows: `No tienes ninguna línea seleccionada`, + microsip: 'Abrir en MicroSIP', }, errors: { statusUnauthorized: 'Acceso denegado', @@ -68,6 +69,11 @@ export default { twoFactor: 'Doble factor', }, }, + verifyEmail: { + pageTitles: { + verifyEmail: 'Verificación de correo', + }, + }, dashboard: { pageTitles: { dashboard: 'Tablón', @@ -268,6 +274,7 @@ export default { photos: 'Fotos', log: 'Registros de auditoría', notes: 'Notas', + action: 'Acción', }, list: { customer: 'Cliente', @@ -443,6 +450,7 @@ export default { typesList: 'Listado tipos', typeCreate: 'Crear tipo', typeEdit: 'Editar tipo', + wagonCounter: 'Contador de carros', }, type: { name: 'Nombre', diff --git a/src/layouts/OutLayout.vue b/src/layouts/OutLayout.vue index 6ea14622f6..f21e6e5689 100644 --- a/src/layouts/OutLayout.vue +++ b/src/layouts/OutLayout.vue @@ -80,7 +80,7 @@ const langs = ['en', 'es']; - + @@ -97,15 +97,4 @@ const langs = ['en', 'es']; min-height: inherit; flex-direction: column; } - -.formCard { - max-width: 350px; - min-width: 300px; -} - -@media (max-width: $breakpoint-xs-max) { - .formCard { - min-width: 100%; - } -} diff --git a/src/pages/Claim/Card/ClaimAction.vue b/src/pages/Claim/Card/ClaimAction.vue new file mode 100644 index 0000000000..44575f1d6f --- /dev/null +++ b/src/pages/Claim/Card/ClaimAction.vue @@ -0,0 +1,518 @@ + + + (claim = data)" + auto-load + /> + (resolvedStateId = data.id)" + auto-load + :where="{ code: 'resolved' }" + /> + (destinationTypes = data)" + /> + + + + + + {{ t('globals.collapseMenu') }} + + + + + + + + {{ `${t('Total claimed')}: ${toCurrency(totalClaimed)}` }} + + + + + + {{ t('claim.summary.actions') }} + + save({ responsibility: value })" + label-always + color="primary" + markers + :marker-labels="marker_labels" + :min="DEFAULT_MIN_RESPONSABILITY" + :max="DEFAULT_MAX_RESPONSABILITY" + /> + + + + + save({ isChargedToMana: value })" + /> + {{ t('mana') }} + + + + + + + + + + {{ value }} + + + + + + + updateDestination(value, row)" + /> + + + + + {{ toCurrency(value) }} + + + + + {{ toCurrency(value) }} + + + + + + + + + + + + + + + + + {{ column.label }} + + + + + {{ column.value.description }} + + + {{ column.value }} + + + + + + + + + + + + + + + + + + + + + + {{ t('dialog title') }} + + + + + + + + + + + + + + + + + +en: + mana: Is paid with mana + dialog title: Change destination to all selected rows + confirmGreuges: Do you want to insert complaints? + confirmGreugesMessage: Insert complaints into the client's record + +es: + mana: Cargado al maná + Delivered: Descripción + Quantity: Cantidad + Claimed: Rec + Description: Descripción + Price: Precio + Discount: Dto. + Destination: Destino + Landed: F.entrega + Remove line: Eliminar línea + Total claimed: Total reclamado + Regularize: Regularizar + Change destination: Cambiar destino + Import claim: Importar reclamación + dialog title: Cambiar destino en todas las filas seleccionadas + Remove: Eliminar + dialogGreuge title: Insertar greuges en la ficha del cliente + ClaimGreugeDescription: Id reclamación + Id item: Id artículo + confirmGreuges: ¿Desea insertar greuges? + confirmGreugesMessage: Insertar greuges en la ficha del cliente + diff --git a/src/pages/Claim/Card/ClaimBasicData.vue b/src/pages/Claim/Card/ClaimBasicData.vue index ddf669dd01..94e447e132 100644 --- a/src/pages/Claim/Card/ClaimBasicData.vue +++ b/src/pages/Claim/Card/ClaimBasicData.vue @@ -6,6 +6,7 @@ import { useI18n } from 'vue-i18n'; import { useSession } from 'src/composables/useSession'; import FetchData from 'components/FetchData.vue'; import FormModel from 'components/FormModel.vue'; +import VnRow from 'components/ui/VnRow.vue'; const route = useRoute(); const { t } = useI18n(); @@ -90,138 +91,119 @@ const statesFilter = { auto-load /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - filter(value, update, workerFilter) - " - :rules="validate('claim.claimStateFk')" - :input-debounce="0" - > - - - - - - - - - filter(value, update, statesFilter) - " - :rules="validate('claim.claimStateFk')" - :input-debounce="0" - > - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + filter(value, update, workerFilter)" + :rules="validate('claim.claimStateFk')" + :input-debounce="0" + > + + + + + + + + + filter(value, update, statesFilter)" + :rules="validate('claim.claimStateFk')" + :input-debounce="0" + > + + + + + + + + + + + + + + + + + + - - diff --git a/src/pages/Claim/Card/ClaimCard.vue b/src/pages/Claim/Card/ClaimCard.vue index 03b9889f00..a8c8329677 100644 --- a/src/pages/Claim/Card/ClaimCard.vue +++ b/src/pages/Claim/Card/ClaimCard.vue @@ -22,11 +22,6 @@ const $props = defineProps({ const entityId = computed(() => { return $props.id || route.params.id; }); - -let salixUrl; -onMounted(async () => { - salixUrl = await getUrl(`claim/${entityId.value}`); -}); @@ -42,18 +37,6 @@ onMounted(async () => { - - - - - {{ t('Action') }} - - diff --git a/src/pages/Claim/Card/ClaimDescriptor.vue b/src/pages/Claim/Card/ClaimDescriptor.vue index af7e84d38d..9dd8aa1f60 100644 --- a/src/pages/Claim/Card/ClaimDescriptor.vue +++ b/src/pages/Claim/Card/ClaimDescriptor.vue @@ -62,13 +62,18 @@ const filter = { ], }; +const STATE_COLOR = { + pending: 'positive', + managed: 'warning', + resolved: 'negative', +}; + function stateColor(code) { - if (code === 'pending') return 'positive'; - if (code === 'managed') return 'warning'; - if (code === 'resolved') return 'negative'; + return STATE_COLOR[code]; } const data = ref(useCardDescription()); const setData = (entity) => { + if (!entity) return; data.value = useCardDescription(entity.client.name, entity.id); state.set('ClaimDescriptor', entity); }; @@ -83,6 +88,7 @@ const setData = (entity) => { :title="data.title" :subtitle="data.subtitle" @on-fetch="setData" + data-key="claimData" > @@ -120,16 +126,16 @@ const setData = (entity) => { - {{ entity.client.salesPersonUser.name }} - + {{ entity.client?.salesPersonUser?.name }} + - + diff --git a/src/pages/Claim/Card/ClaimDevelopment.vue b/src/pages/Claim/Card/ClaimDevelopment.vue index 0c83bdaddf..0ab3c6c90c 100644 --- a/src/pages/Claim/Card/ClaimDevelopment.vue +++ b/src/pages/Claim/Card/ClaimDevelopment.vue @@ -7,6 +7,7 @@ import FetchData from 'components/FetchData.vue'; import VnSelectFilter from 'components/common/VnSelectFilter.vue'; import { getUrl } from 'composables/getUrl'; import { tMobile } from 'composables/tMobile'; +import router from 'src/router'; const route = useRoute(); @@ -19,7 +20,7 @@ const claimResponsibles = ref([]); const claimRedeliveries = ref([]); const workers = ref([]); const selected = ref([]); -const insertButtonRef = ref(); +const saveButtonRef = ref(); let salixUrl; onMounted(async () => { @@ -102,10 +103,6 @@ const columns = computed(() => [ tabIndex: 5, }, ]); - -function goToAction() { - location.href = `${salixUrl}/action`; -} @@ -175,6 +172,7 @@ function goToAction() { :option-label="col.optionLabel" :autofocus="col.tabIndex == 1" input-debounce="0" + hide-selected > @@ -213,6 +211,7 @@ function goToAction() { dense input-debounce="0" :autofocus="col.tabIndex == 1" + hide-selected /> @@ -236,13 +235,11 @@ function goToAction() { @@ -251,9 +248,6 @@ function goToAction() { .grid-style-transition { transition: transform 0.28s, background-color 0.28s; } -.maxwidth { - width: 100%; -} diff --git a/src/pages/Claim/Card/ClaimLines.vue b/src/pages/Claim/Card/ClaimLines.vue index c03291b857..fa7fb123f3 100644 --- a/src/pages/Claim/Card/ClaimLines.vue +++ b/src/pages/Claim/Card/ClaimLines.vue @@ -46,7 +46,7 @@ async function onFetchClaim(data) { const amount = ref(0); const amountClaimed = ref(0); async function onFetch(rows) { - if (!rows || rows.length) return; + if (!rows || !rows.length) return; amount.value = rows.reduce( (acumulator, { sale }) => acumulator + sale.price * sale.quantity, 0 @@ -155,7 +155,7 @@ function showImportDialog() { - + {{ t('Amount') }} diff --git a/src/pages/Claim/Card/ClaimSummary.vue b/src/pages/Claim/Card/ClaimSummary.vue index dc5ec95446..64d7b721be 100644 --- a/src/pages/Claim/Card/ClaimSummary.vue +++ b/src/pages/Claim/Card/ClaimSummary.vue @@ -85,10 +85,15 @@ const detailsColumns = ref([ }, ]); +const STATE_COLOR = { + pending: 'positive', + + managed: 'warning', + + resolved: 'negative', +}; function stateColor(code) { - if (code === 'pending') return 'green'; - if (code === 'managed') return 'orange'; - if (code === 'resolved') return 'red'; + return STATE_COLOR[code]; } const developmentColumns = ref([ diff --git a/src/pages/Claim/ClaimFilter.vue b/src/pages/Claim/ClaimFilter.vue index 5918712fd5..ac8f4f0dbf 100644 --- a/src/pages/Claim/ClaimFilter.vue +++ b/src/pages/Claim/ClaimFilter.vue @@ -3,6 +3,7 @@ import { ref } from 'vue'; import { useI18n } from 'vue-i18n'; import FetchData from 'components/FetchData.vue'; import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue'; +import VnSelectFilter from 'components/common/VnSelectFilter.vue'; const { t } = useI18n(); const props = defineProps({ @@ -60,7 +61,7 @@ const states = ref(); - - - - (businessTypes = data)" auto-load /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - filter(value, update, filterOptions) - " - :rules="validate('client.salesPersonFk')" - :input-debounce="0" - > - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + filter(value, update, filterOptions)" + :rules="validate('client.salesPersonFk')" + :input-debounce="0" + > + + + + + + + + + + + + + + diff --git a/src/pages/Customer/Card/CustomerDescriptor.vue b/src/pages/Customer/Card/CustomerDescriptor.vue index 23e6228ee4..de398be744 100644 --- a/src/pages/Customer/Card/CustomerDescriptor.vue +++ b/src/pages/Customer/Card/CustomerDescriptor.vue @@ -34,6 +34,7 @@ const setData = (entity) => (data.value = useCardDescription(entity.name, entity :title="data.title" :subtitle="data.subtitle" @on-fetch="setData" + data-key="customerData" > diff --git a/src/pages/Customer/Card/CustomerSummary.vue b/src/pages/Customer/Card/CustomerSummary.vue index 081bdd157b..6693274ac9 100644 --- a/src/pages/Customer/Card/CustomerSummary.vue +++ b/src/pages/Customer/Card/CustomerSummary.vue @@ -6,6 +6,7 @@ import { toCurrency, toPercentage, toDate } from 'src/filters'; import CardSummary from 'components/ui/CardSummary.vue'; import { getUrl } from 'src/composables/getUrl'; import VnLv from 'src/components/ui/VnLv.vue'; +import VnLinkPhone from 'src/components/ui/VnLinkPhone.vue'; const route = useRoute(); const { t } = useI18n(); @@ -68,8 +69,18 @@ const creditWarning = computed(() => { - - + + + {{ t('customer.summary.phone') }} + + + + + + {{ t('customer.summary.mobile') }} + + + -import { reactive, watch } from 'vue'; - -const customer = reactive({ - name: '', -}); - -watch(() => customer.name); - - - - - - - - - - - - - - - - - - - - diff --git a/src/pages/Customer/CustomerFilter.vue b/src/pages/Customer/CustomerFilter.vue index 113e0cc7c8..c276b0d3e5 100644 --- a/src/pages/Customer/CustomerFilter.vue +++ b/src/pages/Customer/CustomerFilter.vue @@ -3,6 +3,7 @@ import { ref } from 'vue'; import { useI18n } from 'vue-i18n'; import FetchData from 'components/FetchData.vue'; import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue'; +import VnSelectFilter from 'components/common/VnSelectFilter.vue'; const { t } = useI18n(); const props = defineProps({ @@ -63,7 +64,7 @@ const zones = ref(); - - @@ -124,7 +126,7 @@ const zones = ref(); - - + + + {{ t('customer.list.phone') }} + + + - + - + {{ t('Web Payments') }} @@ -138,7 +144,7 @@ function stateColor(row) { order="created DESC" :limit="20" :offset="50" - auto-load + :auto-load="!!$route?.query.params" > @@ -167,6 +173,13 @@ function stateColor(row) { + + + + {{ row.id }} + + + @@ -175,6 +188,13 @@ function stateColor(row) { + + + + {{ row.customerName }} + + + @@ -188,9 +208,9 @@ function stateColor(row) { - + - + - diff --git a/src/pages/Customer/CustomerPaymentsFilter.vue b/src/pages/Customer/CustomerPaymentsFilter.vue index d2a9160abb..56fb52d792 100644 --- a/src/pages/Customer/CustomerPaymentsFilter.vue +++ b/src/pages/Customer/CustomerPaymentsFilter.vue @@ -9,10 +9,14 @@ const props = defineProps({ required: true, }, }); + +function isValidNumber(value) { + return /^(\d|\d+(\.|,)?\d+)$/.test(value); +} - + {{ t(`params.${tag.label}`) }}: @@ -49,9 +53,99 @@ const props = defineProps({ - + { + if (value.includes(',')) + params.amount = params.amount.replace(',', '.'); + } + " + :rules="[ + (val) => + isValidNumber(val) || !val || 'Please type a number', + ]" + > - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -67,12 +161,19 @@ en: orderFk: Order clientFk: Customer amount: Amount + from: From + to: To es: params: orderFk: Pedido clientFk: Cliente amount: Importe + from: Desde + to: Hasta Order ID: ID pedido Customer ID: ID cliente Amount: Importe + Please type a number: Por favor, escriba un número + From: Desde + To: Hasta diff --git a/src/pages/InvoiceOut/Card/InvoiceOutDescriptor.vue b/src/pages/InvoiceOut/Card/InvoiceOutDescriptor.vue index f6abd77e03..4271f7f382 100644 --- a/src/pages/InvoiceOut/Card/InvoiceOutDescriptor.vue +++ b/src/pages/InvoiceOut/Card/InvoiceOutDescriptor.vue @@ -57,6 +57,7 @@ const setData = (entity) => (data.value = useCardDescription(entity.ref, entity. :title="data.title" :subtitle="data.subtitle" @on-fetch="setData" + data-key="invoiceOutData" > diff --git a/src/pages/Login/LoginMain.vue b/src/pages/Login/LoginMain.vue index 7c0bbbd447..2e4fd91cab 100644 --- a/src/pages/Login/LoginMain.vue +++ b/src/pages/Login/LoginMain.vue @@ -8,6 +8,8 @@ import axios from 'axios'; import { useSession } from 'src/composables/useSession'; import { useLogin } from 'src/composables/useLogin'; +import VnLogo from 'components/ui/VnLogo.vue'; + const quasar = useQuasar(); const session = useSession(); const loginCache = useLogin(); @@ -64,14 +66,8 @@ async function onSubmit() { - - + + - + diff --git a/src/pages/Login/TwoFactor.vue b/src/pages/Login/TwoFactor.vue index f14e854188..1c99e8dec0 100644 --- a/src/pages/Login/TwoFactor.vue +++ b/src/pages/Login/TwoFactor.vue @@ -48,7 +48,7 @@ async function onSubmit() { } - + {{ t('twoFactor.insert') }} @@ -77,4 +77,15 @@ async function onSubmit() { - + diff --git a/src/pages/Login/VerifyEmail.vue b/src/pages/Login/VerifyEmail.vue new file mode 100644 index 0000000000..dae20e8b65 --- /dev/null +++ b/src/pages/Login/VerifyEmail.vue @@ -0,0 +1,124 @@ + + + + + + + {{ t('verifyEmail') }} + + + + + + + + + {{ t(button.text) }} + + + + + + + + { + "en": { + "verifyEmail": "Your email has been successfully verified. You can now log in to enjoy all the features of our platform.", + "goToShop": "Go to the shop", + "logIn": "Log In" + }, + "es": { + "verifyEmail": "Su correo electrónico ha sido verificado con éxito. Ahora puede iniciar sesión para disfrutar de todas las funcionalidades de nuestra plataforma.", + "goToShop": "Ir a la tienda", + "logIn": "Iniciar sesión" + }, + "fr": { + "verifyEmail": "Votre courrier électronique a été vérifié avec succès. Vous pouvez maintenant vous connecter pour profiter de toutes les fonctionnalités de notre plateforme.", + "goToShop": "Aller à la boutique", + "logIn": "Se connecter" + }, + "pt": { + "verifyEmail": "Seu e-mail foi verificado com sucesso. Agora você pode fazer o login para aproveitar todas as funcionalidades da nossa plataforma.", + "goToShop": "Ir para a loja", + "logIn": "Fazer login" + }, + "it": { + "verifyEmail": "La tua email è stata verificata con successo. Ora puoi accedere per godere di tutte le funzionalità della nostra piattaforma.", + "goToShop": "Vai al negozio", + "logIn": "Accedi" + } + } + + + diff --git a/src/pages/Ticket/Card/TicketDescriptor.vue b/src/pages/Ticket/Card/TicketDescriptor.vue index 25c9dfb1cc..d2a4078744 100644 --- a/src/pages/Ticket/Card/TicketDescriptor.vue +++ b/src/pages/Ticket/Card/TicketDescriptor.vue @@ -8,6 +8,7 @@ import CardDescriptor from 'components/ui/CardDescriptor.vue'; import TicketDescriptorMenu from './TicketDescriptorMenu.vue'; import VnLv from 'src/components/ui/VnLv.vue'; import useCardDescription from 'src/composables/useCardDescription'; +import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue'; const $props = defineProps({ id: { @@ -80,6 +81,7 @@ const setData = (entity) => :filter="filter" :title="data.title" :subtitle="data.subtitle" + data-key="ticketData" @on-fetch="setData" > diff --git a/src/pages/Ticket/Card/TicketDescriptorMenu.vue b/src/pages/Ticket/Card/TicketDescriptorMenu.vue index f55229bcff..95f6a94d9f 100644 --- a/src/pages/Ticket/Card/TicketDescriptorMenu.vue +++ b/src/pages/Ticket/Card/TicketDescriptorMenu.vue @@ -87,7 +87,7 @@ function showSmsDialog(template, customData) { componentProps: { phone: phone, template: template, - locale: client.user.lang, + locale: client?.user?.lang ?? 'default_locale', data: data, promise: sendSms, }, diff --git a/src/pages/Ticket/Card/TicketSummary.vue b/src/pages/Ticket/Card/TicketSummary.vue index ce46d1d47a..5c4c79a4fc 100644 --- a/src/pages/Ticket/Card/TicketSummary.vue +++ b/src/pages/Ticket/Card/TicketSummary.vue @@ -10,6 +10,7 @@ import FetchedTags from 'components/ui/FetchedTags.vue'; import InvoiceOutDescriptorProxy from 'pages/InvoiceOut/Card/InvoiceOutDescriptorProxy.vue'; import WorkerDescriptorProxy from 'pages/Worker/Card/WorkerDescriptorProxy.vue'; import VnLv from 'src/components/ui/VnLv.vue'; +import VnLinkPhone from 'src/components/ui/VnLinkPhone.vue'; import { getUrl } from 'src/composables/getUrl'; onUpdated(() => summaryRef.value.fetch()); @@ -172,7 +173,7 @@ async function changeState(value) { :label="t('ticket.summary.agency')" :value="ticket.agencyMode.name" /> - + - + {{ dashIfEmpty(ticket.refFk) }} - - - - + + + {{ t('ticket.summary.consigneePhone') }} + + + + + + {{ t('ticket.summary.consigneeMobile') }} + + + + + + {{ t('ticket.summary.clientPhone') }} + + + + + + {{ t('ticket.summary.clientMobile') }} + + + - + diff --git a/src/pages/Wagon/Card/WagonCard.vue b/src/pages/Wagon/Card/WagonCard.vue new file mode 100644 index 0000000000..18ec121e34 --- /dev/null +++ b/src/pages/Wagon/Card/WagonCard.vue @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + +es: + Search customer: Buscar cliente + You can search by customer id or name: Puedes buscar por id o nombre del cliente + diff --git a/src/pages/Wagon/WagonCounter.vue b/src/pages/Wagon/WagonCounter.vue new file mode 100644 index 0000000000..bd5d2ca674 --- /dev/null +++ b/src/pages/Wagon/WagonCounter.vue @@ -0,0 +1,154 @@ + + + + + + + {{ props.title }} + + + + {{ props.count }} + + + + {{ t('Add 30') }} + + + {{ t('Add 10') }} + + + + + {{ t('Subtract 1') }} + + + {{ t('Flush') }} + + + + + + + + + +es: + Subtract 1: Quitar 1 + Add 30: Añadir 30 + Add 10: Añadir 10 + Flush: Vaciar + Are you sure?: ¿Estás seguro? + It will set to 0: Se pondrá a 0 + The counter will be reset to zero: Se pondrá el contador a cero + diff --git a/src/pages/Worker/Card/WorkerDescriptor.vue b/src/pages/Worker/Card/WorkerDescriptor.vue index ba64a5abbb..4a1b2a6e88 100644 --- a/src/pages/Worker/Card/WorkerDescriptor.vue +++ b/src/pages/Worker/Card/WorkerDescriptor.vue @@ -5,7 +5,9 @@ import { useI18n } from 'vue-i18n'; import { useSession } from 'src/composables/useSession'; import CardDescriptor from 'src/components/ui/CardDescriptor.vue'; import VnLv from 'src/components/ui/VnLv.vue'; +import VnLinkPhone from 'src/components/ui/VnLinkPhone.vue'; import useCardDescription from 'src/composables/useCardDescription'; + const $props = defineProps({ id: { type: Number, @@ -47,19 +49,22 @@ const filter = { ], }; -const sip = computed(() => worker.value.sip && worker.value.sip.extension); +const sip = computed(() => worker.value?.sip && worker.value.sip.extension); function getWorkerAvatar() { const token = getToken(); return `/api/Images/user/160x160/${route.params.id}/download?access_token=${token}`; } const data = ref(useCardDescription()); -const setData = (entity) => - (data.value = useCardDescription(entity.user.nickname, entity.id)); +const setData = (entity) => { + if (!entity) return; + data.value = useCardDescription(entity.user.nickname, entity.id); +}; - - + + - - + + + {{ t('worker.card.phone') }} + + + + + + {{ t('worker.summary.sipExtension') }} + + + diff --git a/src/pages/Worker/Card/WorkerNotificationsManager.vue b/src/pages/Worker/Card/WorkerNotificationsManager.vue index 13c9b3de2b..44573adca9 100644 --- a/src/pages/Worker/Card/WorkerNotificationsManager.vue +++ b/src/pages/Worker/Card/WorkerNotificationsManager.vue @@ -1,10 +1,12 @@ + - - - - - - {{ t('worker.notificationsManager.activeNotifications') }} - - - - - - - - - - {{ t('worker.notificationsManager.availableNotifications') }} - - - + + + + + - - {{ notification.name }} - {{ - notification.description - }} - - + + + + + + {{ notification.name }} + + {{ notification.description }} + + - - - - - - - + + + + + + + + + + diff --git a/src/pages/Worker/Card/WorkerSummary.vue b/src/pages/Worker/Card/WorkerSummary.vue index 7c8accc5d6..970a0dee42 100644 --- a/src/pages/Worker/Card/WorkerSummary.vue +++ b/src/pages/Worker/Card/WorkerSummary.vue @@ -7,6 +7,7 @@ import { getUrl } from 'src/composables/getUrl'; import VnLv from 'src/components/ui/VnLv.vue'; import WorkerDescriptorProxy from './WorkerDescriptorProxy.vue'; import { dashIfEmpty } from 'src/filters'; +import VnLinkPhone from 'src/components/ui/VnLinkPhone.vue'; const route = useRoute(); const { t } = useI18n(); @@ -91,15 +92,24 @@ const filter = { - - - + + + {{ t('worker.summary.phoneExtension') }} + + + + + + {{ t('worker.summary.entPhone') }} + + + + + + {{ t('worker.summary.personalPhone') }} + + + @@ -109,10 +119,12 @@ const filter = { - + + + {{ t('worker.summary.sipExtension') }} + + + diff --git a/src/router/index.js b/src/router/index.js index cccf9af6dc..ca560e9c9d 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -45,7 +45,7 @@ export { Router }; export default route(function (/* { store, ssrContext } */) { Router.beforeEach(async (to, from, next) => { const { isLoggedIn } = session; - const outLayout = ['Login', 'TwoFactor']; + const outLayout = ['Login', 'TwoFactor', 'VerifyEmail']; if (!isLoggedIn() && !outLayout.includes(to.name)) { return next({ name: 'Login', query: { redirect: to.fullPath } }); } diff --git a/src/router/modules/claim.js b/src/router/modules/claim.js index 9df1dd64ed..1dfd75cff8 100644 --- a/src/router/modules/claim.js +++ b/src/router/modules/claim.js @@ -19,6 +19,7 @@ export default { 'ClaimLog', 'ClaimNotes', 'ClaimDevelopment', + 'ClaimAction', ], }, children: [ @@ -130,6 +131,15 @@ export default { }, component: () => import('src/pages/Claim/Card/ClaimNotes.vue'), }, + { + name: 'ClaimAction', + path: 'action', + meta: { + title: 'action', + icon: 'vn:actions', + }, + component: () => import('src/pages/Claim/Card/ClaimAction.vue'), + }, ], }, ], diff --git a/src/router/modules/customer.js b/src/router/modules/customer.js index c7e978eecf..832a1e0fd9 100644 --- a/src/router/modules/customer.js +++ b/src/router/modules/customer.js @@ -10,7 +10,7 @@ export default { component: RouterView, redirect: { name: 'CustomerMain' }, menus: { - main: ['CustomerList', 'CustomerPayments', 'CustomerCreate'], + main: ['CustomerList', 'CustomerPayments'], card: ['CustomerBasicData'], }, children: [ @@ -27,7 +27,7 @@ export default { title: 'list', icon: 'view_list', }, - component: () => import('src/pages/Customer/CustomerList.vue') + component: () => import('src/pages/Customer/CustomerList.vue'), }, { path: 'payments', @@ -36,17 +36,7 @@ export default { title: 'webPayments', icon: 'vn:onlinepayment', }, - component: () => import('src/pages/Customer/CustomerPayments.vue') - }, - { - path: 'create', - name: 'CustomerCreate', - meta: { - title: 'createCustomer', - icon: 'vn:addperson', - roles: ['developer'], - }, - component: () => import('src/pages/Customer/CustomerCreate.vue'), + component: () => import('src/pages/Customer/CustomerPayments.vue'), }, ], }, @@ -63,7 +53,8 @@ export default { title: 'summary', icon: 'launch', }, - component: () => import('src/pages/Customer/Card/CustomerSummary.vue'), + component: () => + import('src/pages/Customer/Card/CustomerSummary.vue'), }, { path: 'basic-data', @@ -72,7 +63,8 @@ export default { title: 'basicData', icon: 'vn:settings', }, - component: () => import('src/pages/Customer/Card/CustomerBasicData.vue'), + component: () => + import('src/pages/Customer/Card/CustomerBasicData.vue'), }, ], }, diff --git a/src/router/modules/route.js b/src/router/modules/route.js index a3550885c1..acda898de3 100644 --- a/src/router/modules/route.js +++ b/src/router/modules/route.js @@ -21,13 +21,13 @@ export default { redirect: { name: 'CmrList' }, children: [ { - path: 'cmr/list', + path: 'cmr', name: 'CmrList', meta: { title: 'cmrsList', icon: 'fact_check', }, - component: () => import('src/pages/Route/Cmr/CmrList.vue') + component: () => import('src/pages/Route/Cmr/CmrList.vue'), }, ], }, diff --git a/src/router/modules/wagon.js b/src/router/modules/wagon.js index 02513d5a8f..238e482dd2 100644 --- a/src/router/modules/wagon.js +++ b/src/router/modules/wagon.js @@ -10,7 +10,7 @@ export default { component: RouterView, redirect: { name: 'WagonMain' }, menus: { - main: ['WagonList', 'WagonTypeList'], + main: ['WagonList', 'WagonTypeList', 'WagonCounter'], card: [], }, children: [ @@ -27,7 +27,7 @@ export default { title: 'wagonsList', icon: 'vn:trolley', }, - component: () => import('src/pages/Wagon/WagonList.vue') + component: () => import('src/pages/Wagon/WagonList.vue'), }, { path: 'create', @@ -36,7 +36,7 @@ export default { title: 'wagonCreate', icon: 'create', }, - component: () => import('src/pages/Wagon/WagonCreate.vue') + component: () => import('src/pages/Wagon/WagonCreate.vue'), }, { path: ':id/edit', @@ -45,7 +45,16 @@ export default { title: 'wagonEdit', icon: 'edit', }, - component: () => import('src/pages/Wagon/WagonCreate.vue') + component: () => import('src/pages/Wagon/WagonCreate.vue'), + }, + { + path: 'counter', + name: 'WagonCounter', + meta: { + title: 'wagonCounter', + icon: 'add_circle', + }, + component: () => import('src/pages/Wagon/WagonCounter.vue'), }, ], }, @@ -62,7 +71,7 @@ export default { title: 'typesList', icon: 'view_list', }, - component: () => import('src/pages/Wagon/Type/WagonTypeList.vue') + component: () => import('src/pages/Wagon/Type/WagonTypeList.vue'), }, { path: 'create', @@ -71,7 +80,7 @@ export default { title: 'typeCreate', icon: 'create', }, - component: () => import('src/pages/Wagon/Type/WagonTypeCreate.vue') + component: () => import('src/pages/Wagon/Type/WagonTypeCreate.vue'), }, { path: ':id/edit', @@ -80,9 +89,9 @@ export default { title: 'typeEdit', icon: 'edit', }, - component: () => import('src/pages/Wagon/Type/WagonTypeCreate.vue') + component: () => import('src/pages/Wagon/Type/WagonTypeCreate.vue'), }, ], - } + }, ], }; diff --git a/src/router/modules/worker.js b/src/router/modules/worker.js index f79d7f06d3..e5ee7c1a29 100644 --- a/src/router/modules/worker.js +++ b/src/router/modules/worker.js @@ -11,7 +11,7 @@ export default { redirect: { name: 'WorkerMain' }, menus: { main: ['WorkerList'], - // card: ['WorkerNotificationsManager'], + card: ['WorkerNotificationsManager'], }, children: [ { @@ -46,15 +46,16 @@ export default { }, 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'), - // }, + { + name: 'WorkerNotificationsManager', + path: 'notifications', + meta: { + title: 'notifications', + icon: 'notifications', + }, + component: () => + import('src/pages/Worker/Card/WorkerNotificationsManager.vue'), + }, ], }, ], diff --git a/src/router/routes.js b/src/router/routes.js index 4425996b05..e3aa22a06e 100644 --- a/src/router/routes.js +++ b/src/router/routes.js @@ -23,6 +23,12 @@ const routes = [ meta: { title: 'twoFactor' }, component: () => import('../pages/Login/TwoFactor.vue'), }, + { + path: '/verifyEmail', + name: 'VerifyEmail', + meta: { title: 'verifyEmail' }, + component: () => import('../pages/Login/VerifyEmail.vue'), + }, ], }, { diff --git a/src/stores/useArrayDataStore.js b/src/stores/useArrayDataStore.js index f9a32a6faa..223406a331 100644 --- a/src/stores/useArrayDataStore.js +++ b/src/stores/useArrayDataStore.js @@ -18,7 +18,8 @@ export const useArrayDataStore = defineStore('arrayDataStore', () => { skip: 0, order: '', data: ref(), - isLoading: false + isLoading: false, + exprBuilder: null, }; } diff --git a/test/cypress/integration/claimAction.spec.js b/test/cypress/integration/claimAction.spec.js new file mode 100644 index 0000000000..f181722fa6 --- /dev/null +++ b/test/cypress/integration/claimAction.spec.js @@ -0,0 +1,45 @@ +/// +describe('ClaimAction', () => { + const claimId = 2; + + const firstRow = 'tbody > :nth-child(1)'; + const destinationRow = '.q-item__section > .q-field'; + + beforeEach(() => { + cy.viewport(1920, 1080); + cy.login('developer'); + cy.visit(`/#/claim/${claimId}/action`); + }); + + it('should import claim', () => { + cy.get('[title="Import claim"]').click(); + }); + + it('should change destination', () => { + const rowData = [true, null, null, 'Bueno']; + cy.fillRow(firstRow, rowData); + }); + + it('should change destination from other button', () => { + const rowData = [true]; + + cy.fillRow(firstRow, rowData); + cy.get('[title="Change destination"]').click(); + cy.selectOption(destinationRow, 'Confeccion'); + cy.get('.q-card > .q-card__actions > .q-btn--standard').click(); + }); + + it('should regularize', () => { + cy.get('[title="Regularize"]').click(); + cy.clickConfirm(); + }); + + it('should remove the line', () => { + cy.fillRow(firstRow, [true]); + cy.removeCard(); + cy.clickConfirm(); + + cy.reload(); + cy.get(firstRow).should('not.exist'); + }); +}); diff --git a/test/cypress/integration/ClaimNotes.spec.js b/test/cypress/integration/claimNotes.spec.js similarity index 90% rename from test/cypress/integration/ClaimNotes.spec.js rename to test/cypress/integration/claimNotes.spec.js index 5b52dd3398..0a0f28fe7d 100644 --- a/test/cypress/integration/ClaimNotes.spec.js +++ b/test/cypress/integration/claimNotes.spec.js @@ -7,7 +7,7 @@ describe('ClaimNotes', () => { it('should add a new note', () => { const message = 'This is a new message.'; - cy.get('.q-page-sticky button').click(); + cy.get('.q-page-sticky > div > button').click(); cy.get('.q-dialog .q-card__section:nth-child(2)').type(message); cy.get('.q-card__actions button:nth-child(2)').click(); cy.get('.q-card .q-card__section:nth-child(2)') diff --git a/test/cypress/integration/vnBreadcrumbs.spec.js b/test/cypress/integration/vnBreadcrumbs.spec.js new file mode 100644 index 0000000000..3c839c1c7d --- /dev/null +++ b/test/cypress/integration/vnBreadcrumbs.spec.js @@ -0,0 +1,21 @@ +/// +describe('VnBreadcrumbs', () => { + const firstCard = '.q-infinite-scroll > :nth-child(1)'; + const lastBreadcrumb = '.q-breadcrumbs--last > .q-breadcrumbs__el'; + beforeEach(() => { + cy.login('developer'); + cy.visit('/'); + }); + + it('should not be breadcrumbs', () => { + cy.get('.q-breadcrumbs').should('not.exist'); + }); + + it('should get the correct breadcrumbs', () => { + cy.visit('#/customer/list'); + cy.get('.q-breadcrumbs__el').should('have.length', 2); + + cy.get(firstCard).click(); + cy.get(`${lastBreadcrumb} > .q-icon`).should('have.text', 'launch'); + }); +}); diff --git a/test/cypress/integration/workerList.spec.js b/test/cypress/integration/workerList.spec.js index d76958367a..2196332636 100644 --- a/test/cypress/integration/workerList.spec.js +++ b/test/cypress/integration/workerList.spec.js @@ -8,18 +8,18 @@ describe('WorkerList', () => { it('should load workers', () => { cy.get('.card-list-body > .list-items > :nth-child(2) > .value > span') .eq(0) - .should('have.text', 'victorvd'); - cy.get('.card-list-body > .list-items > :nth-child(2) > .value > span') - .eq(1) .should('have.text', 'JessicaJones'); cy.get('.card-list-body > .list-items > :nth-child(2) > .value > span') - .eq(2) + .eq(1) .should('have.text', 'BruceBanner'); + cy.get('.card-list-body > .list-items > :nth-child(2) > .value > span') + .eq(2) + .should('have.text', 'CharlesXavier'); }); it('should open the worker summary', () => { cy.get('.card-list-body .actions .q-btn:nth-child(2)').eq(1).click(); - cy.get('.summaryHeader div').should('have.text', '1110 - Jessica Jones'); + cy.get('.summaryHeader div').should('have.text', '1109 - Bruce Banner'); cy.get('.summary .header').eq(0).invoke('text').should('include', 'Basic data'); cy.get('.summary .header').eq(1).should('have.text', 'User data'); }); diff --git a/test/cypress/integration/workerNotificationsManager.spec.js b/test/cypress/integration/workerNotificationsManager.spec.js index 4cd54629a0..1759332774 100644 --- a/test/cypress/integration/workerNotificationsManager.spec.js +++ b/test/cypress/integration/workerNotificationsManager.spec.js @@ -1,36 +1,79 @@ -xdescribe('WorkerNotificationsManager', () => { +describe('WorkerNotificationsManager', () => { + const salesPersonId = 18; + const developerId = 9; + + const activeList = ':nth-child(1) > .q-list'; + const availableList = ':nth-child(2) > .q-list'; + const firstActiveNotification = + ':nth-child(1) > .q-list > :nth-child(1) > .q-item > .q-toggle > .q-toggle__inner'; + const firstAvailableNotification = + ':nth-child(2) > .q-list > :nth-child(1) > .q-item > .q-toggle > .q-toggle__inner'; + 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' + it('should throw an error if you try to change a notification that is not yours', () => { + cy.login('developer'); + cy.visit(`/#/worker/${salesPersonId}/notifications`); + cy.get(firstAvailableNotification).click(); + cy.notificationHas( + '.q-notification__message', + 'The notification subscription of this worker cant be modified' ); - cy.get('.q-chip > .q-icon').eq(0).click(); + }); - cy.reload(); + it('should active a notification that is yours', () => { + cy.login('developer'); + cy.visit(`/#/worker/${developerId}/notifications`); + cy.waitForElement(activeList); + cy.waitForElement(availableList); - 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.get(activeList) + .children() + .its('length') + .then((beforeSize) => { + cy.get(firstAvailableNotification).click(); + cy.get(activeList) + .children() + .should('have.length', beforeSize + 1); + }); + }); - cy.reload(); + it('should deactivate a notification that is yours', () => { + cy.login('developer'); + cy.visit(`/#/worker/${developerId}/notifications`); + cy.waitForElement(activeList); + cy.waitForElement(availableList); - cy.get('.q-chip').should('have.length', 3); + cy.get(availableList) + .children() + .its('length') + .then((beforeSize) => { + cy.get(firstActiveNotification).click(); + cy.get(availableList) + .children() + .should('have.length', beforeSize + 1); + }); + }); + + it('should active a notification if you are their boss', () => { + cy.login('salesBoss'); + cy.visit(`/#/worker/${salesPersonId}/notifications`); + cy.waitForElement(activeList); + cy.waitForElement(availableList); + + cy.get(activeList) + .children() + .its('length') + .then((beforeSize) => { + cy.get(firstAvailableNotification).click(); + cy.get(activeList) + .children() + .should('have.length', beforeSize + 1); + + //Rollback + cy.get(firstActiveNotification).click(); + }); }); }); diff --git a/test/cypress/support/commands.js b/test/cypress/support/commands.js index a725837a18..d361eee1a4 100755 --- a/test/cypress/support/commands.js +++ b/test/cypress/support/commands.js @@ -88,7 +88,12 @@ Cypress.Commands.add('addCard', () => { cy.get('.q-page-sticky > div > .q-btn').click(); }); Cypress.Commands.add('clickConfirm', () => { - cy.get('.q-btn--unelevated > .q-btn__content > .block').click(); + cy.waitForElement('.q-dialog__inner > .q-card'); + cy.get('.q-card__actions > .q-btn--unelevated > .q-btn__content > .block').click(); +}); + +Cypress.Commands.add('notificationHas', (selector, text) => { + cy.get(selector).should('have.text', text); }); Cypress.Commands.add('fillRow', (rowSelector, data) => { diff --git a/test/vitest/__tests__/pages/Worker/WorkerNotificationsManager.spec.js b/test/vitest/__tests__/pages/Worker/WorkerNotificationsManager.spec.js index 7443460789..35ce91e618 100644 --- a/test/vitest/__tests__/pages/Worker/WorkerNotificationsManager.spec.js +++ b/test/vitest/__tests__/pages/Worker/WorkerNotificationsManager.spec.js @@ -1,15 +1,15 @@ import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest'; -import { createWrapper, axios } from 'app/test/vitest/helper'; +import { createWrapper } from 'app/test/vitest/helper'; import WorkerNotificationsManager from 'src/pages/Worker/Card/WorkerNotificationsManager.vue'; +import { ref } from 'vue'; describe('WorkerNotificationsManager', () => { let vm; - const entityId = 1110; beforeAll(() => { vm = createWrapper(WorkerNotificationsManager, { - propsData: { - id: entityId, + global: { + stubs: ['CrudModel'], }, }).vm; }); @@ -18,83 +18,16 @@ describe('WorkerNotificationsManager', () => { 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(); + describe('swapEntry()', () => { + it('should swap notification', async () => { + const from = ref(new Map()); + const to = ref(new Map()); + from.value.set(1, { notificationFk: 1 }); + to.value.set(2, { notificationFk: 2 }); - 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(); + await vm.swapEntry(from.value, to.value, 1); + expect(to.value.size).toBe(2); + expect(from.value.size).toBe(0); }); }); }); diff --git a/test/vitest/helper.js b/test/vitest/helper.js index 8f4dc32216..7cc2bdfa5e 100644 --- a/test/vitest/helper.js +++ b/test/vitest/helper.js @@ -63,6 +63,7 @@ class FormDataMock { } } global.FormData = FormDataMock; +global.URL = class URL {}; export function createWrapper(component, options) { const defaultOptions = {
+ {{ t('claim.summary.actions') }} +
{{ props.title }}