diff --git a/.vscode/settings.json b/.vscode/settings.json index ecc1d50d7..5026b7d3b 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 243d67a34..1a679cdfc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,27 @@ 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). +## [2400.01] - 2024-01-04 + +### Added + +### Changed + +### Fixed + +## [2350.01] - 2023-12-14 + +### 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/cypress.config.js b/cypress.config.js index 2b5b40d08..1934f833e 100644 --- a/cypress.config.js +++ b/cypress.config.js @@ -8,7 +8,7 @@ module.exports = defineConfig({ supportFile: 'test/cypress/support/index.js', videosFolder: 'test/cypress/videos', video: false, - specPattern: 'test/cypress/integration/*.spec.js', + specPattern: 'test/cypress/integration/**/*.spec.js', experimentalRunAllSpecs: true, component: { componentFolder: 'src', diff --git a/package-lock.json b/package-lock.json index a3a9dcc63..9db93eff3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,14 +1,14 @@ { "name": "salix-front", - "version": "23.40.01", + "version": "23.52.01", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "salix-front", - "version": "0.0.1", + "version": "23.52.01", "dependencies": { - "@quasar/cli": "^2.2.1", + "@quasar/cli": "^2.3.0", "@quasar/extras": "^1.16.4", "axios": "^1.4.0", "chromium": "^3.0.3", @@ -946,9 +946,9 @@ } }, "node_modules/@quasar/cli": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@quasar/cli/-/cli-2.2.1.tgz", - "integrity": "sha512-PMwJ76IeeNRRBw+08hUMjhqGC6JKJ/t1zIb+IOiyR5D4rkBR26Ha/Z46OD3KfwUprq4Q8s4ieB1+d3VY8FhPKg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@quasar/cli/-/cli-2.3.0.tgz", + "integrity": "sha512-DNFDemicj3jXe5+Ib+5w9Bwj1U3yoHQkqn0bU/qysIl/p0MmGA1yqOfUF0V4fw/5or1dfCvStIA/oZxUcC+2pQ==", "dependencies": { "@quasar/ssl-certificate": "^1.0.0", "ci-info": "^3.8.0", diff --git a/package.json b/package.json index 3e26b483b..7c966dbb1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "salix-front", - "version": "23.40.01", + "version": "24.00.01", "description": "Salix frontend", "productName": "Salix", "author": "Verdnatura", @@ -9,13 +9,13 @@ "lint": "eslint --ext .js,.vue ./", "format": "prettier --write \"**/*.{js,vue,scss,html,md,json}\" --ignore-path .gitignore", "test:e2e": "cypress open", - "test:e2e:ci": "cd ../salix && gulp docker && cd ../salix-front && cypress run --browser chromium", + "test:e2e:ci": "cd ../salix && gulp docker && cd ../salix-front && cypress run", "test": "echo \"See package.json => scripts for available tests.\" && exit 0", "test:unit": "vitest", "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 cbcbae4dc..755e96bd3 100644 --- a/quasar.config.js +++ b/quasar.config.js @@ -29,7 +29,7 @@ module.exports = configure(function (/* ctx */) { // app boot file (/src/boot) // --> boot files are part of "main.js" // https://v2.quasar.dev/quasar-cli/boot-files - boot: ['i18n', 'axios', 'vnDate'], + boot: ['i18n', 'axios', 'vnDate', 'validations'], // https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#css css: ['app.scss'], @@ -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/boot/validations.js b/src/boot/validations.js new file mode 100644 index 000000000..31e232f86 --- /dev/null +++ b/src/boot/validations.js @@ -0,0 +1,6 @@ +import { boot } from 'quasar/wrappers'; +import { useValidationsStore } from 'src/stores/useValidationsStore'; + +export default boot(async ({ store }) => { + await useValidationsStore(store).fetchModels(); +}); diff --git a/src/boot/vnDate.js b/src/boot/vnDate.js index c78886b57..33d5ac27f 100644 --- a/src/boot/vnDate.js +++ b/src/boot/vnDate.js @@ -15,4 +15,14 @@ export default boot(() => { Date.vnNow = () => { return new Date(Date.vnUTC()).getTime(); }; + + Date.vnFirstDayOfMonth = () => { + const date = new Date(Date.vnUTC()); + return new Date(date.getFullYear(), date.getMonth(), 1); + }; + + Date.vnLastDayOfMonth = () => { + const date = new Date(Date.vnUTC()); + return new Date(date.getFullYear(), date.getMonth() + 1, 0); + }; }); diff --git a/src/components/CrudModel.vue b/src/components/CrudModel.vue index 2a4982fce..75353a35a 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() { @@ -135,6 +136,10 @@ async function saveChanges(data) { hasChanges.value = false; isLoading.value = false; emit('saveChanges', data); + quasar.notify({ + type: 'positive', + message: t('globals.dataSaved'), + }); } async function insert() { @@ -268,7 +273,7 @@ watch(formUrl, async () => { - + {}, + }, + observeFormChanges: { + type: Boolean, + default: true, + description: + 'Esto se usa principalmente para permitir guardar sin hacer cambios (Útil para la feature de clonar ya que en este caso queremos poder guardar de primeras)', + }, }); const emit = defineEmits(['onFetch']); @@ -43,44 +63,73 @@ defineExpose({ save, }); -onMounted(async () => await fetch()); +onMounted(async () => { + // Podemos enviarle al form la estructura de data inicial sin necesidad de fetchearla + if ($props.formInitialData && !$props.autoLoad) { + state.set($props.model, $props.formInitialData); + } else { + await fetch(); + } + + // Disparamos el watcher del form después de que se haya cargado la data inicial, si así se desea + if ($props.observeFormChanges) { + startFormWatcher(); + } +}); onUnmounted(() => { state.unset($props.model); }); const isLoading = ref(false); -const hasChanges = ref(false); -const originalData = ref(); +// Si elegimos observar los cambios del form significa que inicialmente las actions estaran deshabilitadas +const hasChanges = ref(!$props.observeFormChanges); +const originalData = ref({...$props.formInitialData}); const formData = computed(() => state.get($props.model)); const formUrl = computed(() => $props.url); +const startFormWatcher = () => { + watch( + () => formData.value, + (val) => { + if (val) hasChanges.value = true; + }, + { deep: true } + ); +}; + function tMobile(...args) { if (!quasar.platform.is.mobile) return t(...args); } async function fetch() { const { data } = await axios.get($props.url, { - params: { filter: $props.filter }, + params: { filter: JSON.stringify($props.filter) }, }); state.set($props.model, data); originalData.value = data && JSON.parse(JSON.stringify(data)); - watch(formData.value, () => (hasChanges.value = true)); - emit('onFetch', state.get($props.model)); } async function save() { if (!hasChanges.value) { - return quasar.notify({ - type: 'negative', - message: t('globals.noChanges'), - }); + notify('globals.noChanges', 'negative'); + return; } isLoading.value = true; - await axios.patch($props.urlUpdate || $props.url, formData.value); + + try { + if ($props.urlCreate) { + await axios.post($props.urlCreate, formData.value); + notify('globals.dataCreated', 'positive'); + } else { + await axios.patch($props.urlUpdate || $props.url, formData.value); + } + } catch (err) { + notify('errors.create', 'negative'); + } originalData.value = JSON.parse(JSON.stringify(formData.value)); hasChanges.value = false; @@ -91,11 +140,12 @@ function reset() { state.set($props.model, originalData.value); originalData.value = JSON.parse(JSON.stringify(originalData.value)); - watch(formData.value, () => (hasChanges.value = true)); - emit('onFetch', state.get($props.model)); - hasChanges.value = false; + if ($props.observeFormChanges) { + hasChanges.value = false; + } } + // eslint-disable-next-line vue/no-dupe-keys function filter(value, update, filterOptions) { update( @@ -118,13 +168,28 @@ watch(formUrl, async () => { }); + diff --git a/src/components/NavBar.vue b/src/components/NavBar.vue index 4e3e241b8..2368e078e 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,27 +25,17 @@ const pinnedModulesRef = ref(); diff --git a/src/components/common/SendEmailDialog.vue b/src/components/common/SendEmailDialog.vue index ab83c0949..0f574a795 100644 --- a/src/components/common/SendEmailDialog.vue +++ b/src/components/common/SendEmailDialog.vue @@ -24,12 +24,13 @@ const address = ref(props.data.address); const isLoading = ref(false); async function confirm() { - const response = { address }; - + const response = { address: address.value }; if (props.promise) { isLoading.value = true; + const { address: _address, ...restData } = props.data; + try { - Object.assign(response, props.data); + Object.assign(response, restData); await props.promise(response); } finally { isLoading.value = false; diff --git a/src/components/common/VnAccountNumber.vue b/src/components/common/VnAccountNumber.vue new file mode 100644 index 000000000..f7273a72d --- /dev/null +++ b/src/components/common/VnAccountNumber.vue @@ -0,0 +1,41 @@ + + + diff --git a/src/components/common/VnBreadcrumbs.vue b/src/components/common/VnBreadcrumbs.vue new file mode 100644 index 000000000..792671e21 --- /dev/null +++ b/src/components/common/VnBreadcrumbs.vue @@ -0,0 +1,82 @@ + + + diff --git a/src/components/common/VnInputDate.vue b/src/components/common/VnInputDate.vue new file mode 100644 index 000000000..a8e0d4a43 --- /dev/null +++ b/src/components/common/VnInputDate.vue @@ -0,0 +1,76 @@ + + + + + diff --git a/src/components/common/VnJsonValue.vue b/src/components/common/VnJsonValue.vue new file mode 100644 index 000000000..a2e858d0d --- /dev/null +++ b/src/components/common/VnJsonValue.vue @@ -0,0 +1,88 @@ + + + + + diff --git a/src/components/common/VnLog.vue b/src/components/common/VnLog.vue index 1213c8bbc..0949fb5cb 100644 --- a/src/components/common/VnLog.vue +++ b/src/components/common/VnLog.vue @@ -1,128 +1,634 @@ - en: + to: To + pointRecord: View record at this point in time + recordChanges: show all record changes + tooltips: + search: Search by id or concept + changes: Search by changes actions: - insert: Creates - update: Updates - delete: Deletes - models: - Claim: Claim - ClaimDms: Document - ClaimBeginning: Claimed Sales - ClaimObservation: Observation + Creates: Creates + Edits: Edits + Deletes: Deletes + Accesses: Accesses + Users: + User: Usuario + All: Todo + System: Sistema properties: id: ID claimFk: Claim ID @@ -172,6 +1040,12 @@ en: responsibility: Responsibility packages: Packages es: + to: Hasta + pointRecord: Ver el registro en este punto + recordChanges: Mostrar todos los cambios realizados en el registro + tooltips: + search: Buscar por identificador o concepto + changes: Buscar por cambios. Los atributos deben buscarse por su nombre interno, para obtenerlo situar el cursor sobre el atributo. Audit logs: Registros de auditoría Property: Propiedad Before: Antes @@ -179,14 +1053,14 @@ es: Yes: Si Nothing: Nada actions: - insert: Crea - update: Actualiza - delete: Elimina - models: - Claim: Reclamación - ClaimDms: Documento - ClaimBeginning: Línea reclamada - ClaimObservation: Observación + Creates: Crea + Edits: Modifica + Deletes: Elimina + Accesses: Accede + Users: + User: Usuario + All: Todo + System: Sistema properties: id: ID claimFk: ID reclamación diff --git a/src/components/common/VnSelectFilter.vue b/src/components/common/VnSelectFilter.vue index 55395f260..568da613f 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,14 @@ const $props = defineProps({ type: String, default: '', }, + filterOptions: { + type: Array, + default: () => [], + }, + isClearable: { + type: Boolean, + default: true, + }, }); const { optionLabel, options } = toRefs($props); const myOptions = ref([]); @@ -28,18 +36,22 @@ function setOptions(data) { setOptions(options.value); const filter = (val, options) => { - const search = val.toLowerCase(); + const search = val.toString().toLowerCase(); - if (val === '') return options; + if (!search) return options; return options.filter((row) => { + if ($props.filterOptions.length) { + return $props.filterOptions.some((prop) => { + const propValue = String(row[prop]).toLowerCase(); + return propValue.includes(search); + }); + } + const id = row.id; - const name = row[$props.optionLabel].toLowerCase(); + const optionLabel = String(row[$props.optionLabel]).toLowerCase(); - const idMatches = id == search; - const nameMatches = name.indexOf(search) > -1; - - return idMatches || nameMatches; + return id == search || optionLabel.includes(search); }); }; @@ -85,11 +97,16 @@ const value = computed({ fill-input ref="vnSelectRef" > - diff --git a/src/components/ui/CardDescriptor.vue b/src/components/ui/CardDescriptor.vue index f63b75de6..2669b53a9 100644 --- a/src/components/ui/CardDescriptor.vue +++ b/src/components/ui/CardDescriptor.vue @@ -1,8 +1,10 @@ - - diff --git a/src/pages/Claim/Card/ClaimCard.vue b/src/pages/Claim/Card/ClaimCard.vue index 03b9889f0..a8c832967 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}`); -});