From ad8f9142e99385d9b5c03ff5de1d2f0ad6198f06 Mon Sep 17 00:00:00 2001 From: Vika Vorkin Date: Thu, 8 Feb 2024 18:47:54 +0200 Subject: [PATCH] Add export button to all list using built in export functionality of refinedev Signed-off-by: Vika --- client/package-lock.json | 43 ++++++++++++++++++---------- client/package.json | 4 +-- client/public/locales/hu/common.json | 3 +- client/public/locales/it/common.json | 3 +- client/public/locales/ru/common.json | 3 +- client/public/locales/uk/common.json | 3 +- client/public/locales/zh/common.json | 3 +- client/src/pages/filaments/list.tsx | 15 ++++++++-- client/src/pages/spools/list.tsx | 14 +++++++-- client/src/pages/vendors/list.tsx | 15 ++++++++-- client/src/utils/objects.ts | 13 +++++++++ 11 files changed, 91 insertions(+), 28 deletions(-) create mode 100644 client/src/utils/objects.ts diff --git a/client/package-lock.json b/client/package-lock.json index b078be75..61b6fd96 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -11,7 +11,7 @@ "@ant-design/icons": "^5.2.6", "@loadable/component": "^5.16.2", "@refinedev/antd": "^5.37.0", - "@refinedev/core": "^4.46.0", + "@refinedev/core": "^4.47.0", "@refinedev/kbar": "^1.3.4", "@refinedev/react-router-v6": "^4.5.4", "@refinedev/simple-rest": "^5.0.0", @@ -2083,13 +2083,12 @@ } }, "node_modules/@refinedev/core": { - "version": "4.46.0", - "resolved": "https://registry.npmjs.org/@refinedev/core/-/core-4.46.0.tgz", - "integrity": "sha512-hLzhdsyI4sCxuCVdRAMAsj1N0tmyyDchsVu36bMPnU83ZnUsk8fNsb1LDXOIxFl6zr88LWzS3vTYpfIC4PprOQ==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@refinedev/core/-/core-4.47.1.tgz", + "integrity": "sha512-J2bsJTZ+pkqwXlkH84oauPkc0O9P6m+39FF7TgvvkKzu7XKIg/ifoeG1PDMy5Nxgdb9oiJLr0H4YQYKNi8JErA==", "dependencies": { - "@refinedev/devtools-internal": "1.1.4", + "@refinedev/devtools-internal": "1.1.5", "@tanstack/react-query": "^4.10.1", - "export-to-csv-fix-source-map": "^0.2.1", "lodash": "^4.17.21", "lodash-es": "^4.17.21", "papaparse": "^5.3.0", @@ -2107,11 +2106,29 @@ } }, "node_modules/@refinedev/devtools-internal": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@refinedev/devtools-internal/-/devtools-internal-1.1.4.tgz", - "integrity": "sha512-AuXlKRdDZ8POC1B2DMa3AzSICN3LmKebmsMUry3+T9xc7TkL2lGy3anqJnBOQw8asWd5fr+vz1UYjdVcvqUqJA==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@refinedev/devtools-internal/-/devtools-internal-1.1.5.tgz", + "integrity": "sha512-24yE9r6rnTcCAe35XqmeiL5oL9FlodAOAPo4joMxu2SDzBNndh1ZdEEuFxoLd9t9CYu46cjLKQhaWcWWC2Ii3A==", + "dependencies": { + "@refinedev/devtools-shared": "1.1.3", + "@tanstack/react-query": "^4.10.1", + "error-stack-parser": "^2.1.4" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", + "@types/react-dom": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + } + }, + "node_modules/@refinedev/devtools-internal/node_modules/@refinedev/devtools-shared": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@refinedev/devtools-shared/-/devtools-shared-1.1.3.tgz", + "integrity": "sha512-3xtVUwMGWwwnufDL+QDEdMkoi+qLQllefil+y5UyNsDuEIeh9xcNn/Uxl/N27lObA+zakHX9QOR++lR8SQ+TnQ==", "dependencies": { - "@refinedev/devtools-shared": "1.1.2", "@tanstack/react-query": "^4.10.1", "error-stack-parser": "^2.1.4" }, @@ -2172,6 +2189,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/@refinedev/devtools-shared/-/devtools-shared-1.1.2.tgz", "integrity": "sha512-ze+akDLtCblBBEZuFTYoiAu1CfEdzBRBdttVaLJEAgSNdMva9vpbV/7gG5yTQRIIi4kIdfghphwrGHBxOOKjXQ==", + "dev": true, "dependencies": { "@tanstack/react-query": "^4.10.1", "error-stack-parser": "^2.1.4" @@ -5166,11 +5184,6 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true }, - "node_modules/export-to-csv-fix-source-map": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/export-to-csv-fix-source-map/-/export-to-csv-fix-source-map-0.2.1.tgz", - "integrity": "sha512-02CGZG9Kduc0l9ErJ5b+99+PjeQIyoeVGz+f7/Yc04V4YKNPbdT63sQqBC5RW05va4w0MZen7pQpf92H3funYw==" - }, "node_modules/express": { "version": "4.18.2", "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", diff --git a/client/package.json b/client/package.json index 2ceb41ad..c7f77715 100644 --- a/client/package.json +++ b/client/package.json @@ -11,7 +11,7 @@ "@ant-design/icons": "^5.2.6", "@loadable/component": "^5.16.2", "@refinedev/antd": "^5.37.0", - "@refinedev/core": "^4.46.0", + "@refinedev/core": "^4.47.0", "@refinedev/kbar": "^1.3.4", "@refinedev/react-router-v6": "^4.5.4", "@refinedev/simple-rest": "^5.0.0", @@ -65,4 +65,4 @@ "last 1 safari version" ] } -} \ No newline at end of file +} diff --git a/client/public/locales/hu/common.json b/client/public/locales/hu/common.json index e775af5c..4eef71bc 100644 --- a/client/public/locales/hu/common.json +++ b/client/public/locales/hu/common.json @@ -161,7 +161,8 @@ "unArchive": "Arhiválás visszavonása", "clone": "Másolás", "clear": "Törlés", - "saveAndAdd": "Mentés és hozzáadás" + "saveAndAdd": "Mentés és hozzáadás", + "export": "Exportálás" }, "filament": { "fields_help": { diff --git a/client/public/locales/it/common.json b/client/public/locales/it/common.json index 43733a39..1db0a44e 100644 --- a/client/public/locales/it/common.json +++ b/client/public/locales/it/common.json @@ -162,7 +162,8 @@ "unArchive": "Ripristina", "clone": "Clona", "clear": "Pulisci", - "saveAndAdd": "Salva e aggiungi" + "saveAndAdd": "Salva e aggiungi", + "export": "Esporta" }, "filament": { "fields_help": { diff --git a/client/public/locales/ru/common.json b/client/public/locales/ru/common.json index cbaa1d9e..1e12ffdf 100644 --- a/client/public/locales/ru/common.json +++ b/client/public/locales/ru/common.json @@ -28,7 +28,8 @@ "notAccessTitle": "У вас нет разрешения на доступ", "hideColumns": "Скрыть столбцы", "clearFilters": "Очистить фильтр", - "saveAndAdd": "Сохранить и добавить" + "saveAndAdd": "Сохранить и добавить", + "export": "Экспорт" }, "warnWhenUnsavedChanges": "Вы уверены, что хотите выйти? У вас есть несохраненные изменения.", "notifications": { diff --git a/client/public/locales/uk/common.json b/client/public/locales/uk/common.json index 5815d12a..066ad278 100644 --- a/client/public/locales/uk/common.json +++ b/client/public/locales/uk/common.json @@ -21,7 +21,8 @@ "notAccessTitle": "У Вас бракує прав для доступу", "clone": "Клонувати", "unArchive": "Розархівувати", - "saveAndAdd": "Зберегти та додати" + "saveAndAdd": "Зберегти та додати", + "export": "Експорт" }, "spool": { "fields": { diff --git a/client/public/locales/zh/common.json b/client/public/locales/zh/common.json index 399f13a2..7d3a7ed2 100644 --- a/client/public/locales/zh/common.json +++ b/client/public/locales/zh/common.json @@ -28,7 +28,8 @@ "notAccessTitle": "您没有访问权限", "hideColumns": "隐藏列", "clearFilters": "清除筛选器", - "saveAndAdd": "保存并添加" + "saveAndAdd": "保存并添加", + "export": "导出到" }, "warnWhenUnsavedChanges": "您确定要离开吗?您有未保存的更改。", "notifications": { diff --git a/client/src/pages/filaments/list.tsx b/client/src/pages/filaments/list.tsx index 7379c3a3..3791b8f4 100644 --- a/client/src/pages/filaments/list.tsx +++ b/client/src/pages/filaments/list.tsx @@ -1,6 +1,6 @@ import React from "react"; -import { IResourceComponentsProps, useTranslate, useInvalidate, useNavigation } from "@refinedev/core"; -import { useTable, List } from "@refinedev/antd"; +import { IResourceComponentsProps, useTranslate, useInvalidate, useNavigation, useExport } from "@refinedev/core"; +import { useTable, List, ExportButton } from "@refinedev/antd"; import { Table, Button, Dropdown } from "antd"; import dayjs from "dayjs"; import utc from "dayjs/plugin/utc"; @@ -28,6 +28,7 @@ import { removeUndefined } from "../../utils/filtering"; import { EntityType, useGetFields } from "../../utils/queryFields"; import { useNavigate } from "react-router-dom"; import { useCurrency } from "../../utils/settings"; +import { flatten } from "../../utils/objects"; dayjs.extend(utc); @@ -84,6 +85,13 @@ export const FilamentList: React.FC = () => { // Load initial state const initialState = useInitialTableState(namespace); + const { triggerExport, isLoading } = useExport({ + mapData: item => flatten(item), + unparseConfig: { + columns: allColumnsWithExtraFields + } + }); + // Fetch data from the API // To provide the live updates, we use a custom solution (useLiveify) instead of the built-in refine "liveMode" feature. // This is because the built-in feature does not call the liveProvider subscriber with a list of IDs, but instead @@ -213,6 +221,9 @@ export const FilamentList: React.FC = () => { {defaultButtons} + + {t("buttons.export")} + )} > diff --git a/client/src/pages/spools/list.tsx b/client/src/pages/spools/list.tsx index c9ef5002..c31a41ea 100644 --- a/client/src/pages/spools/list.tsx +++ b/client/src/pages/spools/list.tsx @@ -1,6 +1,6 @@ import React from "react"; -import { IResourceComponentsProps, useInvalidate, useNavigation, useTranslate } from "@refinedev/core"; -import { useTable, List } from "@refinedev/antd"; +import { IResourceComponentsProps, useInvalidate, useNavigation, useTranslate, useExport } from "@refinedev/core"; +import { useTable, List, ExportButton } from "@refinedev/antd"; import { Table, Button, Dropdown, Modal } from "antd"; import dayjs from "dayjs"; import utc from "dayjs/plugin/utc"; @@ -38,6 +38,7 @@ import { removeUndefined } from "../../utils/filtering"; import { EntityType, useGetFields } from "../../utils/queryFields"; import { useNavigate } from "react-router-dom"; import { useCurrency } from "../../utils/settings"; +import { flatten } from "../../utils/objects"; dayjs.extend(utc); @@ -111,6 +112,12 @@ export const SpoolList: React.FC = () => { // State for the switch to show archived spools const [showArchived, setShowArchived] = useSavedState("spoolList-showArchived", false); + const { triggerExport, isLoading } = useExport( + { + mapData: item => flatten(item) + } + ); + // Fetch data from the API // To provide the live updates, we use a custom solution (useLiveify) instead of the built-in refine "liveMode" feature. // This is because the built-in feature does not call the liveProvider subscriber with a list of IDs, but instead @@ -308,6 +315,9 @@ export const SpoolList: React.FC = () => { {defaultButtons} + + {t("buttons.export")} + )} > diff --git a/client/src/pages/vendors/list.tsx b/client/src/pages/vendors/list.tsx index a79e16d6..85dec8f0 100644 --- a/client/src/pages/vendors/list.tsx +++ b/client/src/pages/vendors/list.tsx @@ -1,6 +1,6 @@ import React from "react"; -import { IResourceComponentsProps, useTranslate, useInvalidate, useNavigation } from "@refinedev/core"; -import { useTable, List } from "@refinedev/antd"; +import { IResourceComponentsProps, useTranslate, useInvalidate, useNavigation, useExport } from "@refinedev/core"; +import { useTable, List, ExportButton } from "@refinedev/antd"; import { Table, Button, Dropdown } from "antd"; import dayjs from "dayjs"; import utc from "dayjs/plugin/utc"; @@ -12,6 +12,7 @@ import { useLiveify } from "../../components/liveify"; import { removeUndefined } from "../../utils/filtering"; import { EntityType, useGetFields } from "../../utils/queryFields"; import { useNavigate } from "react-router-dom"; +import { flatten } from "../../utils/objects"; dayjs.extend(utc); @@ -30,6 +31,13 @@ export const VendorList: React.FC = () => { // Load initial state const initialState = useInitialTableState(namespace); + const { triggerExport, isLoading } = useExport({ + mapData: item => flatten(item), + unparseConfig: { + columns: allColumnsWithExtraFields + } + }); + // Fetch data from the API const { tableProps, sorters, setSorters, filters, setFilters, current, pageSize, setCurrent } = useTable({ syncWithLocation: false, @@ -145,6 +153,9 @@ export const VendorList: React.FC = () => { {defaultButtons} + + {t("buttons.export")} + )} > diff --git a/client/src/utils/objects.ts b/client/src/utils/objects.ts new file mode 100644 index 00000000..a8a003e4 --- /dev/null +++ b/client/src/utils/objects.ts @@ -0,0 +1,13 @@ +export const flatten = (data: object, prefix: string = '', delimiter: string = '.') => { + const result: { [key: string]: string | number | null } = {}; + + Object.entries(data).forEach(([key, value]) => { + if (typeof value === 'object') { + Object.assign(result, flatten(value, `${prefix}${key}${delimiter}`, delimiter)); + } else { + result[`${prefix}${key}`] = value; + } + }); + + return result; +};