@openmrs/esm-reports-app 4.0.6-pre.161

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/.turbo/turbo-build.log +36 -0
  2. package/README.md +23 -0
  3. package/dist/126.js +1 -0
  4. package/dist/126.js.map +1 -0
  5. package/dist/144.js +2 -0
  6. package/dist/144.js.LICENSE.txt +19 -0
  7. package/dist/144.js.map +1 -0
  8. package/dist/156.js +1 -0
  9. package/dist/156.js.map +1 -0
  10. package/dist/202.js +2 -0
  11. package/dist/202.js.LICENSE.txt +35 -0
  12. package/dist/202.js.map +1 -0
  13. package/dist/216.js +1 -0
  14. package/dist/216.js.map +1 -0
  15. package/dist/297.js +2 -0
  16. package/dist/297.js.LICENSE.txt +32 -0
  17. package/dist/297.js.map +1 -0
  18. package/dist/300.js +1 -0
  19. package/dist/304.js +1 -0
  20. package/dist/304.js.map +1 -0
  21. package/dist/322.js +1 -0
  22. package/dist/322.js.map +1 -0
  23. package/dist/336.js +1 -0
  24. package/dist/336.js.map +1 -0
  25. package/dist/353.js +1 -0
  26. package/dist/353.js.map +1 -0
  27. package/dist/367.js +2 -0
  28. package/dist/367.js.LICENSE.txt +5 -0
  29. package/dist/367.js.map +1 -0
  30. package/dist/41.js +2 -0
  31. package/dist/41.js.LICENSE.txt +9 -0
  32. package/dist/41.js.map +1 -0
  33. package/dist/549.js +1 -0
  34. package/dist/549.js.map +1 -0
  35. package/dist/553.js +2 -0
  36. package/dist/553.js.LICENSE.txt +14 -0
  37. package/dist/553.js.map +1 -0
  38. package/dist/640.js +1 -0
  39. package/dist/640.js.map +1 -0
  40. package/dist/699.js +1 -0
  41. package/dist/699.js.map +1 -0
  42. package/dist/855.js +2 -0
  43. package/dist/855.js.LICENSE.txt +9 -0
  44. package/dist/855.js.map +1 -0
  45. package/dist/926.js +1 -0
  46. package/dist/926.js.map +1 -0
  47. package/dist/947.js +1 -0
  48. package/dist/947.js.map +1 -0
  49. package/dist/972.js +2 -0
  50. package/dist/972.js.LICENSE.txt +14 -0
  51. package/dist/972.js.map +1 -0
  52. package/dist/main.js +1 -0
  53. package/dist/main.js.map +1 -0
  54. package/dist/openmrs-esm-reports-app.js +1 -0
  55. package/dist/openmrs-esm-reports-app.js.buildmanifest.json +657 -0
  56. package/dist/openmrs-esm-reports-app.js.map +1 -0
  57. package/dist/routes.json +1 -0
  58. package/jest.config.js +3 -0
  59. package/package.json +57 -0
  60. package/src/components/edit-scheduled-report/edit-scheduled-report-form.component.tsx +149 -0
  61. package/src/components/edit-scheduled-report/edit-scheduled-report-form.scss +60 -0
  62. package/src/components/next-report-execution.component.tsx +31 -0
  63. package/src/components/overlay.component.tsx +53 -0
  64. package/src/components/overlay.scss +95 -0
  65. package/src/components/overview.component.tsx +356 -0
  66. package/src/components/pagination-constants.ts +3 -0
  67. package/src/components/report-overview-button.component.tsx +27 -0
  68. package/src/components/report-parameter-input.component.tsx +121 -0
  69. package/src/components/report-schedule-description.component.tsx +21 -0
  70. package/src/components/report-status.component.tsx +68 -0
  71. package/src/components/report-statuses-constants.ts +11 -0
  72. package/src/components/reports.resource.tsx +224 -0
  73. package/src/components/reports.scss +90 -0
  74. package/src/components/run-report/cancel-report-modal.component.tsx +129 -0
  75. package/src/components/run-report/run-report-form.component.tsx +241 -0
  76. package/src/components/run-report/run-report-form.scss +87 -0
  77. package/src/components/scheduled-overview-cell-content.component.tsx +85 -0
  78. package/src/components/scheduled-overview.component.tsx +118 -0
  79. package/src/components/scheduled-report-status.component.tsx +25 -0
  80. package/src/components/simple-cron-editor/commons.ts +47 -0
  81. package/src/components/simple-cron-editor/cron-date-picker.component.tsx +67 -0
  82. package/src/components/simple-cron-editor/cron-day-of-month-select.component.tsx +74 -0
  83. package/src/components/simple-cron-editor/cron-day-of-week-select.component.tsx +67 -0
  84. package/src/components/simple-cron-editor/cron-time-picker.component.tsx +60 -0
  85. package/src/components/simple-cron-editor/simple-cron-editor.component.tsx +342 -0
  86. package/src/components/simple-cron-editor/simple-cron-editor.scss +34 -0
  87. package/src/constants.ts +3 -0
  88. package/src/declarations.d.ts +6 -0
  89. package/src/hooks/useOverlay.tsx +45 -0
  90. package/src/index.ts +73 -0
  91. package/src/reports-link.tsx +22 -0
  92. package/src/reports.component.tsx +21 -0
  93. package/src/root.scss +15 -0
  94. package/src/routes.json +40 -0
  95. package/src/setup-tests.ts +1 -0
  96. package/src/types/report-definition.ts +12 -0
  97. package/src/types/report-design.ts +4 -0
  98. package/src/types/report-request.ts +8 -0
  99. package/src/utils/openmrs-cron-utils.ts +26 -0
  100. package/src/utils/time-utils.ts +43 -0
  101. package/translations/en.json +86 -0
  102. package/tsconfig.json +5 -0
  103. package/webpack.config.js +1 -0
@@ -0,0 +1,356 @@
1
+ import {
2
+ Button,
3
+ Checkbox,
4
+ DataTable,
5
+ Pagination,
6
+ Table,
7
+ TableBody,
8
+ TableCell,
9
+ TableContainer,
10
+ TableHead,
11
+ TableHeader,
12
+ TableRow,
13
+ } from '@carbon/react';
14
+ import React, { useCallback, useEffect, useState } from 'react';
15
+ import { useTranslation } from 'react-i18next';
16
+ import { downloadMultipleReports, downloadReport, preserveReport, useReports } from './reports.resource';
17
+ import {
18
+ ExtensionSlot,
19
+ isDesktop,
20
+ navigate,
21
+ showModal,
22
+ showSnackbar,
23
+ useLayoutType,
24
+ userHasAccess,
25
+ useSession,
26
+ } from '@openmrs/esm-framework';
27
+ import { Calendar, Download, Play, Save, TrashCan } from '@carbon/react/icons';
28
+ import styles from './reports.scss';
29
+ import { DEFAULT_PAGE_NUMBER, DEFAULT_PAGE_SIZE, DEFAULT_PAGE_SIZES } from './pagination-constants';
30
+ import { closeOverlay, launchOverlay } from '../hooks/useOverlay';
31
+ import RunReportForm from './run-report/run-report-form.component';
32
+ import Overlay from './overlay.component';
33
+ import ReportStatus from './report-status.component';
34
+ import { COMPLETED, RAN_REPORT_STATUSES, SAVED } from './report-statuses-constants';
35
+ import ReportOverviewButton from './report-overview-button.component';
36
+ import { PRIVILEGE_SYSTEM_DEVELOPER } from '../constants';
37
+ import classNames from 'classnames';
38
+
39
+ const OverviewComponent: React.FC = () => {
40
+ const { t } = useTranslation();
41
+ const currentSession = useSession();
42
+
43
+ let [checkedReportUuidsArray, setCheckedReportUuidsArray] = useState([]);
44
+ const [downloadReportButtonVisible, setDownloadReportButtonVisible] = useState(false);
45
+
46
+ useEffect(() => {
47
+ setDownloadReportButtonVisible(checkedReportUuidsArray.length > 0);
48
+ }, [checkedReportUuidsArray]);
49
+
50
+ const tableHeaders = [
51
+ { key: 'reportName', header: t('reportName', 'Report Name') },
52
+ { key: 'status', header: t('status', 'Status') },
53
+ { key: 'requestedBy', header: t('requestedBy', 'Requested by') },
54
+ { key: 'requestedOn', header: t('requestedOn', 'Requested on') },
55
+ { key: 'outputFormat', header: t('outputFormat', 'Output format') },
56
+ { key: 'parameters', header: t('parameters', 'Parameters') },
57
+ { key: 'actions', header: t('actions', 'Actions') },
58
+ ];
59
+
60
+ const [currentPage, setCurrentPage] = useState(DEFAULT_PAGE_NUMBER);
61
+ const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE);
62
+
63
+ const { reports, reportsTotalCount, mutateReports } = useReports(
64
+ RAN_REPORT_STATUSES.join(','),
65
+ currentPage,
66
+ pageSize,
67
+ );
68
+
69
+ const layout = useLayoutType();
70
+
71
+ function getReportStatus(row) {
72
+ return row?.cells.find((cell) => cell.info?.header === 'status')?.value;
73
+ }
74
+
75
+ function isCurrentUserTheSameAsReportRequestedByUser(reportRequestUuid: string) {
76
+ const report = reports.find((tableRow) => tableRow.id === reportRequestUuid);
77
+ const requestedByUserUuid = report?.requestedByUserUuid;
78
+ const currentUserUuid = currentSession?.user.uuid;
79
+
80
+ return requestedByUserUuid === currentUserUuid;
81
+ }
82
+
83
+ function isSystemDeveloperUser() {
84
+ return userHasAccess(PRIVILEGE_SYSTEM_DEVELOPER, currentSession.user);
85
+ }
86
+
87
+ function isEligibleReportUser(reportRequestUuid: string) {
88
+ return isCurrentUserTheSameAsReportRequestedByUser(reportRequestUuid) || isSystemDeveloperUser();
89
+ }
90
+
91
+ function renderRowCheckbox(row, index) {
92
+ const statusCell = row?.cells.find((cell) => cell.info?.header === 'status');
93
+ const statusValue = statusCell?.value;
94
+ if (statusValue === COMPLETED || statusValue === SAVED) {
95
+ return (
96
+ <td className={classNames({ [styles.rowCellEven]: index % 2 === 0, [styles.rowCellOdd]: index % 2 !== 0 })}>
97
+ <Checkbox
98
+ id={`checkbox-${row.id}`}
99
+ onChange={(e) => handleOnCheckboxClick(e)}
100
+ checked={checkedReportUuidsArray.includes(row.id)}
101
+ />
102
+ </td>
103
+ );
104
+ } else {
105
+ return (
106
+ <td
107
+ className={classNames({ [styles.rowCellEven]: index % 2 === 0, [styles.rowCellOdd]: index % 2 !== 0 })}
108
+ ></td>
109
+ );
110
+ }
111
+ }
112
+
113
+ function handleOnCheckboxClick(event) {
114
+ const checkboxElement = event?.target;
115
+ const checkboxId = checkboxElement.id;
116
+ const reportUuid = checkboxId.slice(checkboxId.indexOf('-') + 1);
117
+ const isChecked = checkboxElement?.checked;
118
+
119
+ setCheckedReportUuidsArray((prevState) => {
120
+ if (isChecked && !prevState.includes(reportUuid)) {
121
+ return [...prevState, reportUuid];
122
+ } else {
123
+ return prevState.filter((checkedReportUuid) => checkedReportUuid !== reportUuid);
124
+ }
125
+ });
126
+ }
127
+
128
+ const handlePreserveReport = useCallback(async (reportRequestUuid: string) => {
129
+ preserveReport(reportRequestUuid)
130
+ .then(() => {
131
+ mutateReports();
132
+ showSnackbar({
133
+ kind: 'success',
134
+ title: t('preserveReport', 'Preserve report'),
135
+ subtitle: t('reportPreservedSuccessfully', 'Report preserved successfully'),
136
+ });
137
+ })
138
+ .catch(() => {
139
+ showSnackbar({
140
+ kind: 'error',
141
+ title: t('preserveReport', 'Preserve report'),
142
+ subtitle: t('reportPreservingErrorMsg', 'Error during report preserving'),
143
+ });
144
+ });
145
+ }, []);
146
+
147
+ const launchDeleteReportDialog = (reportRequestUuid: string) => {
148
+ const dispose = showModal('cancel-report-modal', {
149
+ closeModal: () => {
150
+ dispose();
151
+ mutateReports();
152
+ },
153
+ reportRequestUuid,
154
+ modalType: 'delete',
155
+ });
156
+ };
157
+
158
+ const handleDownloadReport = useCallback(async (reportRequestUuid: string) => {
159
+ try {
160
+ const response = await downloadReport(reportRequestUuid);
161
+ processAndDownloadFile(response);
162
+ clearReportCheckboxes();
163
+ showSnackbar({
164
+ kind: 'success',
165
+ title: t('downloadReport', 'Download report'),
166
+ subtitle: t('reportDownloadedSuccessfully', 'Report downloaded successfully'),
167
+ });
168
+ } catch (error) {
169
+ showSnackbar({
170
+ kind: 'error',
171
+ title: t('downloadReport', 'Download report'),
172
+ subtitle: t('reportDownloadError', 'Error downloading report'),
173
+ });
174
+ }
175
+ }, []);
176
+
177
+ const handleDownloadMultipleReports = useCallback(async (reportRequestUuids) => {
178
+ try {
179
+ const response = await downloadMultipleReports(reportRequestUuids);
180
+ response.forEach((file) => processAndDownloadFile(file));
181
+ clearReportCheckboxes();
182
+ showSnackbar({
183
+ kind: 'success',
184
+ title: t('downloadReport', 'Download report(s)'),
185
+ subtitle: t('reportDownloadedSuccessfully', 'Report(s) downloaded successfully'),
186
+ });
187
+ } catch (error) {
188
+ showSnackbar({
189
+ kind: 'error',
190
+ title: t('downloadReport', 'Download report(s)s'),
191
+ subtitle: t('reportDownloadingErrorMsg', 'Error during report(s) downloading'),
192
+ });
193
+ }
194
+ }, []);
195
+
196
+ const processAndDownloadFile = (file) => {
197
+ const decodedData = window.atob(file.fileContent);
198
+ const byteArray = new Uint8Array(decodedData.length);
199
+ for (let i = 0; i < decodedData.length; i++) {
200
+ byteArray[i] = decodedData.charCodeAt(i);
201
+ }
202
+ const url = window.URL.createObjectURL(new Blob([byteArray]));
203
+ const link = document.createElement('a');
204
+ link.href = url;
205
+ link.setAttribute('download', file.filename);
206
+ document.body.appendChild(link);
207
+ link.click();
208
+ document.body.removeChild(link);
209
+ };
210
+
211
+ const clearReportCheckboxes = () => {
212
+ setCheckedReportUuidsArray([]);
213
+ };
214
+
215
+ return (
216
+ <div>
217
+ <ExtensionSlot name="breadcrumbs-slot" className={styles.breadcrumb} />
218
+ <div className={styles.mainPanelDiv}>
219
+ <div className={styles.reportsLabelDiv}>
220
+ <h3>{t('reports', 'Reports')}</h3>
221
+ </div>
222
+ <div className={styles.mainActionButtonsDiv}>
223
+ <Button
224
+ kind="ghost"
225
+ renderIcon={() => <Download size={16} style={{ fill: '#0F62FE' }} className={styles.actionButtonIcon} />}
226
+ iconDescription="Download reports"
227
+ onClick={() => handleDownloadMultipleReports(checkedReportUuidsArray.join(','))}
228
+ className={classNames(styles.mainActionButton, {
229
+ [styles.downloadReportsVisible]: downloadReportButtonVisible,
230
+ [styles.downloadReportsHidden]: !downloadReportButtonVisible,
231
+ })}
232
+ >
233
+ {t('downloadReports', 'Download reports')}
234
+ </Button>
235
+ <Button
236
+ kind="ghost"
237
+ renderIcon={() => <Play size={16} style={{ fill: '#0F62FE' }} className={styles.actionButtonIcon} />}
238
+ iconDescription="Run reports"
239
+ onClick={() => {
240
+ launchOverlay(
241
+ t('runReport', 'Run Report'),
242
+ <RunReportForm
243
+ closePanel={() => {
244
+ closeOverlay();
245
+ mutateReports();
246
+ }}
247
+ />,
248
+ );
249
+ }}
250
+ className={styles.mainActionButton}
251
+ >
252
+ {t('runReports', 'Run reports')}
253
+ </Button>
254
+ <Overlay />
255
+ <Button
256
+ kind="ghost"
257
+ renderIcon={() => <Calendar size={16} style={{ fill: '#0F62FE' }} className={styles.actionButtonIcon} />}
258
+ iconDescription="Report schedule"
259
+ onClick={() => navigate({ to: `\${openmrsSpaBase}/reports/scheduled-overview` })}
260
+ className={styles.mainActionButton}
261
+ >
262
+ {t('reportSchedule', 'Report schedule')}
263
+ </Button>
264
+ </div>
265
+ </div>
266
+ <DataTable rows={reports} headers={tableHeaders} isSortable>
267
+ {({ rows, headers }) => (
268
+ <TableContainer>
269
+ <Table>
270
+ <TableHead>
271
+ <TableRow>
272
+ <th></th>
273
+ {headers.map((header) => (
274
+ <TableHeader>{header.header?.content ?? header.header}</TableHeader>
275
+ ))}
276
+ </TableRow>
277
+ </TableHead>
278
+ <TableBody>
279
+ {rows.map((row, index) => (
280
+ <TableRow className={styles.tableRow}>
281
+ {renderRowCheckbox(row, index)}
282
+ {row.cells.map((cell) => (
283
+ <TableCell
284
+ key={cell.id}
285
+ className={classNames({
286
+ [styles.rowCellEven]: index % 2 === 0,
287
+ [styles.rowCellOdd]: index % 2 !== 0,
288
+ })}
289
+ >
290
+ {cell.info.header === 'actions' ? (
291
+ <div>
292
+ <ReportOverviewButton
293
+ shouldBeDisplayed={getReportStatus(row) === COMPLETED || getReportStatus(row) === SAVED}
294
+ label={t('download', 'Download')}
295
+ icon={() => <Download size={16} className={styles.actionButtonIcon} />}
296
+ reportRequestUuid={row.id}
297
+ onClick={() => handleDownloadReport(row.id)}
298
+ />
299
+ <ReportOverviewButton
300
+ shouldBeDisplayed={getReportStatus(row) === COMPLETED && isEligibleReportUser(row.id)}
301
+ label={t('preserve', 'Preserve')}
302
+ icon={() => <Save size={16} className={styles.actionButtonIcon} />}
303
+ reportRequestUuid={row.id}
304
+ onClick={() => handlePreserveReport(row.id)}
305
+ />
306
+ <ReportOverviewButton
307
+ shouldBeDisplayed={isEligibleReportUser(row.id)}
308
+ label={t('delete', 'Delete')}
309
+ icon={() => <TrashCan size={16} className={styles.actionButtonIcon} />}
310
+ reportRequestUuid={row.id}
311
+ onClick={() => launchDeleteReportDialog(row.id)}
312
+ />
313
+ </div>
314
+ ) : cell.info.header === 'status' ? (
315
+ <div>
316
+ <ReportStatus status={cell.value} />
317
+ </div>
318
+ ) : cell.info.header === 'reportName' ? (
319
+ <div>{cell.value?.content ?? cell.value}</div>
320
+ ) : (
321
+ cell.value?.content ?? cell.value
322
+ )}
323
+ </TableCell>
324
+ ))}
325
+ </TableRow>
326
+ ))}
327
+ </TableBody>
328
+ </Table>
329
+ </TableContainer>
330
+ )}
331
+ </DataTable>
332
+ {reports.length > 0 ? (
333
+ <Pagination
334
+ backwardText={t('previousPage', 'Previous page')}
335
+ forwardText={t('nextPage', 'Next page')}
336
+ page={currentPage}
337
+ pageSize={pageSize}
338
+ pageSizes={DEFAULT_PAGE_SIZES}
339
+ totalItems={reportsTotalCount}
340
+ size={isDesktop(layout) ? 'sm' : 'lg'}
341
+ onChange={({ pageSize: newPageSize, page: newPage }) => {
342
+ if (newPageSize !== pageSize) {
343
+ setPageSize(newPageSize);
344
+ }
345
+
346
+ if (newPage !== currentPage) {
347
+ setCurrentPage(newPage);
348
+ }
349
+ }}
350
+ />
351
+ ) : null}
352
+ </div>
353
+ );
354
+ };
355
+
356
+ export default OverviewComponent;
@@ -0,0 +1,3 @@
1
+ export const DEFAULT_PAGE_SIZE = 10;
2
+ export const DEFAULT_PAGE_SIZES = [5, 10, 20, 50];
3
+ export const DEFAULT_PAGE_NUMBER = 1;
@@ -0,0 +1,27 @@
1
+ import React from 'react';
2
+ import { Button } from '@carbon/react';
3
+ import styles from './reports.scss';
4
+
5
+ interface ReportOverviewButtonProps {
6
+ shouldBeDisplayed: boolean;
7
+ label: string;
8
+ icon: any;
9
+ reportRequestUuid: string;
10
+ onClick: () => void;
11
+ }
12
+
13
+ const ReportOverviewButton: React.FC<ReportOverviewButtonProps> = ({ shouldBeDisplayed, label, icon, onClick }) => {
14
+ if (shouldBeDisplayed) {
15
+ return (
16
+ <div className={styles.actionButtonsWrapperDiv}>
17
+ <Button kind="ghost" renderIcon={icon} iconDescription={label} onClick={onClick} className={styles.actionButon}>
18
+ {label}
19
+ </Button>
20
+ </div>
21
+ );
22
+ } else {
23
+ return <div className={styles.actionButtonsWrapperDiv}></div>;
24
+ }
25
+ };
26
+
27
+ export default ReportOverviewButton;
@@ -0,0 +1,121 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import styles from './run-report/run-report-form.scss';
3
+ import { DatePicker, DatePickerInput, Select, SelectItem, TextInput } from '@carbon/react';
4
+ import { useLocations } from './reports.resource';
5
+ import { isEqual } from 'lodash-es';
6
+
7
+ interface ReportParameterInputProps {
8
+ parameter: any;
9
+ value: any;
10
+ onChange: (value) => void;
11
+ }
12
+
13
+ function getInitialValue(parameter: any, value: any) {
14
+ if (parameter.type === 'java.util.Date') {
15
+ return new Date(value);
16
+ } else if (parameter.type === 'org.openmrs.Location') {
17
+ return value?.uuid;
18
+ } else {
19
+ return value;
20
+ }
21
+ }
22
+
23
+ const ReportParameterInput: React.FC<ReportParameterInputProps> = ({ parameter, value, onChange }) => {
24
+ const { locations } = useLocations();
25
+ const [valueInternal, setValueInternal] = useState<string | number | Date>(getInitialValue(parameter, value));
26
+
27
+ useEffect(() => {
28
+ const newInternalValue = getInitialValue(parameter, value);
29
+ if (!isValueEqual(newInternalValue, valueInternal)) {
30
+ setValueInternal(newInternalValue);
31
+ }
32
+ }, [value]);
33
+
34
+ const isValueEqual = (valueA, valueB) => {
35
+ if (parameter.type === 'java.util.Date') {
36
+ return isEqual(new Date(valueA), new Date(valueB));
37
+ } else {
38
+ return isEqual(valueA, valueB);
39
+ }
40
+ };
41
+
42
+ useEffect(() => {
43
+ if (parameter.type === 'java.util.Date') {
44
+ onChange(new Date(valueInternal).toLocaleDateString());
45
+ } else {
46
+ onChange(valueInternal);
47
+ }
48
+ }, [valueInternal]);
49
+
50
+ const renderParameterElementBasedOnType = () => {
51
+ switch (parameter.type) {
52
+ case 'java.util.Date':
53
+ return (
54
+ <DatePicker
55
+ datePickerType="single"
56
+ name={parameter.name}
57
+ onChange={([dateValue]) => handleOnDateChange(dateValue)}
58
+ className={styles.datePicker}
59
+ value={valueInternal}
60
+ >
61
+ <DatePickerInput id={parameter.name} name={parameter.name} labelText={parameter.label} type="date" />
62
+ </DatePicker>
63
+ );
64
+ case 'java.lang.String':
65
+ case 'java.lang.Integer':
66
+ return (
67
+ <TextInput
68
+ id={parameter.name}
69
+ name={parameter.name}
70
+ labelText={parameter.label}
71
+ className={styles.basicInputElement}
72
+ onChange={(e) => handleOnChange(e)}
73
+ value={valueInternal}
74
+ />
75
+ );
76
+ case 'org.openmrs.Location':
77
+ return (
78
+ <Select
79
+ id={parameter.name}
80
+ name={parameter.name}
81
+ labelText={parameter.label}
82
+ className={styles.basicInputElement}
83
+ onChange={(e) => handleOnChange(e)}
84
+ value={valueInternal}
85
+ >
86
+ <SelectItem text="" value={''} />
87
+ {locations?.map((location) => (
88
+ <SelectItem key={location.uuid} text={location.display} value={location.uuid}>
89
+ {location.display}
90
+ </SelectItem>
91
+ ))}
92
+ </Select>
93
+ );
94
+ default:
95
+ return (
96
+ <span className={styles.unknownParameterTypeSpan}>
97
+ {`Unknown parameter type: ${parameter.type} for parameter: ${parameter.label}`}
98
+ </span>
99
+ );
100
+ }
101
+ };
102
+
103
+ function handleOnChange(event) {
104
+ let eventValue = null;
105
+ if (event.target.type == 'checkbox') {
106
+ eventValue = event.target.checked;
107
+ } else {
108
+ eventValue = event.target.value;
109
+ }
110
+
111
+ setValueInternal(eventValue);
112
+ }
113
+
114
+ function handleOnDateChange(dateValue) {
115
+ setValueInternal(new Date(dateValue));
116
+ }
117
+
118
+ return <div className={styles.runReportInnerDivElement}>{renderParameterElementBasedOnType()}</div>;
119
+ };
120
+
121
+ export default ReportParameterInput;
@@ -0,0 +1,21 @@
1
+ import React from 'react';
2
+ import { useSession } from '@openmrs/esm-framework';
3
+ import cronstrue from 'cronstrue';
4
+
5
+ interface ReportScheduleProps {
6
+ schedule: string;
7
+ }
8
+
9
+ const ReportScheduleDescription: React.FC<ReportScheduleProps> = ({ schedule }) => {
10
+ const session = useSession();
11
+ const scheduleDescription = schedule
12
+ ? cronstrue.toString(schedule, {
13
+ locale: session.locale,
14
+ use24HourTimeFormat: true,
15
+ dayOfWeekStartIndexZero: true,
16
+ })
17
+ : '';
18
+ return <span>{scheduleDescription}</span>;
19
+ };
20
+
21
+ export default ReportScheduleDescription;
@@ -0,0 +1,68 @@
1
+ import React from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { CheckmarkFilled, CheckmarkOutline, CloseFilled, Queued } from '@carbon/react/icons';
4
+ import { Loading } from '@carbon/react';
5
+ import styles from './reports.scss';
6
+ import {
7
+ COMPLETED,
8
+ FAILED,
9
+ PROCESSING,
10
+ SAVED,
11
+ SCHEDULED,
12
+ SCHEDULE_COMPLETED,
13
+ REQUESTED,
14
+ } from './report-statuses-constants';
15
+
16
+ interface ReportStatusProps {
17
+ status: string;
18
+ }
19
+
20
+ const ReportStatus: React.FC<ReportStatusProps> = ({ status }) => {
21
+ const { t } = useTranslation();
22
+ return (
23
+ <>
24
+ {status === SAVED && (
25
+ <>
26
+ <CheckmarkFilled size={16} className={`${styles.statusIcon} ${styles.successIcon}`} />
27
+ {t('completedAndPreserved', 'Completed and Preserved')}
28
+ </>
29
+ )}
30
+ {status === COMPLETED && (
31
+ <>
32
+ <CheckmarkOutline size={16} className={`${styles.statusIcon} ${styles.successIcon}`} />
33
+ {t('completed', 'Completed')}
34
+ </>
35
+ )}
36
+ {status === PROCESSING && (
37
+ <>
38
+ <Loading small={true} withOverlay={false} className={`${styles.statusIcon} ${styles.runningIcon}`} />
39
+ {t('running', 'Running')}
40
+ </>
41
+ )}
42
+ {status === FAILED && (
43
+ <>
44
+ <CloseFilled size={16} className={`${styles.statusIcon} ${styles.failedIcon}`} />
45
+ {t('failed', 'Failed')}
46
+ </>
47
+ )}
48
+ {status === SCHEDULED && (
49
+ <>
50
+ <span className={styles.scheduledStatusText}>{t('scheduled', 'Scheduled')}</span>
51
+ </>
52
+ )}
53
+ {status === SCHEDULE_COMPLETED && (
54
+ <>
55
+ <span className={styles.scheduleCompletedStatusText}>{t('scheduleCompleted', 'Schedule completed')}</span>
56
+ </>
57
+ )}
58
+ {status === REQUESTED && (
59
+ <>
60
+ <Queued size={16} className={`${styles.statusIcon}`} />
61
+ {t('queued', 'Queued')}
62
+ </>
63
+ )}
64
+ </>
65
+ );
66
+ };
67
+
68
+ export default ReportStatus;
@@ -0,0 +1,11 @@
1
+ export const SAVED = 'SAVED';
2
+ export const COMPLETED = 'COMPLETED';
3
+ export const PROCESSING = 'PROCESSING';
4
+ export const FAILED = 'FAILED';
5
+ export const SCHEDULED = 'SCHEDULED';
6
+ export const SCHEDULE_COMPLETED = 'SCHEDULE_COMPLETED';
7
+ export const REQUESTED = 'REQUESTED';
8
+
9
+ export const RAN_REPORT_STATUSES = [REQUESTED, COMPLETED, SAVED, FAILED, PROCESSING];
10
+ export const PROCESSING_REPORT_STATUSES = [REQUESTED, PROCESSING];
11
+ export const SCHEDULED_REPORT_STATUSES = [SCHEDULED, SCHEDULE_COMPLETED];