Skip to content

Commit 52630e7

Browse files
author
Sascha Pfeiffer
committed
Added Download button to all tables
Signed-off-by: Sascha Pfeiffer <sascha.pfeiffer@esaqa.com>
1 parent 0209fa5 commit 52630e7

File tree

3 files changed

+132
-1
lines changed

3 files changed

+132
-1
lines changed

public/locales/en/translation.json

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{
2+
"DOWNLOAD": "Download",
23
"RETRY": "Retry",
34
"SERVER_UNSUPPORTED": "Server unsupported",
45
"THE_VERSION_OF_THE_SERVER_IS_TOO_OLD_AND_NOT_SUPPORTED_PLEASE_UPGRADE": "The version of the server is too old. This client doesn't support the old version anymore. Please update your server or tell your administrator to update it.",

src/components/Table/CustomMaterialTable.jsx

+131
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { withStyles } from '@material-ui/core';
44
import { useTranslation } from 'react-i18next';
55
import PropTypes from 'prop-types';
66
import { forwardRef } from 'react';
7+
import moment from 'moment';
78

89
import AddBox from '@material-ui/icons/AddBox';
910
import ArrowDownward from '@material-ui/icons/ArrowDownward';
@@ -12,6 +13,7 @@ import ChevronLeft from '@material-ui/icons/ChevronLeft';
1213
import ChevronRight from '@material-ui/icons/ChevronRight';
1314
import Clear from '@material-ui/icons/Clear';
1415
import Delete from '@material-ui/icons/Delete';
16+
import CloudDownloadIcon from '@material-ui/icons/CloudDownload';
1517
import Edit from '@material-ui/icons/Edit';
1618
import FilterList from '@material-ui/icons/FilterList';
1719
import FirstPage from '@material-ui/icons/FirstPage';
@@ -76,6 +78,135 @@ const CustomMaterialTable = ({
7678

7779
const mergedOptions = { ...defaultOptions, ...options };
7880

81+
const downloaddataAsCsv = async (pageSize = 100) => {
82+
let allData = [];
83+
let currentPage = 0;
84+
let hasMoreData = true;
85+
86+
try {
87+
// Handle case where data is already an array
88+
if (typeof data !== 'function') {
89+
if (!Array.isArray(data)) {
90+
throw new Error(
91+
'Data must be either a function or an array'
92+
);
93+
}
94+
95+
allData = data;
96+
if (!allData.length) {
97+
return;
98+
}
99+
100+
const headers = Object.keys(allData[0] || {}).sort();
101+
return generateAndDownloadCsv(allData, headers);
102+
}
103+
104+
// Handle case where data is a function
105+
const firstPageQuery = {
106+
page: currentPage,
107+
pageSize: pageSize,
108+
search: '',
109+
};
110+
111+
const firstPageResponse = await data(firstPageQuery);
112+
if (!firstPageResponse.data.length) {
113+
return;
114+
}
115+
116+
// Get headers from first item and sort alphabetically
117+
const headers = Object.keys(firstPageResponse.data[0]).sort();
118+
allData = [...firstPageResponse.data];
119+
120+
// Continue fetching if there's more data
121+
hasMoreData =
122+
firstPageResponse.data.length === pageSize &&
123+
allData.length < firstPageResponse.totalCount;
124+
currentPage++;
125+
126+
// Fetch remaining pages
127+
while (hasMoreData) {
128+
const query = {
129+
page: currentPage,
130+
pageSize: pageSize,
131+
search: '',
132+
};
133+
134+
const response = await data(query);
135+
allData = [...allData, ...response.data];
136+
137+
hasMoreData =
138+
response.data.length === pageSize &&
139+
allData.length < response.totalCount;
140+
currentPage++;
141+
}
142+
143+
return generateAndDownloadCsv(allData, headers);
144+
} catch (error) {
145+
console.error('Error downloading data:', error);
146+
throw error;
147+
}
148+
};
149+
150+
// Helper function to generate and download CSV
151+
const generateAndDownloadCsv = (allData, headers) => {
152+
// Convert data to CSV format
153+
const csvContent = [
154+
// Add headers
155+
headers.join(','),
156+
// Add data rows, handling missing fields
157+
...allData.map((item) =>
158+
headers
159+
.map((header) => {
160+
const value = item[header] ?? '';
161+
// Handle special cases like arrays and objects
162+
const processedValue =
163+
typeof value === 'object'
164+
? JSON.stringify(value)
165+
: String(value);
166+
// Escape commas, quotes, and newlines
167+
const escapedValue = processedValue
168+
.replace(/"/g, '""')
169+
.replace(/\n/g, ' ');
170+
return `"${escapedValue}"`;
171+
})
172+
.join(',')
173+
),
174+
].join('\n');
175+
176+
// Create and download the CSV file
177+
const blob = new Blob([csvContent], {
178+
type: 'text/csv;charset=utf-8;',
179+
});
180+
const link = document.createElement('a');
181+
const url = URL.createObjectURL(blob);
182+
183+
link.setAttribute('href', url);
184+
link.setAttribute(
185+
'download',
186+
`download_${moment().format('YYYY-MM-DD_HH-mm-ss')}.csv`
187+
);
188+
document.body.appendChild(link);
189+
link.click();
190+
document.body.removeChild(link);
191+
URL.revokeObjectURL(url);
192+
193+
return {
194+
totalItems: allData.length,
195+
columns: headers,
196+
};
197+
};
198+
199+
if (!actions) {
200+
actions = [];
201+
}
202+
203+
actions.push({
204+
tooltip: t('DOWNLOAD'),
205+
icon: CloudDownloadIcon,
206+
isFreeAction: true,
207+
onClick: (evt) => downloaddataAsCsv(),
208+
});
209+
79210
return (
80211
<MaterialTable
81212
onChangeRowsPerPage={setPageSize}

src/views/Users/Index.jsx

-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import React, { useRef, useState } from 'react';
22
import { Grid } from '@material-ui/core';
33
import { useTranslation } from 'react-i18next';
44
import moment from 'moment';
5-
import PropTypes from 'prop-types';
65
import { useHistory } from 'react-router-dom';
76

87
import { GridItem, CustomMaterialTable } from '../../components';

0 commit comments

Comments
 (0)