diff --git a/.eslintrc.js b/.eslintrc.js index c8bdecb1a..1d09a896f 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -58,7 +58,7 @@ module.exports = { rules: { 'prefer-promise-reject-errors': 'off', 'no-unused-vars': 'warn', - + "vue/no-multiple-template-root": "off" , // allow debugger during development only 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', }, 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/Jenkinsfile b/Jenkinsfile index 437332c4e..9dd72ccc3 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -96,4 +96,4 @@ pipeline { } } } -} \ No newline at end of file +} diff --git a/package-lock.json b/package-lock.json index a3a9dcc63..ce9c4d7c1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,18 @@ { "name": "salix-front", - "version": "23.40.01", + "version": "24.02.01", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "salix-front", - "version": "0.0.1", + "version": "24.02.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", + "croppie": "^2.6.5", "pinia": "^2.1.3", "quasar": "^2.12.0", "validator": "^13.9.0", @@ -946,9 +947,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", @@ -3169,6 +3170,11 @@ "node": ">= 10" } }, + "node_modules/croppie": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/croppie/-/croppie-2.6.5.tgz", + "integrity": "sha512-IlChnVUGG5T3w2gRZIaQgBtlvyuYnlUWs2YZIXXR3H9KrlO1PtBT3j+ykxvy9eZIWhk+V5SpBmhCQz5UXKrEKQ==" + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", diff --git a/package.json b/package.json index 7a9331870..74f83334c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "salix-front", - "version": "23.48.01", + "version": "24.10.0", "description": "Salix frontend", "productName": "Salix", "author": "Verdnatura", @@ -9,23 +9,23 @@ "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", + "croppie": "^2.6.5", "pinia": "^2.1.3", "quasar": "^2.12.0", "validator": "^13.9.0", "vue": "^3.3.4", "vue-i18n": "^9.2.2", - "vue-router": "^4.2.1", - "vue-router-mock": "^0.2.0" + "vue-router": "^4.2.1" }, "devDependencies": { "@intlify/unplugin-vue-i18n": "^0.8.1", @@ -53,4 +53,4 @@ "vite": "^4.3.5", "vitest": "^0.31.1" } -} \ No newline at end of file +} 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/App.vue b/src/App.vue index 8e9bea2e4..d0d8c9358 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,10 +1,11 @@ + + + + +en: + title: New bank entity + subtitle: Please, ensure you put the correct data! + name: Name + swift: Swift + country: Country + id: Entity code +es: + title: Nueva entidad bancaria + subtitle: ¡Por favor, asegúrate de poner los datos correctos! + name: Nombre + swift: Swift + country: País + id: Código de la entidad + diff --git a/src/components/CreateDepartmentChild.vue b/src/components/CreateDepartmentChild.vue new file mode 100644 index 000000000..8f5b4b874 --- /dev/null +++ b/src/components/CreateDepartmentChild.vue @@ -0,0 +1,100 @@ + + + + + + + +es: + Name: Nombre + New department: Nuevo departamento + diff --git a/src/components/CreateNewCityForm.vue b/src/components/CreateNewCityForm.vue new file mode 100644 index 000000000..7326ea7a5 --- /dev/null +++ b/src/components/CreateNewCityForm.vue @@ -0,0 +1,72 @@ + + + + + +es: + New city: Nueva ciudad + Please, ensure you put the correct data!: ¡Por favor, asegúrese de poner los datos correctos! + Name: Nombre + Province: Provincia + diff --git a/src/components/CreateNewPostcodeForm.vue b/src/components/CreateNewPostcodeForm.vue new file mode 100644 index 000000000..47836c05b --- /dev/null +++ b/src/components/CreateNewPostcodeForm.vue @@ -0,0 +1,148 @@ + + + + + +es: + New postcode: Nuevo código postal + Please, ensure you put the correct data!: ¡Por favor, asegúrese de poner los datos correctos! + City: Población + Province: Provincia + Country: País + Postcode: Código postal + diff --git a/src/components/CreateNewProvinceForm.vue b/src/components/CreateNewProvinceForm.vue new file mode 100644 index 000000000..b972db2c9 --- /dev/null +++ b/src/components/CreateNewProvinceForm.vue @@ -0,0 +1,72 @@ + + + + + +es: + New province: Nueva provincia + Please, ensure you put the correct data!: ¡Por favor, asegúrese de poner los datos correctos! + Name: Nombre + Autonomy: Autonomía + diff --git a/src/components/CreateThermographForm.vue b/src/components/CreateThermographForm.vue new file mode 100644 index 000000000..d4511a0e7 --- /dev/null +++ b/src/components/CreateThermographForm.vue @@ -0,0 +1,114 @@ + + + + + +es: + Identifier: Identificador + Model: Modelo + Warehouse: Almacén + Temperature: Temperatura + New thermograph: Nuevo termógrafo + diff --git a/src/components/CrudModel.vue b/src/components/CrudModel.vue index bd3a01304..9bb05d439 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() { @@ -195,7 +196,6 @@ function getChanges() { const creates = []; const pk = $props.primaryKey; - for (const [i, row] of formData.value.entries()) { if (!row[pk]) { creates.push(row); @@ -224,15 +224,19 @@ function getDifferences(obj1, obj2) { delete obj2.$index; for (let key in obj1) { - if (obj2[key] && obj1[key] !== obj2[key]) { + if (obj2[key] && JSON.stringify(obj1[key]) !== JSON.stringify(obj2[key])) { diff[key] = obj2[key]; } } for (let key in obj2) { - if (obj1[key] === undefined || obj1[key] !== obj2[key]) { + if ( + obj1[key] === undefined || + JSON.stringify(obj1[key]) !== JSON.stringify(obj2[key]) + ) { diff[key] = obj2[key]; } } + return diff; } @@ -272,7 +276,7 @@ watch(formUrl, async () => { - + +import { reactive, computed, ref } from 'vue'; +import { useI18n } from 'vue-i18n'; + +import VnSelectFilter from 'src/components/common/VnSelectFilter.vue'; +import FetchData from 'components/FetchData.vue'; +import VnRow from 'components/ui/VnRow.vue'; +import VnInput from 'src/components/common/VnInput.vue'; + +import Croppie from 'croppie/croppie'; +import 'croppie/croppie.css'; +import useNotify from 'src/composables/useNotify.js'; +import axios from 'axios'; + +const emit = defineEmits(['closeForm', 'onPhotoUploaded']); + +const props = defineProps({ + id: { + type: String, + default: '', + }, + collection: { + type: String, + default: '', + }, +}); + +const { t } = useI18n(); +const { notify } = useNotify(); + +const uploadMethodsOptions = [ + { label: t('Select from computer'), value: 'computer' }, + { label: t('Import from external URL'), value: 'URL' }, +]; + +const viewportTypes = [ + { + code: 'normal', + description: t('Normal'), + viewport: { + width: 400, + height: 400, + }, + output: { + width: 1200, + height: 1200, + }, + }, + { + code: 'panoramic', + description: t('Panoramic'), + viewport: { + width: 675, + height: 450, + }, + output: { + width: 1350, + height: 900, + }, + }, + { + code: 'vertical', + description: t('Vertical'), + viewport: { + width: 306.66, + height: 533.33, + }, + output: { + width: 460, + height: 800, + }, + }, +]; + +const uploadMethodSelected = ref('computer'); +const viewPortTypeSelected = ref(viewportTypes[0]); +const inputFileRef = ref(null); +const allowedContentTypes = ref(''); +const photoContainerRef = ref(null); +const editor = ref(null); +const newPhoto = reactive({ + id: props.id, + collection: props.collection, + file: null, + url: null, + blob: null, +}); + +const openInputFile = () => { + inputFileRef.value.pickFiles(); +}; + +const displayEditor = () => { + const viewportType = viewPortTypeSelected.value; + const viewport = viewportType.viewport; + const boundaryWidth = viewport.width + 200; + const boundaryHeight = viewport.height + 200; + + if (editor.value) editor.value.destroy(); + editor.value = new Croppie(photoContainerRef.value, { + viewport: { width: viewport.width, height: viewport.height }, + boundary: { width: boundaryWidth, height: boundaryHeight }, + enableOrientation: true, + showZoomer: true, + }); +}; + +const viewportSelection = computed({ + get() { + return viewPortTypeSelected.value; + }, + set(val) { + viewPortTypeSelected.value = val; + + const hasFile = newPhoto.files || newPhoto.url; + if (!val || !hasFile) return; + + let file; + if (uploadMethodSelected.value == 'computer') file = newPhoto.files; + else if (uploadMethodSelected.value == 'URL') file = newPhoto.url; + + updatePhotoPreview(file); + }, +}); + +const updatePhotoPreview = (value) => { + if (value) { + displayEditor(); + if (uploadMethodSelected.value == 'computer') { + newPhoto.files = value; + const reader = new FileReader(); + reader.onload = (e) => editor.value.bind({ url: e.target.result }); + reader.readAsDataURL(value); + } else if (uploadMethodSelected.value == 'URL') { + newPhoto.url = value; + const img = new Image(); + img.crossOrigin = 'Anonymous'; + img.src = value; + img.onload = () => editor.value.bind({ url: value }); + img.onerror = () => { + notify( + t("This photo provider doesn't allow remote downloads"), + 'negative' + ); + }; + } + } +}; + +const rotateLeft = () => { + editor.value.rotate(90); +}; + +const rotateRight = () => { + editor.value.rotate(-90); +}; + +const onUploadAccept = () => { + try { + if (!newPhoto.files && !newPhoto.url) { + notify(t('Select an image'), 'negative'); + return; + } + + const options = { + type: 'blob', + }; + + editor.value + .result(options) + .then((result) => { + const file = new File([result], newPhoto.files?.name || ''); + newPhoto.blob = file; + }) + .then(() => makeRequest()); + } catch (err) { + console.error('Error uploading image'); + } +}; + +const makeRequest = async () => { + const formData = new FormData(); + const now = Date.vnNew(); + const timestamp = now.getTime(); + const fileName = `${newPhoto.files?.name}_${timestamp}`; + formData.append('blob', newPhoto.blob, fileName); + + await axios.post('Images/upload', formData, { + params: newPhoto, + headers: { + 'Content-Type': 'multipart/form-data', + }, + }); + + emit('closeForm'); + emit('onPhotoUploaded'); + + notify(t('globals.dataSaved'), 'positive'); +}; + + + + + + + +es: + Edit photo: Editar foto + Select from computer: Seleccionar desde ordenador + Import from external URL: Importar desde URL externa + Vertical: Vertical + Normal: Normal + Panoramic: Panorámica + Orientation: Orientación + File: Fichero + This photo provider doesn't allow remote downloads: Este proveedor de fotos no permite descargas remotas + Rotate left: Girar a la izquierda + Rotate right: Girar a la derecha + Select an image: Selecciona una imagen + diff --git a/src/components/EditTableCellValueForm.vue b/src/components/EditTableCellValueForm.vue new file mode 100644 index 000000000..6d3b7b08b --- /dev/null +++ b/src/components/EditTableCellValueForm.vue @@ -0,0 +1,141 @@ + + + + + + + + en: + editBuyTitle: Edit {buysAmount} buy(s) + es: + editBuyTitle: Editar {buysAmount} compra(s) + Field to edit: Campo a editar + Value: Valor + diff --git a/src/components/FetchData.vue b/src/components/FetchData.vue index 251d7502a..4f5d7a57d 100644 --- a/src/components/FetchData.vue +++ b/src/components/FetchData.vue @@ -27,6 +27,10 @@ const $props = defineProps({ type: String, default: '', }, + params: { + type: Object, + default: null, + }, }); const emit = defineEmits(['onFetch']); @@ -38,18 +42,19 @@ onMounted(async () => { } }); -async function fetch() { +async function fetch(fetchFilter = {}) { try { - const filter = Object.assign({}, $props.filter); // eslint-disable-line vue/no-dupe-keys - if ($props.where) filter.where = $props.where; + const filter = Object.assign(fetchFilter, $props.filter); // eslint-disable-line vue/no-dupe-keys + if ($props.where && !fetchFilter.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 }, + params: { filter: JSON.stringify(filter), ...$props.params }, }); emit('onFetch', data); + return data; } catch (e) { // } diff --git a/src/components/FilterItemForm.vue b/src/components/FilterItemForm.vue new file mode 100644 index 000000000..4c329a8e8 --- /dev/null +++ b/src/components/FilterItemForm.vue @@ -0,0 +1,242 @@ + + + + + +es: + Filter item: Filtrar artículo + Enter a new search: Introduce una nueva búsqueda + + + diff --git a/src/components/FilterTravelForm.vue b/src/components/FilterTravelForm.vue new file mode 100644 index 000000000..499d5bc4e --- /dev/null +++ b/src/components/FilterTravelForm.vue @@ -0,0 +1,240 @@ + + + + + +es: + Filter travels: Filtro envíos + Enter a new search: Introduce una nueva búsqueda + + + diff --git a/src/components/FormModel.vue b/src/components/FormModel.vue index e8a9e4c17..9fd16088c 100644 --- a/src/components/FormModel.vue +++ b/src/components/FormModel.vue @@ -1,11 +1,12 @@ diff --git a/src/components/LeftMenu.vue b/src/components/LeftMenu.vue index 2eac97c65..1721adfec 100644 --- a/src/components/LeftMenu.vue +++ b/src/components/LeftMenu.vue @@ -206,6 +206,17 @@ async function togglePinned(item, event) { diff --git a/src/components/NavBar.vue b/src/components/NavBar.vue index 778f6bfb9..12366e174 100644 --- a/src/components/NavBar.vue +++ b/src/components/NavBar.vue @@ -24,28 +24,15 @@ const pinnedModulesRef = ref(); diff --git a/src/components/common/VnSelectDialog.vue b/src/components/common/VnSelectDialog.vue new file mode 100644 index 000000000..355a25109 --- /dev/null +++ b/src/components/common/VnSelectDialog.vue @@ -0,0 +1,86 @@ + + + + + diff --git a/src/components/common/VnSelectFilter.vue b/src/components/common/VnSelectFilter.vue index 43827b831..4903a5327 100644 --- a/src/components/common/VnSelectFilter.vue +++ b/src/components/common/VnSelectFilter.vue @@ -1,10 +1,13 @@