ui-soxo-bootstrap-core 2.6.32-dev.5 → 2.6.33

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.
@@ -2,30 +2,35 @@ 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 { Location, FormCreator, GlobalContext, Card } from './../../../../lib/';
5
+ import { QrcodeOutlined } from '@ant-design/icons';
6
+
7
+ import { Location, FormCreator, GlobalContext, ExportReactCSV, getExportData, Card, TableComponent, QrScanner } from './../../../../lib/';
6
8
 
7
9
  import { CoreScripts } from './../../../../models/';
8
10
 
9
11
  import moment from 'moment-timezone';
10
12
 
13
+ import Button from '../../../../lib/elements/basic/button/button';
14
+
11
15
  import './reporting-dashboard.scss';
12
16
 
13
17
  // import MenuDashBoard from '../../../../pages/homepage-api/menu-dashboard';
14
18
  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';
15
24
 
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';
25
+ // import { isPdfFile } from 'pdfjs-dist';
22
26
 
23
- // var genericComponents = require('./../../../../lib');
27
+ var genericComponents = require('./../../../../lib');
24
28
 
25
29
  const { Search } = Input;
26
30
 
27
31
  /**
28
32
  * ReportingDashboard component renders the dashboard and handles patient details,
33
+ * configuration, and form layout for generating reports.
29
34
  *
30
35
  * @param {Object} props - The component's props.
31
36
  * @param {Object} props.match - The match object containing the URL parameters.
@@ -51,7 +56,7 @@ export default function ReportingDashboard({
51
56
  const [config, setConfig] = useState({});
52
57
 
53
58
  // State to manage the layout of the form
54
- const [formLayout] = useState('vertical');
59
+ const [formLayout, setFormLayout] = useState('vertical');
55
60
 
56
61
  const [loading, setLoading] = useState(true);
57
62
 
@@ -78,9 +83,9 @@ export default function ReportingDashboard({
78
83
 
79
84
  const [columns, setColumns] = useState([]); // To set columns
80
85
 
81
- const [, setSummaryColumns] = useState([]);
86
+ const [summaryColumns, setSummaryColumns] = useState([]);
82
87
 
83
- const [reportRequestPayload, setReportRequestPayload] = useState(null);
88
+ const [patients, setPatients] = useState([]); //Patients list array
84
89
 
85
90
  const urlParams = Location.search();
86
91
 
@@ -101,6 +106,7 @@ export default function ReportingDashboard({
101
106
  * @returns {Promise<void>} A promise that resolves when the patient details have been fetched and the state has been updated.
102
107
  */
103
108
  async function getPatientDetails(idOverride) {
109
+ setPatients([]);
104
110
  const fetchId = idOverride || id;
105
111
  await CoreScripts.getRecord({ id: fetchId, dbPtr }).then(async ({ result }) => {
106
112
  // Check if display columns are provided from backend
@@ -260,40 +266,85 @@ export default function ReportingDashboard({
260
266
  // Refresh patient details.
261
267
 
262
268
  function refresh() {
263
- getPatientDetails(scriptId.current || id);
269
+ getPatientDetails();
264
270
  }
265
271
 
266
- const buildReportRequestPayload = (values = {}, paginationOverride) => {
267
- const pager = paginationOverride || pagination;
268
- const formattedValues = {};
269
-
270
- Object.keys(values || {}).forEach((key) => {
271
- const val = values[key];
272
- formattedValues[key] = moment.isMoment(val) ? val.format('YYYY-MM-DD') : val;
273
- });
272
+ const fetchReportData = async (id, values, dbPtr, pagination, parsedColumns) => {
273
+ const { current, pageSize } = pagination || {};
274
+ // If card script id is exist load that id otherwise load id
275
+ const coreScriptId = scriptId.current ? scriptId.current : id;
276
+ const normalizedColumns = Array.isArray(parsedColumns) ? parsedColumns : Array.isArray(columns) ? columns : [];
274
277
 
275
- const paginationData = {
276
- page: pager.current || 1,
277
- limit: pager.pageSize || 10,
278
- };
279
-
280
- if (scope) {
281
- return {
278
+ setLoading(true);
279
+ try {
280
+ // Prepare payload for backend: format only here
281
+ const formattedValues = {};
282
+ Object.keys(values).forEach((key) => {
283
+ const val = values[key];
284
+ formattedValues[key] = moment.isMoment(val) ? val.format('YYYY-MM-DD') : val;
285
+ });
286
+ // Pagination Data
287
+ const paginationData = {
288
+ page: pagination.current || 1,
289
+ limit: pagination.pageSize || 10,
290
+ };
291
+ // Combine form data + pagination
292
+ let formBody = {
282
293
  body: {
283
- ...scope,
294
+ ...formattedValues,
284
295
  ...paginationData,
285
296
  },
286
297
  };
287
- }
298
+ // Optional override if `scope` exists
299
+ if (scope) {
300
+ formBody = { body: { ...scope, ...paginationData } };
301
+ }
288
302
 
289
- return {
290
- body: {
291
- ...formattedValues,
292
- ...paginationData,
293
- },
294
- };
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
+ }
295
345
  };
296
- const handleSubmit = (values) => {
346
+
347
+ const handleSubmit = (values) => {
297
348
  // Extract search fields from the form values
298
349
  const searchKeys = searchParameters.map((p) => p.field);
299
350
  const currentSearchValues = {};
@@ -373,86 +424,6 @@ export default function ReportingDashboard({
373
424
  runSubmit(finalValues);
374
425
  }
375
426
  };
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
- // };
456
427
 
457
428
  const runSubmit = (finalValues) => {
458
429
  const { pageSize } = pagination;
@@ -581,12 +552,17 @@ export default function ReportingDashboard({
581
552
 
582
553
  // Call API
583
554
  try {
584
- setPagination((prev) => ({
585
- ...prev,
586
- current: paginationData.current,
587
- pageSize: paginationData.pageSize,
588
- }));
589
- setReportRequestPayload(buildReportRequestPayload(values, paginationData));
555
+ await fetchReportData(id, values, dbPtr, paginationData, parsedColumns);
556
+ } finally {
557
+ setLoading(false);
558
+ setCardLoading(false);
559
+ }
560
+ };
561
+
562
+ // Pagination Handler
563
+ const handlePagination = async (newPagination) => {
564
+ try {
565
+ await fetchReportData(id, formContents, dbPtr, newPagination);
590
566
  } finally {
591
567
  setLoading(false);
592
568
  setCardLoading(false);
@@ -674,39 +650,447 @@ export default function ReportingDashboard({
674
650
  ) : null}
675
651
  </div>
676
652
 
677
- <ReportingTable
653
+ {/** GuestList component start*/}
654
+ <GuestList
655
+ patients={patients}
678
656
  columns={columns}
657
+ summaryColumns={summaryColumns}
679
658
  isFixedIndex={isFixedIndex}
680
659
  showScanner={showScanner}
681
- reportId={reportId}
682
- requestId={scriptId.current ? scriptId.current : id}
683
- requestPayload={reportRequestPayload}
684
- dbPtr={dbPtr}
685
660
  barcodeFilterKey={barcodeFilterKey}
686
- CustomComponents={CustomComponents}
661
+ CustomComponents={{ ...CustomComponents, ...genericComponents, ...ReportingDashboardComp }}
687
662
  refresh={refresh}
688
663
  config={config}
689
-
690
664
  loading={cardLoading}
691
665
  pagination={pagination}
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
- }}
666
+ handlePagination={handlePagination}
703
667
  attributes={attributes}
704
- // selectedSearchFields={selectedSearchFields}
705
- // handleRemoveSearchField={handleRemoveSearchField}
706
- // fetchReportData={(paginationUpdate) => fetchReportData(id, formContents, dbPtr, paginationUpdate || pagination)}
668
+ selectedSearchFields={selectedSearchFields}
669
+ handleRemoveSearchField={handleRemoveSearchField}
670
+ fetchReportData={(paginationUpdate) => fetchReportData(id, formContents, dbPtr, paginationUpdate || pagination)}
707
671
  />
672
+ {/** GuestList component end*/}
708
673
  </>
709
674
  )}
710
675
  </Card>
711
676
  );
712
677
  }
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
+ }