From a2ab4fceb7deee608623c49f4169fec771cfd950 Mon Sep 17 00:00:00 2001 From: mono Date: Sat, 13 Jun 2026 16:32:44 -0500 Subject: [PATCH 1/2] #40 feat: add catalogue images CRUD and display in product catalog --- src/components/CatalogueImagesManagement.vue | 374 +++++++++++++++++++ src/components/NavBar.vue | 5 +- src/pages/admin/catalogue-images.vue | 10 + src/pages/catalog.vue | 2 +- src/router/index.js | 1 + src/services/api.js | 16 + src/services/django-api.js | 20 + 7 files changed, 425 insertions(+), 3 deletions(-) create mode 100644 src/components/CatalogueImagesManagement.vue create mode 100644 src/pages/admin/catalogue-images.vue diff --git a/src/components/CatalogueImagesManagement.vue b/src/components/CatalogueImagesManagement.vue new file mode 100644 index 0000000..9be93e8 --- /dev/null +++ b/src/components/CatalogueImagesManagement.vue @@ -0,0 +1,374 @@ + + + + + diff --git a/src/components/NavBar.vue b/src/components/NavBar.vue index e0c0e27..ad8c616 100644 --- a/src/components/NavBar.vue +++ b/src/components/NavBar.vue @@ -106,8 +106,9 @@ { title: 'Cuadres de tarro', route: '/cuadres_de_tarro', icon: 'mdi-chart-bar'}, { title: 'CSV Tryton', route: '/ventas_para_tryton', icon: 'mdi-file-table'}, { title: 'Compra adm', route: '/compra_admin', icon: 'mdi-cart'}, - { title: 'Gestión de Productos', route: '/admin/products', icon: 'mdi-package-variant'}, - { title: 'Ver Ventas por Catálogo', route: '/admin/catalog-sales', icon: 'mdi-cart-arrow-down'}, + { title: 'Gestión de Productos', route: '/admin/products', icon: 'mdi-package-variant'}, + { title: 'Imágenes de Catálogo', route: '/admin/catalogue-images', icon: 'mdi-image-multiple'}, + { title: 'Ver Ventas por Catálogo', route: '/admin/catalog-sales', icon: 'mdi-cart-arrow-down'}, { divider: true }, { header: 'Sincronización Tryton' }, { title: 'Importar Productos', route: '/sincronizar_productos_tryton', icon: 'mdi-download'}, diff --git a/src/pages/admin/catalogue-images.vue b/src/pages/admin/catalogue-images.vue new file mode 100644 index 0000000..96fb29e --- /dev/null +++ b/src/pages/admin/catalogue-images.vue @@ -0,0 +1,10 @@ + + + diff --git a/src/pages/catalog.vue b/src/pages/catalog.vue index 01812b2..7e68a95 100644 --- a/src/pages/catalog.vue +++ b/src/pages/catalog.vue @@ -379,7 +379,7 @@ export default { this.items = data.map((product) => ({ ...product, quantity: 0, - img: product.img || not_image_product, + img: (product.catalogue_images?.length > 0) ? product.catalogue_images[0] : (product.img || not_image_product), })); }) .catch((error) => { diff --git a/src/router/index.js b/src/router/index.js index 6cb9cb5..a896bdd 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -21,6 +21,7 @@ const ADMIN_ROUTES = [ '/cuadrar_tarro', '/admin/products', '/admin/catalog-sales', + '/admin/catalogue-images', ] const router = createRouter({ diff --git a/src/services/api.js b/src/services/api.js index 5f9ec70..2147aa9 100644 --- a/src/services/api.js +++ b/src/services/api.js @@ -82,6 +82,22 @@ class Api { getCurrentUser() { return this.apiImplementation.getCurrentUser(); } + + getCatalogueImages() { + return this.apiImplementation.getCatalogueImages(); + } + + createCatalogueImage(data) { + return this.apiImplementation.createCatalogueImage(data); + } + + updateCatalogueImage(id, data) { + return this.apiImplementation.updateCatalogueImage(id, data); + } + + deleteCatalogueImage(id) { + return this.apiImplementation.deleteCatalogueImage(id); + } } export default Api; diff --git a/src/services/django-api.js b/src/services/django-api.js index 3ac56e5..95b956d 100644 --- a/src/services/django-api.js +++ b/src/services/django-api.js @@ -130,6 +130,26 @@ class DjangoApi { const url = this.base + "/api/users/me/"; return this.getRequest(url); } + + getCatalogueImages() { + const url = this.base + "/don_confiao/api/catalogue_images/"; + return this.getRequest(url); + } + + createCatalogueImage(data) { + const url = this.base + "/don_confiao/api/catalogue_images/"; + return http.post(url, data).then((r) => r.data); + } + + updateCatalogueImage(id, data) { + const url = this.base + `/don_confiao/api/catalogue_images/${id}/`; + return http.put(url, data).then((r) => r.data); + } + + deleteCatalogueImage(id) { + const url = this.base + `/don_confiao/api/catalogue_images/${id}/`; + return http.delete(url).then((r) => r.data); + } } export default DjangoApi; From 5397ab22550b4e2a80624e64a88862d5ea5c574d Mon Sep 17 00:00:00 2001 From: mono Date: Sat, 13 Jun 2026 17:59:14 -0500 Subject: [PATCH 2/2] =?UTF-8?q?#40=20fix:=20upload=20de=20im=C3=A1genes=20?= =?UTF-8?q?con=20multipart/form-data?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/CatalogueImagesManagement.vue | 25 +++++++++++++------- src/services/django-api.js | 8 +++++-- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/components/CatalogueImagesManagement.vue b/src/components/CatalogueImagesManagement.vue index 9be93e8..f752ada 100644 --- a/src/components/CatalogueImagesManagement.vue +++ b/src/components/CatalogueImagesManagement.vue @@ -100,13 +100,14 @@ /> { }); watch( - () => form.value.file, + selectedFile, (file) => { if (previewObjectUrl) { URL.revokeObjectURL(previewObjectUrl); @@ -246,6 +249,10 @@ watch( }, ); +function onFileSelected(file) { + selectedFile.value = file; +} + function formatDate(dateStr) { if (!dateStr) return "-"; const d = new Date(dateStr); @@ -276,14 +283,15 @@ async function loadProducts() { function openCreateDialog() { dialog.value = { show: true, isEdit: false, editingItem: null }; - form.value = { product: null, file: null, preview: null }; + selectedFile.value = null; + form.value = { product: null, preview: null }; } function openEditDialog(item) { dialog.value = { show: true, isEdit: true, editingItem: item }; + selectedFile.value = null; form.value = { product: item.product, - file: null, preview: item.image, }; } @@ -293,10 +301,11 @@ function closeDialog() { URL.revokeObjectURL(previewObjectUrl); previewObjectUrl = null; } + selectedFile.value = null; dialog.value.show = false; dialog.value.isEdit = false; dialog.value.editingItem = null; - form.value = { product: null, file: null, preview: null }; + form.value = { product: null, preview: null }; } async function submitForm() { @@ -310,8 +319,8 @@ async function submitForm() { try { const fd = new FormData(); fd.append("product", form.value.product); - if (form.value.file) { - fd.append("image", form.value.file); + if (selectedFile.value) { + fd.append("image", selectedFile.value); } if (dialog.value.isEdit) { diff --git a/src/services/django-api.js b/src/services/django-api.js index 95b956d..61153fa 100644 --- a/src/services/django-api.js +++ b/src/services/django-api.js @@ -138,12 +138,16 @@ class DjangoApi { createCatalogueImage(data) { const url = this.base + "/don_confiao/api/catalogue_images/"; - return http.post(url, data).then((r) => r.data); + return http.post(url, data, { + headers: { 'Content-Type': undefined }, + }).then((r) => r.data); } updateCatalogueImage(id, data) { const url = this.base + `/don_confiao/api/catalogue_images/${id}/`; - return http.put(url, data).then((r) => r.data); + return http.put(url, data, { + headers: { 'Content-Type': undefined }, + }).then((r) => r.data); } deleteCatalogueImage(id) {