ui-soxo-bootstrap-core 2.6.31 → 2.6.32-dev.1
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.
- package/core/components/index.js +2 -11
- package/core/components/landing-api/landing-api.js +165 -5
- package/core/components/license-management/license-alert.js +97 -0
- package/core/lib/components/global-header/global-header.js +18 -77
- package/core/lib/components/index.js +2 -2
- package/core/lib/components/sidemenu/sidemenu.js +13 -8
- package/core/models/core-scripts/core-scripts.js +14 -1
- package/core/models/menus/menus.js +29 -1
- package/core/modules/reporting/components/reporting-dashboard/display-columns/display-cell-renderer.js +202 -5
- package/core/modules/reporting/components/reporting-dashboard/display-columns/display-cell-renderer.test.js +73 -0
- package/core/modules/reporting/components/reporting-dashboard/reporting-dashboard.js +143 -527
- package/core/modules/reporting/components/reporting-dashboard/reporting-table.js +519 -0
- package/package.json +1 -1
|
@@ -2,35 +2,30 @@ import React, { useState, useEffect, useContext, useRef } from 'react';
|
|
|
2
2
|
|
|
3
3
|
import { Table, Skeleton, Input, Modal, message, Pagination, Tag } from 'antd';
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
import { Location, FormCreator, GlobalContext, ExportReactCSV, getExportData, Card, TableComponent, QrScanner } from './../../../../lib/';
|
|
5
|
+
import { Location, FormCreator, GlobalContext, Card } from './../../../../lib/';
|
|
8
6
|
|
|
9
7
|
import { CoreScripts } from './../../../../models/';
|
|
10
8
|
|
|
11
9
|
import moment from 'moment-timezone';
|
|
12
10
|
|
|
13
|
-
import Button from '../../../../lib/elements/basic/button/button';
|
|
14
|
-
|
|
15
11
|
import './reporting-dashboard.scss';
|
|
16
12
|
|
|
17
13
|
// import MenuDashBoard from '../../../../pages/homepage-api/menu-dashboard';
|
|
18
14
|
import MenuDashBoardComponent from '../../../../lib/elements/basic/menu-dashboard/menu-dashboard';
|
|
19
|
-
import { useHistory } from 'react-router-dom';
|
|
20
|
-
import * as ReportingDashboardComp from '../index';
|
|
21
|
-
import buildDisplayColumns from './display-columns/build-display-columns';
|
|
22
|
-
import { getRedirectLink } from './display-columns/display-cell-renderer';
|
|
23
|
-
import AdvancedSearchSelect from './adavance-search/advance-search';
|
|
24
15
|
|
|
25
|
-
|
|
16
|
+
import ReportingTable from './reporting-table';
|
|
17
|
+
// import * as ReportingDashboardComp from '../index';
|
|
18
|
+
// import buildDisplayColumns from './display-columns/build-display-columns';
|
|
19
|
+
// import AdvancedSearchSelect from './adavance-search/advance-search';
|
|
20
|
+
|
|
21
|
+
// // import { isPdfFile } from 'pdfjs-dist';
|
|
26
22
|
|
|
27
|
-
var genericComponents = require('./../../../../lib');
|
|
23
|
+
// var genericComponents = require('./../../../../lib');
|
|
28
24
|
|
|
29
25
|
const { Search } = Input;
|
|
30
26
|
|
|
31
27
|
/**
|
|
32
28
|
* ReportingDashboard component renders the dashboard and handles patient details,
|
|
33
|
-
* configuration, and form layout for generating reports.
|
|
34
29
|
*
|
|
35
30
|
* @param {Object} props - The component's props.
|
|
36
31
|
* @param {Object} props.match - The match object containing the URL parameters.
|
|
@@ -56,7 +51,7 @@ export default function ReportingDashboard({
|
|
|
56
51
|
const [config, setConfig] = useState({});
|
|
57
52
|
|
|
58
53
|
// State to manage the layout of the form
|
|
59
|
-
const [formLayout
|
|
54
|
+
const [formLayout] = useState('vertical');
|
|
60
55
|
|
|
61
56
|
const [loading, setLoading] = useState(true);
|
|
62
57
|
|
|
@@ -83,9 +78,9 @@ export default function ReportingDashboard({
|
|
|
83
78
|
|
|
84
79
|
const [columns, setColumns] = useState([]); // To set columns
|
|
85
80
|
|
|
86
|
-
const [
|
|
81
|
+
const [, setSummaryColumns] = useState([]);
|
|
87
82
|
|
|
88
|
-
const [
|
|
83
|
+
const [reportRequestPayload, setReportRequestPayload] = useState(null);
|
|
89
84
|
|
|
90
85
|
const urlParams = Location.search();
|
|
91
86
|
|
|
@@ -106,7 +101,6 @@ export default function ReportingDashboard({
|
|
|
106
101
|
* @returns {Promise<void>} A promise that resolves when the patient details have been fetched and the state has been updated.
|
|
107
102
|
*/
|
|
108
103
|
async function getPatientDetails(idOverride) {
|
|
109
|
-
setPatients([]);
|
|
110
104
|
const fetchId = idOverride || id;
|
|
111
105
|
await CoreScripts.getRecord({ id: fetchId, dbPtr }).then(async ({ result }) => {
|
|
112
106
|
// Check if display columns are provided from backend
|
|
@@ -266,85 +260,40 @@ export default function ReportingDashboard({
|
|
|
266
260
|
// Refresh patient details.
|
|
267
261
|
|
|
268
262
|
function refresh() {
|
|
269
|
-
getPatientDetails();
|
|
263
|
+
getPatientDetails(scriptId.current || id);
|
|
270
264
|
}
|
|
271
265
|
|
|
272
|
-
const
|
|
273
|
-
const
|
|
274
|
-
|
|
275
|
-
const coreScriptId = scriptId.current ? scriptId.current : id;
|
|
276
|
-
const normalizedColumns = Array.isArray(parsedColumns) ? parsedColumns : Array.isArray(columns) ? columns : [];
|
|
266
|
+
const buildReportRequestPayload = (values = {}, paginationOverride) => {
|
|
267
|
+
const pager = paginationOverride || pagination;
|
|
268
|
+
const formattedValues = {};
|
|
277
269
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
};
|
|
291
|
-
// Combine form data + pagination
|
|
292
|
-
let formBody = {
|
|
270
|
+
Object.keys(values || {}).forEach((key) => {
|
|
271
|
+
const val = values[key];
|
|
272
|
+
formattedValues[key] = moment.isMoment(val) ? val.format('YYYY-MM-DD') : val;
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
const paginationData = {
|
|
276
|
+
page: pager.current || 1,
|
|
277
|
+
limit: pager.pageSize || 10,
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
if (scope) {
|
|
281
|
+
return {
|
|
293
282
|
body: {
|
|
294
|
-
...
|
|
283
|
+
...scope,
|
|
295
284
|
...paginationData,
|
|
296
285
|
},
|
|
297
286
|
};
|
|
298
|
-
// Optional override if `scope` exists
|
|
299
|
-
if (scope) {
|
|
300
|
-
formBody = { body: { ...scope, ...paginationData } };
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
// Fetch result
|
|
304
|
-
const result = await CoreScripts.getReportingLisitng(coreScriptId, formBody, dbPtr);
|
|
305
|
-
|
|
306
|
-
const apiData = Array.isArray(result) ? result : Array.isArray(result?.result) ? result.result : [];
|
|
307
|
-
|
|
308
|
-
// Handle both result formats
|
|
309
|
-
let resultDetails = apiData[0] || [];
|
|
310
|
-
if (result?.result && result?.result[0]) {
|
|
311
|
-
resultDetails = result.result[0];
|
|
312
|
-
}
|
|
313
|
-
// Update patients
|
|
314
|
-
setPatients(resultDetails || []);
|
|
315
|
-
|
|
316
|
-
// When display_columns is missing, build columns from the response keys.
|
|
317
|
-
if (normalizedColumns.length === 0 && resultDetails.length > 0) {
|
|
318
|
-
// Create columns dynamically from resultDetails keys
|
|
319
|
-
setColumns((prev) => {
|
|
320
|
-
if (prev.length > 0) return prev;
|
|
321
|
-
return Object.keys(resultDetails[0]).map((key) => ({
|
|
322
|
-
title: key,
|
|
323
|
-
field: key,
|
|
324
|
-
}));
|
|
325
|
-
});
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
if (result.length) {
|
|
329
|
-
// Set Pgination data into URL
|
|
330
|
-
Location.search({ ...Location.search(), current, pageSize });
|
|
331
|
-
|
|
332
|
-
setPagination((prev) => ({
|
|
333
|
-
...prev,
|
|
334
|
-
current: pagination.current,
|
|
335
|
-
pageSize: pagination.pageSize,
|
|
336
|
-
total: resultDetails?.[0]?.TotalCount ?? pagination?.total,
|
|
337
|
-
}));
|
|
338
|
-
}
|
|
339
|
-
} catch (error) {
|
|
340
|
-
console.error('Error fetching report data:', error);
|
|
341
|
-
} finally {
|
|
342
|
-
// Always runs, success or error
|
|
343
|
-
setLoading(false);
|
|
344
287
|
}
|
|
345
|
-
};
|
|
346
288
|
|
|
347
|
-
|
|
289
|
+
return {
|
|
290
|
+
body: {
|
|
291
|
+
...formattedValues,
|
|
292
|
+
...paginationData,
|
|
293
|
+
},
|
|
294
|
+
};
|
|
295
|
+
};
|
|
296
|
+
const handleSubmit = (values) => {
|
|
348
297
|
// Extract search fields from the form values
|
|
349
298
|
const searchKeys = searchParameters.map((p) => p.field);
|
|
350
299
|
const currentSearchValues = {};
|
|
@@ -424,6 +373,86 @@ export default function ReportingDashboard({
|
|
|
424
373
|
runSubmit(finalValues);
|
|
425
374
|
}
|
|
426
375
|
};
|
|
376
|
+
// const handleSubmit = (values) => {
|
|
377
|
+
// // Extract search fields from the form values
|
|
378
|
+
// const searchKeys = searchParameters.map((p) => p.field);
|
|
379
|
+
// const currentSearchValues = {};
|
|
380
|
+
// const formValues = {};
|
|
381
|
+
|
|
382
|
+
// Object.keys(values).forEach((key) => {
|
|
383
|
+
// if (searchKeys.includes(key)) {
|
|
384
|
+
// currentSearchValues[key] = values[key];
|
|
385
|
+
// } else {
|
|
386
|
+
// formValues[key] = values[key];
|
|
387
|
+
// }
|
|
388
|
+
// });
|
|
389
|
+
|
|
390
|
+
// const hasSearchValues = Object.values(currentSearchValues).some((v) => Array.isArray(v) && v.length > 0);
|
|
391
|
+
|
|
392
|
+
// if (!hasSearchValues) {
|
|
393
|
+
// runSubmit(formValues);
|
|
394
|
+
// return;
|
|
395
|
+
// }
|
|
396
|
+
|
|
397
|
+
// // Check if main form values (non-search) changed
|
|
398
|
+
// const formChanged = Object.keys(formValues).some((key) => {
|
|
399
|
+
// const newVal = formValues[key];
|
|
400
|
+
// const oldVal = formContents[key];
|
|
401
|
+
|
|
402
|
+
// if (moment.isMoment(newVal) && moment.isMoment(oldVal)) {
|
|
403
|
+
// return !newVal.isSame(oldVal, 'day');
|
|
404
|
+
// }
|
|
405
|
+
|
|
406
|
+
// return newVal !== oldVal;
|
|
407
|
+
// });
|
|
408
|
+
|
|
409
|
+
// if (formChanged) {
|
|
410
|
+
// Modal.confirm({
|
|
411
|
+
// title: 'Filters changed',
|
|
412
|
+
// content: 'You changed some filters. Do you want to search using these filters also?',
|
|
413
|
+
// okText: 'Yes',
|
|
414
|
+
// cancelText: 'No',
|
|
415
|
+
|
|
416
|
+
// onOk() {
|
|
417
|
+
// // YES → send form values + search condition
|
|
418
|
+
// const finalValues = {
|
|
419
|
+
// ...formValues,
|
|
420
|
+
// search_values: currentSearchValues,
|
|
421
|
+
// };
|
|
422
|
+
|
|
423
|
+
// runSubmit(finalValues);
|
|
424
|
+
// },
|
|
425
|
+
|
|
426
|
+
// onCancel() {
|
|
427
|
+
// // NO → reset form values
|
|
428
|
+
// const resetValues = {};
|
|
429
|
+
// Object.keys(formValues).forEach((key) => {
|
|
430
|
+
// resetValues[key] = null;
|
|
431
|
+
// });
|
|
432
|
+
|
|
433
|
+
// const finalValues = {
|
|
434
|
+
// ...resetValues,
|
|
435
|
+
// search_values: currentSearchValues,
|
|
436
|
+
// };
|
|
437
|
+
|
|
438
|
+
// runSubmit(finalValues);
|
|
439
|
+
// },
|
|
440
|
+
// });
|
|
441
|
+
// } else {
|
|
442
|
+
// // no form change
|
|
443
|
+
// const resetValues = {};
|
|
444
|
+
// Object.keys(formValues).forEach((key) => {
|
|
445
|
+
// resetValues[key] = null;
|
|
446
|
+
// });
|
|
447
|
+
|
|
448
|
+
// const finalValues = {
|
|
449
|
+
// ...resetValues,
|
|
450
|
+
// search_values: currentSearchValues,
|
|
451
|
+
// };
|
|
452
|
+
|
|
453
|
+
// runSubmit(finalValues);
|
|
454
|
+
// }
|
|
455
|
+
// };
|
|
427
456
|
|
|
428
457
|
const runSubmit = (finalValues) => {
|
|
429
458
|
const { pageSize } = pagination;
|
|
@@ -552,17 +581,12 @@ export default function ReportingDashboard({
|
|
|
552
581
|
|
|
553
582
|
// Call API
|
|
554
583
|
try {
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
// Pagination Handler
|
|
563
|
-
const handlePagination = async (newPagination) => {
|
|
564
|
-
try {
|
|
565
|
-
await fetchReportData(id, formContents, dbPtr, newPagination);
|
|
584
|
+
setPagination((prev) => ({
|
|
585
|
+
...prev,
|
|
586
|
+
current: paginationData.current,
|
|
587
|
+
pageSize: paginationData.pageSize,
|
|
588
|
+
}));
|
|
589
|
+
setReportRequestPayload(buildReportRequestPayload(values, paginationData));
|
|
566
590
|
} finally {
|
|
567
591
|
setLoading(false);
|
|
568
592
|
setCardLoading(false);
|
|
@@ -650,447 +674,39 @@ export default function ReportingDashboard({
|
|
|
650
674
|
) : null}
|
|
651
675
|
</div>
|
|
652
676
|
|
|
653
|
-
|
|
654
|
-
<GuestList
|
|
655
|
-
patients={patients}
|
|
677
|
+
<ReportingTable
|
|
656
678
|
columns={columns}
|
|
657
|
-
summaryColumns={summaryColumns}
|
|
658
679
|
isFixedIndex={isFixedIndex}
|
|
659
680
|
showScanner={showScanner}
|
|
681
|
+
reportId={reportId}
|
|
682
|
+
requestId={scriptId.current ? scriptId.current : id}
|
|
683
|
+
requestPayload={reportRequestPayload}
|
|
684
|
+
dbPtr={dbPtr}
|
|
660
685
|
barcodeFilterKey={barcodeFilterKey}
|
|
661
|
-
CustomComponents={
|
|
686
|
+
CustomComponents={CustomComponents}
|
|
662
687
|
refresh={refresh}
|
|
663
688
|
config={config}
|
|
689
|
+
|
|
664
690
|
loading={cardLoading}
|
|
665
691
|
pagination={pagination}
|
|
666
|
-
|
|
692
|
+
onPaginationChange={(nextPagination) => {
|
|
693
|
+
Location.search({
|
|
694
|
+
...Location.search(),
|
|
695
|
+
current: nextPagination.current,
|
|
696
|
+
pageSize: nextPagination.pageSize,
|
|
697
|
+
});
|
|
698
|
+
setPagination((prev) => ({
|
|
699
|
+
...prev,
|
|
700
|
+
...nextPagination,
|
|
701
|
+
}));
|
|
702
|
+
}}
|
|
667
703
|
attributes={attributes}
|
|
668
|
-
selectedSearchFields={selectedSearchFields}
|
|
669
|
-
handleRemoveSearchField={handleRemoveSearchField}
|
|
670
|
-
fetchReportData={(paginationUpdate) => fetchReportData(id, formContents, dbPtr, paginationUpdate || pagination)}
|
|
704
|
+
// selectedSearchFields={selectedSearchFields}
|
|
705
|
+
// handleRemoveSearchField={handleRemoveSearchField}
|
|
706
|
+
// fetchReportData={(paginationUpdate) => fetchReportData(id, formContents, dbPtr, paginationUpdate || pagination)}
|
|
671
707
|
/>
|
|
672
|
-
{/** GuestList component end*/}
|
|
673
708
|
</>
|
|
674
709
|
)}
|
|
675
710
|
</Card>
|
|
676
711
|
);
|
|
677
712
|
}
|
|
678
|
-
|
|
679
|
-
/**
|
|
680
|
-
*
|
|
681
|
-
* @param root0
|
|
682
|
-
* @param root0.patients
|
|
683
|
-
* @param root0.CustomComponents
|
|
684
|
-
* @param root0.summaryColumns
|
|
685
|
-
* @param root0.refresh
|
|
686
|
-
* @param root0.isFixedIndex
|
|
687
|
-
* @returns {*}
|
|
688
|
-
*/
|
|
689
|
-
//Renders a table displaying a list of patients with dynamic columns
|
|
690
|
-
function GuestList({
|
|
691
|
-
patients,
|
|
692
|
-
columns,
|
|
693
|
-
loading,
|
|
694
|
-
CustomComponents,
|
|
695
|
-
refresh,
|
|
696
|
-
isFixedIndex,
|
|
697
|
-
barcodeFilterKey,
|
|
698
|
-
showScanner,
|
|
699
|
-
config,
|
|
700
|
-
pagination,
|
|
701
|
-
handlePagination,
|
|
702
|
-
attributes,
|
|
703
|
-
selectedSearchFields,
|
|
704
|
-
handleRemoveSearchField,
|
|
705
|
-
fetchReportData,
|
|
706
|
-
}) {
|
|
707
|
-
/**
|
|
708
|
-
* @param {*} propValues
|
|
709
|
-
*/
|
|
710
|
-
const propValues = (attributes && JSON.parse(attributes)) || {};
|
|
711
|
-
|
|
712
|
-
const { buttonAttributes = [] } = propValues;
|
|
713
|
-
|
|
714
|
-
var [query, setQuery] = useState('');
|
|
715
|
-
|
|
716
|
-
const [exportData, setExportData] = useState({});
|
|
717
|
-
|
|
718
|
-
// const [data, setData] = useState([]);
|
|
719
|
-
|
|
720
|
-
//visibility of the QR scanner modal.
|
|
721
|
-
const [isScannerVisible, setScannerVisible] = useState(false);
|
|
722
|
-
|
|
723
|
-
// Stores the patients filtered specifically by QR scan match.
|
|
724
|
-
const [filteredPatients, setFilteredPatients] = useState([]); // Show all initially
|
|
725
|
-
|
|
726
|
-
// patient object to redirect to upon successful QR scan.
|
|
727
|
-
const [redirectPatient, setRedirectPatient] = useState(null);
|
|
728
|
-
|
|
729
|
-
const [visible, setVisible] = useState(false);
|
|
730
|
-
|
|
731
|
-
const [ActiveComponent, setActiveComponent] = useState(null);
|
|
732
|
-
|
|
733
|
-
let history = useHistory();
|
|
734
|
-
|
|
735
|
-
const { isMobile, dispatch } = useContext(GlobalContext);
|
|
736
|
-
const [single, setSingle] = useState({});
|
|
737
|
-
const otherDetails = config.other_details1 ? JSON.parse(config.other_details1) : {};
|
|
738
|
-
|
|
739
|
-
// const otherDetails = config.other_details1 ? JSON.parse(config.other_details1) : {};
|
|
740
|
-
// const [view, setView] = useState(isMobile ? true : false); //Need to check this condition
|
|
741
|
-
const cols = buildDisplayColumns({
|
|
742
|
-
columns,
|
|
743
|
-
patients,
|
|
744
|
-
isFixedIndex,
|
|
745
|
-
CustomComponents,
|
|
746
|
-
refresh,
|
|
747
|
-
otherDetails,
|
|
748
|
-
});
|
|
749
|
-
|
|
750
|
-
/**
|
|
751
|
-
*
|
|
752
|
-
* @param {*} result
|
|
753
|
-
*/
|
|
754
|
-
|
|
755
|
-
// function changeView(result) {
|
|
756
|
-
// setView(result);
|
|
757
|
-
// }
|
|
758
|
-
|
|
759
|
-
/**
|
|
760
|
-
*
|
|
761
|
-
* @param {*} event
|
|
762
|
-
*/
|
|
763
|
-
|
|
764
|
-
function onSearch(event) {
|
|
765
|
-
setQuery(event.target.value);
|
|
766
|
-
}
|
|
767
|
-
|
|
768
|
-
/**
|
|
769
|
-
*
|
|
770
|
-
*/
|
|
771
|
-
|
|
772
|
-
useEffect(() => {
|
|
773
|
-
//Cheaking if there is patient data exists
|
|
774
|
-
if (patients) {
|
|
775
|
-
// let data = patients?.map((entry) => {
|
|
776
|
-
// entry.rowIndex = entry.opb_id;
|
|
777
|
-
|
|
778
|
-
// entry.dispatch = dispatch;
|
|
779
|
-
|
|
780
|
-
// return entry;
|
|
781
|
-
// });
|
|
782
|
-
|
|
783
|
-
// setData(data);
|
|
784
|
-
|
|
785
|
-
// Define export data
|
|
786
|
-
// Sanitize cols for export to ensure titles are strings
|
|
787
|
-
const exportCols = cols.map((col) => {
|
|
788
|
-
if (col.title && typeof col.title === 'object' && col.title.props) {
|
|
789
|
-
return { ...col, title: col.title.props.title };
|
|
790
|
-
}
|
|
791
|
-
return col;
|
|
792
|
-
});
|
|
793
|
-
const summaryCols = columns.filter((col) => col.enable_summary);
|
|
794
|
-
let dataToExport = [...patients];
|
|
795
|
-
|
|
796
|
-
if (summaryCols.length > 0) {
|
|
797
|
-
// Build one synthetic row for CSV export that mirrors the table layout:
|
|
798
|
-
// numeric summary cells are populated from `calculateSummaryValues`, while
|
|
799
|
-
// non-summary columns stay blank unless a configured caption should be shown.
|
|
800
|
-
const summaryValues = calculateSummaryValues(summaryCols, patients);
|
|
801
|
-
const summaryRow = { isSummaryRow: true };
|
|
802
|
-
|
|
803
|
-
cols.forEach((col, index) => {
|
|
804
|
-
// Start each export column empty so the appended row keeps the same shape
|
|
805
|
-
// as the data rows and does not leak index/helper values into the export.
|
|
806
|
-
const colKey = col.field || col.key || col.dataIndex;
|
|
807
|
-
if (colKey && !summaryRow[colKey]) {
|
|
808
|
-
summaryRow[colKey] = '';
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
if (summaryValues[col.field] !== undefined) {
|
|
812
|
-
// Fill columns that have an aggregate configured (sum, count, avg, etc.).
|
|
813
|
-
summaryRow[col.field] = summaryValues[col.field];
|
|
814
|
-
} else {
|
|
815
|
-
// If this column is marked as the caption target for a summary column,
|
|
816
|
-
// place the configured label (for example "Total") into that cell.
|
|
817
|
-
const captionConfig = columns.find((c) => col.field && c.caption_field === col.field);
|
|
818
|
-
if (captionConfig) {
|
|
819
|
-
summaryRow[col.field] = captionConfig.summary_caption || '';
|
|
820
|
-
}
|
|
821
|
-
}
|
|
822
|
-
});
|
|
823
|
-
dataToExport.push(summaryRow);
|
|
824
|
-
}
|
|
825
|
-
let exportDatas = getExportData(dataToExport, exportCols);
|
|
826
|
-
|
|
827
|
-
if (exportDatas.exportDataColumns.length && exportDatas.exportDataHeaders.length) {
|
|
828
|
-
setExportData({ exportDatas });
|
|
829
|
-
}
|
|
830
|
-
}
|
|
831
|
-
}, [patients, columns]);
|
|
832
|
-
|
|
833
|
-
let filtered;
|
|
834
|
-
|
|
835
|
-
if (patients) {
|
|
836
|
-
filtered = patients.filter((record) => {
|
|
837
|
-
if (query) {
|
|
838
|
-
// Keys
|
|
839
|
-
let keys = Object.keys(record);
|
|
840
|
-
|
|
841
|
-
let flag = false;
|
|
842
|
-
|
|
843
|
-
keys.forEach((key) => {
|
|
844
|
-
let ele = record[key];
|
|
845
|
-
|
|
846
|
-
if (ele && typeof ele === 'string' && ele.toLowerCase().indexOf(query.toLowerCase()) !== -1) {
|
|
847
|
-
flag = true;
|
|
848
|
-
}
|
|
849
|
-
});
|
|
850
|
-
|
|
851
|
-
/**Will return flag */
|
|
852
|
-
return flag;
|
|
853
|
-
} else {
|
|
854
|
-
return true;
|
|
855
|
-
}
|
|
856
|
-
});
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
/**
|
|
860
|
-
* Checks for a match in the filtered patient list based on a scanned code,
|
|
861
|
-
* updates the relevant state if a match is found, and redirects to the
|
|
862
|
-
* patient's detail page. Displays a warning if no match is found.
|
|
863
|
-
*
|
|
864
|
-
* @param {string} code - The scanned code to match against a specific field of each patient.
|
|
865
|
-
*/
|
|
866
|
-
|
|
867
|
-
const handleScanSuccess = (code) => {
|
|
868
|
-
// Filters patients based on the scanned code and the selected barcode key(using attributes 'barcodeFilterKey')
|
|
869
|
-
const matched = filtered.filter((patient) => patient[barcodeFilterKey] === code);
|
|
870
|
-
|
|
871
|
-
if (matched.length) {
|
|
872
|
-
const patient = matched[0];
|
|
873
|
-
setFilteredPatients(matched);
|
|
874
|
-
setRedirectPatient(matched);
|
|
875
|
-
message.success(`Match found for ${code}, redirecting...`);
|
|
876
|
-
|
|
877
|
-
const actionColumn = columns.find((col) => col.field === 'action') || columns.find((col) => col.type === 'action');
|
|
878
|
-
if (actionColumn) {
|
|
879
|
-
const redirectLink = getRedirectLink(actionColumn, patient);
|
|
880
|
-
// history.push(redirectLink);
|
|
881
|
-
window.location.href = redirectLink;
|
|
882
|
-
}
|
|
883
|
-
} else {
|
|
884
|
-
Modal.warning({
|
|
885
|
-
title: 'No matching records.',
|
|
886
|
-
content: `No match for scanned code: ${code}`,
|
|
887
|
-
});
|
|
888
|
-
}
|
|
889
|
-
};
|
|
890
|
-
|
|
891
|
-
//open the edit modal
|
|
892
|
-
const handleOpenEdit = (button) => {
|
|
893
|
-
const componentName = button.component;
|
|
894
|
-
const ComponentToRender = ReportingDashboardComp[componentName];
|
|
895
|
-
|
|
896
|
-
if (!ComponentToRender) {
|
|
897
|
-
console.error(`Component ${componentName} not found!`);
|
|
898
|
-
return;
|
|
899
|
-
}
|
|
900
|
-
|
|
901
|
-
setSingle({});
|
|
902
|
-
setActiveComponent(() => ComponentToRender);
|
|
903
|
-
setVisible(true);
|
|
904
|
-
};
|
|
905
|
-
|
|
906
|
-
// close the edit modal
|
|
907
|
-
const handleCloseEdit = () => {
|
|
908
|
-
setShowEdit(false);
|
|
909
|
-
};
|
|
910
|
-
/**
|
|
911
|
-
* Calculates aggregate values for the configured summary columns.
|
|
912
|
-
*
|
|
913
|
-
* Each summary definition contributes one value keyed by its `field`. Missing
|
|
914
|
-
* row values are treated as `0` for numeric operations so the table summary and
|
|
915
|
-
* export summary row can be built from the same result object.
|
|
916
|
-
*
|
|
917
|
-
* Supported functions:
|
|
918
|
-
* `sum` - totals all numeric values in the field.
|
|
919
|
-
* `count` - returns the number of rows in the current dataset.
|
|
920
|
-
* `avg` - returns the arithmetic mean of the field values.
|
|
921
|
-
* `min` - returns the smallest numeric value in the field.
|
|
922
|
-
* `max` - returns the largest numeric value in the field.
|
|
923
|
-
*
|
|
924
|
-
* @param {Array<Object>} summaryCols - Column configs with `field` and `function`.
|
|
925
|
-
* @param {Array<Object>} pageData - Rows currently being summarized.
|
|
926
|
-
* @returns {Object} Aggregate values keyed by field name.
|
|
927
|
-
*/
|
|
928
|
-
function calculateSummaryValues(summaryCols, pageData) {
|
|
929
|
-
const summaryValues = {};
|
|
930
|
-
|
|
931
|
-
summaryCols.forEach((col) => {
|
|
932
|
-
const field = col.field;
|
|
933
|
-
|
|
934
|
-
if (col.function === 'sum') {
|
|
935
|
-
summaryValues[field] = pageData.reduce((total, row) => total + Number(row[field] || 0), 0);
|
|
936
|
-
}
|
|
937
|
-
|
|
938
|
-
if (col.function === 'count') {
|
|
939
|
-
summaryValues[field] = pageData.length;
|
|
940
|
-
}
|
|
941
|
-
|
|
942
|
-
if (col.function === 'avg') {
|
|
943
|
-
const total = pageData.reduce((sum, row) => sum + Number(row[field] || 0), 0);
|
|
944
|
-
summaryValues[field] = pageData.length ? total / pageData.length : 0;
|
|
945
|
-
}
|
|
946
|
-
if (col.function === 'min') {
|
|
947
|
-
const values = pageData.map((row) => Number(row[field] || 0));
|
|
948
|
-
summaryValues[field] = values.length ? Math.min(...values) : 0;
|
|
949
|
-
}
|
|
950
|
-
|
|
951
|
-
if (col.function === 'max') {
|
|
952
|
-
const values = pageData.map((row) => Number(row[field] || 0));
|
|
953
|
-
summaryValues[field] = values.length ? Math.max(...values) : 0;
|
|
954
|
-
}
|
|
955
|
-
});
|
|
956
|
-
|
|
957
|
-
return summaryValues;
|
|
958
|
-
}
|
|
959
|
-
return (
|
|
960
|
-
<>
|
|
961
|
-
<div className="table-header">
|
|
962
|
-
<div className="table-left">
|
|
963
|
-
{/* {selectedSearchFields?.length > 0 ? (
|
|
964
|
-
<div className="search-tags-container">
|
|
965
|
-
{selectedSearchFields.map((field) => (
|
|
966
|
-
<Tag key={field.field} closable color="blue" onClose={() => handleRemoveSearchField(field.field)}>
|
|
967
|
-
{field.caption}
|
|
968
|
-
</Tag>
|
|
969
|
-
))}
|
|
970
|
-
</div>
|
|
971
|
-
) : null} */}
|
|
972
|
-
</div>
|
|
973
|
-
|
|
974
|
-
<div className="table-right">
|
|
975
|
-
{/* shwoing caption is not correct so this commented */}
|
|
976
|
-
{/* <span className="menu-caption">{config.caption}</span> */}
|
|
977
|
-
<Search className="table-search-input" placeholder="Enter Search Value" allowClear onChange={onSearch} />
|
|
978
|
-
<div className="table-export-button">
|
|
979
|
-
{exportData.exportDatas && (
|
|
980
|
-
<ExportReactCSV
|
|
981
|
-
title={config.caption}
|
|
982
|
-
headers={exportData.exportDatas.exportDataHeaders}
|
|
983
|
-
csvData={exportData.exportDatas.exportDataColumns}
|
|
984
|
-
/>
|
|
985
|
-
)}
|
|
986
|
-
</div>
|
|
987
|
-
|
|
988
|
-
{/* QR Scan start */}
|
|
989
|
-
{showScanner ? (
|
|
990
|
-
<Button size="small" type="primary" icon={<QrcodeOutlined />} onClick={() => setScannerVisible(true)}>
|
|
991
|
-
Scan QR
|
|
992
|
-
</Button>
|
|
993
|
-
) : null}
|
|
994
|
-
{/** Add User button */}
|
|
995
|
-
{Array.isArray(buttonAttributes) &&
|
|
996
|
-
buttonAttributes.map((btn, index) => (
|
|
997
|
-
<Button key={index} size="small" type="primary" style={{ marginLeft: 8 }} onClick={() => handleOpenEdit(btn)}>
|
|
998
|
-
{btn.title}
|
|
999
|
-
</Button>
|
|
1000
|
-
))}
|
|
1001
|
-
|
|
1002
|
-
<Modal open={visible} onCancel={() => setVisible(false)} footer={null} destroyOnClose width={950} style={{ top: 10 }}>
|
|
1003
|
-
{ActiveComponent && (
|
|
1004
|
-
<ActiveComponent
|
|
1005
|
-
formContent={single}
|
|
1006
|
-
callback={() => {
|
|
1007
|
-
setVisible(false);
|
|
1008
|
-
refresh();
|
|
1009
|
-
setVisible(false);
|
|
1010
|
-
fetchReportData();
|
|
1011
|
-
}}
|
|
1012
|
-
// {...dynamicProps}
|
|
1013
|
-
/>
|
|
1014
|
-
)}
|
|
1015
|
-
</Modal>
|
|
1016
|
-
|
|
1017
|
-
<Modal open={isScannerVisible} title="Scan QR Code" footer={null} onCancel={() => setScannerVisible(false)} destroyOnClose>
|
|
1018
|
-
<QrScanner onScanSuccess={handleScanSuccess} onClose={() => setScannerVisible(false)} />
|
|
1019
|
-
</Modal>
|
|
1020
|
-
{/* QR Scan End */}
|
|
1021
|
-
</div>
|
|
1022
|
-
</div>
|
|
1023
|
-
|
|
1024
|
-
<div>
|
|
1025
|
-
<Card>
|
|
1026
|
-
{loading ? (
|
|
1027
|
-
<>
|
|
1028
|
-
<Skeleton active paragraph={{ rows: 6 }} />
|
|
1029
|
-
</>
|
|
1030
|
-
) : (
|
|
1031
|
-
<TableComponent
|
|
1032
|
-
size="small"
|
|
1033
|
-
// scroll={{ x: 'max-content' }}
|
|
1034
|
-
scroll={{ x: 'max-content', y: '60vh' }}
|
|
1035
|
-
rowKey={(record) => record.OpNo}
|
|
1036
|
-
dataSource={filtered ? filtered : patients} // In case if there is no filtered values we can use patient data
|
|
1037
|
-
columns={cols}
|
|
1038
|
-
sticky
|
|
1039
|
-
pagination={false}
|
|
1040
|
-
summary={(pageData) => {
|
|
1041
|
-
const summaryCols = columns.filter((col) => col.enable_summary);
|
|
1042
|
-
if (!summaryCols.length) return null;
|
|
1043
|
-
/** calculate summary*/
|
|
1044
|
-
|
|
1045
|
-
const summaryValues = calculateSummaryValues(summaryCols, pageData);
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
return (
|
|
1049
|
-
<Table.Summary.Row className="report-summary-row">
|
|
1050
|
-
{cols.map((col, index) => {
|
|
1051
|
-
if (summaryValues[col.field] !== undefined) {
|
|
1052
|
-
return (
|
|
1053
|
-
<Table.Summary.Cell key={index}>
|
|
1054
|
-
<strong style={{ fontWeight: 900 }}>{summaryValues[col.field]}</strong>
|
|
1055
|
-
</Table.Summary.Cell>
|
|
1056
|
-
);
|
|
1057
|
-
}
|
|
1058
|
-
|
|
1059
|
-
const captionConfig = columns.find((c) => col.field && c.caption_field === col.field);
|
|
1060
|
-
if (captionConfig) {
|
|
1061
|
-
return (
|
|
1062
|
-
<Table.Summary.Cell key={index}>
|
|
1063
|
-
<strong style={{ fontWeight: 900 }}>{captionConfig.summary_caption || ''}</strong>
|
|
1064
|
-
</Table.Summary.Cell>
|
|
1065
|
-
);
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
|
-
return <Table.Summary.Cell key={index} />;
|
|
1069
|
-
})}
|
|
1070
|
-
</Table.Summary.Row>
|
|
1071
|
-
);
|
|
1072
|
-
}}
|
|
1073
|
-
/>
|
|
1074
|
-
)}
|
|
1075
|
-
|
|
1076
|
-
{/* Pagination aligned to the right */}
|
|
1077
|
-
<div style={{ display: 'flex', justifyContent: 'flex-end', marginTop: 8 }}>
|
|
1078
|
-
<Pagination
|
|
1079
|
-
showSizeChanger
|
|
1080
|
-
current={pagination.current}
|
|
1081
|
-
pageSize={pagination.pageSize}
|
|
1082
|
-
total={pagination.total}
|
|
1083
|
-
pageSizeOptions={[20, 30, 50, 100]}
|
|
1084
|
-
onChange={(page, pageSize) => handlePagination({ current: page, pageSize })}
|
|
1085
|
-
/>
|
|
1086
|
-
</div>
|
|
1087
|
-
|
|
1088
|
-
{/*If patient data exists show the number else to 0 */}
|
|
1089
|
-
<p className="size-hint">{patients ? patients.length : 0} records. </p>
|
|
1090
|
-
</Card>
|
|
1091
|
-
{/* </> */}
|
|
1092
|
-
{/* )} */}
|
|
1093
|
-
</div>
|
|
1094
|
-
</>
|
|
1095
|
-
);
|
|
1096
|
-
}
|