From 1a9556f8110d5de773012f3870f2da4aa0f6e0b0 Mon Sep 17 00:00:00 2001 From: imperosol Date: Sat, 14 Dec 2024 00:11:25 +0100 Subject: [PATCH] add a button to download products as csv --- .../bundled/counter/product-list-index.ts | 66 ++++++++++++++++++- counter/templates/counter/product_list.jinja | 16 ++++- 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/counter/static/bundled/counter/product-list-index.ts b/counter/static/bundled/counter/product-list-index.ts index 80cd8627..f0c7ae4d 100644 --- a/counter/static/bundled/counter/product-list-index.ts +++ b/counter/static/bundled/counter/product-list-index.ts @@ -1,8 +1,12 @@ +import { paginated } from "#core:utils/api"; +import { csv } from "#core:utils/csv"; import { History, getCurrentUrlParams, updateQueryString } from "#core:utils/history"; +import type { NestedKeyOf } from "#core:utils/types"; +import { showSaveFilePicker } from "native-file-system-adapter"; import { type ProductSchema, - productSearchProductsDetailed, type ProductSearchProductsDetailedData, + productSearchProductsDetailed, } from "#openapi"; type ProductType = string; @@ -11,6 +15,38 @@ type GroupedProducts = Record; const defaultPageSize = 100; const defaultPage = 1; +/** + * Keys of the properties to include in the CSV. + */ +const csvColumns = [ + "id", + "name", + "code", + "description", + "product_type.name", + "club.name", + "limit_age", + "purchase_price", + "selling_price", + "archived", +] as NestedKeyOf[]; + +/** + * Title of the csv columns. + */ +const csvColumnTitles = [ + "id", + gettext("name"), + "code", + "description", + gettext("product type"), + "club", + gettext("limit age"), + gettext("purchase price"), + gettext("selling price"), + gettext("archived"), +]; + document.addEventListener("alpine:init", () => { Alpine.data("productList", () => ({ loading: false, @@ -84,5 +120,33 @@ document.addEventListener("alpine:init", () => { }, {}); this.loading = false; }, + + /** + * Download products corresponding to the current filters as a CSV file. + * If the pagination has multiple pages, all pages are downloaded. + */ + async downloadCsv() { + this.csvLoading = true; + const fileHandle = await showSaveFilePicker({ + _preferPolyfill: false, + suggestedName: gettext("products.csv"), + types: [], + excludeAcceptAllOption: false, + }); + // if products to download are already in-memory, directly take them. + // If not, fetch them. + const products = + this.nbPages > 1 + ? await paginated(productSearchProductsDetailed, this.getQueryParams()) + : Object.values(this.products).flat(); + const content = csv.stringify(products, { + columns: csvColumns, + titleRow: csvColumnTitles, + }); + const file = await fileHandle.createWritable(); + await file.write(content); + await file.close(); + this.csvLoading = false; + }, })); }); diff --git a/counter/templates/counter/product_list.jinja b/counter/templates/counter/product_list.jinja index 92d955cc..5b2074e8 100644 --- a/counter/templates/counter/product_list.jinja +++ b/counter/templates/counter/product_list.jinja @@ -39,9 +39,19 @@

{% trans %}Product list{% endtrans %}

- - {% trans %}New product{% endtrans %} - +
+ + {% trans %}New product{% endtrans %} + + +