diff --git a/package.json b/package.json
index f420183f36..29b8ab6f1d 100644
--- a/package.json
+++ b/package.json
@@ -8,6 +8,7 @@
"@hospitalrun/components": "~1.16.0",
"@reduxjs/toolkit": "~1.4.0",
"@types/escape-string-regexp": "~2.0.1",
+ "@types/json2csv": "~5.0.1",
"@types/pouchdb-find": "~6.3.4",
"bootstrap": "~4.5.0",
"date-fns": "~2.16.0",
@@ -15,6 +16,7 @@
"i18next": "~19.7.0",
"i18next-browser-languagedetector": "~6.0.0",
"i18next-xhr-backend": "~3.2.2",
+ "json2csv": "~5.0.1",
"lodash": "^4.17.15",
"node-sass": "~4.14.0",
"pouchdb": "~7.2.1",
diff --git a/src/__tests__/incidents/list/ViewIncidentsTable.test.tsx b/src/__tests__/incidents/list/ViewIncidentsTable.test.tsx
index d01ef96a52..c0168d6794 100644
--- a/src/__tests__/incidents/list/ViewIncidentsTable.test.tsx
+++ b/src/__tests__/incidents/list/ViewIncidentsTable.test.tsx
@@ -1,4 +1,5 @@
-import { Table } from '@hospitalrun/components'
+import { Table, Dropdown } from '@hospitalrun/components'
+import format from 'date-fns/format'
import { mount, ReactWrapper } from 'enzyme'
import { createMemoryHistory } from 'history'
import React from 'react'
@@ -6,7 +7,7 @@ import { act } from 'react-dom/test-utils'
import { Router } from 'react-router'
import IncidentFilter from '../../../incidents/IncidentFilter'
-import ViewIncidentsTable from '../../../incidents/list/ViewIncidentsTable'
+import ViewIncidentsTable, { populateExportData } from '../../../incidents/list/ViewIncidentsTable'
import IncidentSearchRequest from '../../../incidents/model/IncidentSearchRequest'
import IncidentRepository from '../../../shared/db/IncidentRepository'
import Incident from '../../../shared/model/Incident'
@@ -73,6 +74,58 @@ describe('View Incidents Table', () => {
expect(incidentsTable.prop('actionsHeaderText')).toEqual('actions.label')
})
+ it('should display a download button', async () => {
+ const expectedIncidents: Incident[] = [
+ {
+ id: 'incidentId1',
+ code: 'someCode',
+ date: new Date(2020, 7, 4, 0, 0, 0, 0).toISOString(),
+ reportedOn: new Date(2020, 8, 4, 0, 0, 0, 0).toISOString(),
+ reportedBy: 'com.test:user',
+ status: 'reported',
+ } as Incident,
+ ]
+ const { wrapper } = await setup({ status: IncidentFilter.all }, expectedIncidents)
+
+ const dropDownButton = wrapper.find(Dropdown)
+ expect(dropDownButton.exists()).toBeTruthy()
+ })
+
+ it('should populate export data correctly', async () => {
+ const data = [
+ {
+ category: 'asdf',
+ categoryItem: 'asdf',
+ code: 'I-eClU6OdkR',
+ createdAt: '2020-09-06T04:02:38.011Z',
+ date: '2020-09-06T04:02:32.855Z',
+ department: 'asdf',
+ description: 'asdf',
+ id: 'af9f968f-61d9-47c3-9321-5da3f381c38b',
+ reportedBy: 'some user',
+ reportedOn: '2020-09-06T04:02:38.011Z',
+ rev: '1-91d1ba60588b779c9554c7e20e15419c',
+ status: 'reported',
+ updatedAt: '2020-09-06T04:02:38.011Z',
+ },
+ ]
+
+ const expectedExportData = [
+ {
+ code: 'I-eClU6OdkR',
+ date: format(new Date(data[0].date), 'yyyy-MM-dd hh:mm a'),
+ reportedBy: 'some user',
+ reportedOn: format(new Date(data[0].reportedOn), 'yyyy-MM-dd hh:mm a'),
+ status: 'reported',
+ },
+ ]
+
+ const exportData = [{}]
+ populateExportData(exportData, data)
+
+ expect(exportData).toEqual(expectedExportData)
+ })
+
it('should format the data correctly', async () => {
const expectedIncidents: Incident[] = [
{
diff --git a/src/__tests__/shared/utils/DataHelpers.test.ts b/src/__tests__/shared/utils/DataHelpers.test.ts
new file mode 100644
index 0000000000..16342f7414
--- /dev/null
+++ b/src/__tests__/shared/utils/DataHelpers.test.ts
@@ -0,0 +1,33 @@
+import { getCSV, DownloadLink } from '../../../shared/util/DataHelpers'
+
+describe('Use Data Helpers util', () => {
+ it('should construct csv', () => {
+ const input = [
+ {
+ code: 'I-eClU6OdkR',
+ date: '2020-09-06 12:02 PM',
+ reportedBy: 'some user',
+ reportedOn: '2020-09-06 12:02 PM',
+ status: 'reported',
+ },
+ ]
+ const output = getCSV(input).replace(/(\r\n|\n|\r)/gm, '')
+ const expectedOutput =
+ '"code","date","reportedBy","reportedOn","status""I-eClU6OdkR","2020-09-06 12:02 PM","some user","2020-09-06 12:02 PM","reported"'
+ expect(output).toMatch(expectedOutput)
+ })
+
+ it('should download data as expected', () => {
+ const response = DownloadLink('data to be downloaded', 'filename.txt')
+
+ const element = document.createElement('a')
+ element.setAttribute(
+ 'href',
+ `data:text/plain;charset=utf-8,${encodeURIComponent('data to be downloaded')}`,
+ )
+ element.setAttribute('download', 'filename.txt')
+
+ element.style.display = 'none'
+ expect(response).toEqual(element)
+ })
+})
diff --git a/src/incidents/list/ViewIncidentsTable.tsx b/src/incidents/list/ViewIncidentsTable.tsx
index d57532e3f4..e38f723de2 100644
--- a/src/incidents/list/ViewIncidentsTable.tsx
+++ b/src/incidents/list/ViewIncidentsTable.tsx
@@ -1,9 +1,10 @@
-import { Spinner, Table } from '@hospitalrun/components'
+import { Spinner, Table, Dropdown } from '@hospitalrun/components'
import format from 'date-fns/format'
import React from 'react'
import { useHistory } from 'react-router'
import useTranslator from '../../shared/hooks/useTranslator'
+import { DownloadLink, getCSV } from '../../shared/util/DataHelpers'
import { extractUsername } from '../../shared/util/extractUsername'
import useIncidents from '../hooks/useIncidents'
import IncidentSearchRequest from '../model/IncidentSearchRequest'
@@ -12,6 +13,27 @@ interface Props {
searchRequest: IncidentSearchRequest
}
+export function populateExportData(dataToPopulate: any, theData: any) {
+ let first = true
+ if (theData != null) {
+ theData.forEach((elm: any) => {
+ const entry = {
+ code: elm.code,
+ date: format(new Date(elm.date), 'yyyy-MM-dd hh:mm a'),
+ reportedBy: elm.reportedBy,
+ reportedOn: format(new Date(elm.reportedOn), 'yyyy-MM-dd hh:mm a'),
+ status: elm.status,
+ }
+ if (first) {
+ dataToPopulate[0] = entry
+ first = false
+ } else {
+ dataToPopulate.push(entry)
+ }
+ })
+ }
+}
+
function ViewIncidentsTable(props: Props) {
const { searchRequest } = props
const { t } = useTranslator()
@@ -22,33 +44,85 @@ function ViewIncidentsTable(props: Props) {
return