ui-soxo-bootstrap-core 2.6.1-dev.3 → 2.6.1-dev.31

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 (68) hide show
  1. package/core/components/extra-info/extra-info-details.js +2 -2
  2. package/core/components/index.js +2 -11
  3. package/core/components/landing-api/landing-api.js +216 -18
  4. package/core/components/landing-api/landing-api.scss +22 -0
  5. package/core/components/license-management/license-alert.js +97 -0
  6. package/core/lib/Store.js +8 -4
  7. package/core/lib/components/global-header/global-header.js +217 -242
  8. package/core/lib/components/index.js +2 -2
  9. package/core/lib/components/sidemenu/sidemenu.js +19 -13
  10. package/core/lib/components/sidemenu/sidemenu.scss +1 -1
  11. package/core/lib/elements/basic/country-phone-input/country-phone-input.js +14 -9
  12. package/core/lib/elements/basic/dragabble-wrapper/draggable-wrapper.js +1 -1
  13. package/core/lib/elements/basic/menu-tree/menu-tree.js +26 -13
  14. package/core/lib/models/forms/components/form-creator/form-creator.js +525 -468
  15. package/core/lib/models/forms/components/form-creator/form-creator.scss +30 -26
  16. package/core/lib/models/menus/components/menu-list/menu-list.js +424 -467
  17. package/core/lib/models/process/components/process-dashboard/process-dashboard.js +469 -3
  18. package/core/lib/models/process/components/process-dashboard/process-dashboard.scss +4 -0
  19. package/core/lib/modules/generic/generic-list/ExportReactCSV.js +28 -2
  20. package/core/lib/pages/change-password/change-password.js +17 -24
  21. package/core/lib/pages/change-password/change-password.scss +45 -48
  22. package/core/lib/pages/login/commnication-mode-selection.js +2 -2
  23. package/core/lib/pages/login/login.js +53 -64
  24. package/core/lib/pages/login/login.scss +9 -0
  25. package/core/lib/pages/login/reset-password.js +17 -17
  26. package/core/lib/pages/login/reset-password.scss +10 -1
  27. package/core/lib/pages/profile/themes.json +4 -4
  28. package/core/lib/utils/api/api.utils.js +53 -45
  29. package/core/lib/utils/common/common.utils.js +49 -35
  30. package/core/lib/utils/generic/generic.utils.js +2 -1
  31. package/core/lib/utils/http/http.utils.js +33 -4
  32. package/core/lib/utils/index.js +4 -1
  33. package/core/models/base/base.js +7 -3
  34. package/core/models/core-scripts/core-scripts.js +147 -126
  35. package/core/models/doctor/components/doctor-add/doctor-add.js +9 -4
  36. package/core/models/menus/components/menu-add/menu-add.js +1 -1
  37. package/core/models/menus/components/menu-lists/menu-lists.js +53 -54
  38. package/core/models/menus/menus.js +49 -2
  39. package/core/models/roles/components/role-add/role-add.js +92 -59
  40. package/core/models/roles/components/role-list/role-list.js +1 -1
  41. package/core/models/staff/components/staff-add/staff-add.js +20 -32
  42. package/core/models/users/components/assign-role/assign-role.js +145 -50
  43. package/core/models/users/components/assign-role/assign-role.scss +209 -45
  44. package/core/models/users/components/assign-role/avatar-props.js +45 -0
  45. package/core/models/users/components/user-add/user-add.js +46 -55
  46. package/core/models/users/components/user-add/user-edit.js +25 -4
  47. package/core/models/users/users.js +9 -1
  48. package/core/modules/dashboard/components/dashboard-card/menu-dashboard-card.js +1 -1
  49. package/core/modules/reporting/components/reporting-dashboard/README.md +316 -0
  50. package/core/modules/reporting/components/reporting-dashboard/adavance-search/advance-search.js +174 -0
  51. package/core/modules/reporting/components/reporting-dashboard/adavance-search/advance-search.scss +76 -0
  52. package/core/modules/reporting/components/reporting-dashboard/display-columns/build-display-columns.js +90 -0
  53. package/core/modules/reporting/components/reporting-dashboard/display-columns/build-display-columns.test.js +74 -0
  54. package/core/modules/reporting/components/reporting-dashboard/display-columns/display-cell-renderer.js +448 -0
  55. package/core/modules/reporting/components/reporting-dashboard/display-columns/display-cell-renderer.test.js +199 -0
  56. package/core/modules/reporting/components/reporting-dashboard/reporting-dashboard.js +195 -822
  57. package/core/modules/reporting/components/reporting-dashboard/reporting-dashboard.scss +43 -0
  58. package/core/modules/reporting/components/reporting-dashboard/reporting-table.js +517 -0
  59. package/core/modules/steps/action-buttons.js +30 -16
  60. package/core/modules/steps/action-buttons.scss +55 -9
  61. package/core/modules/steps/chat-assistant.js +141 -0
  62. package/core/modules/steps/openai-realtime.js +275 -0
  63. package/core/modules/steps/readme.md +167 -0
  64. package/core/modules/steps/steps.js +1286 -60
  65. package/core/modules/steps/steps.scss +703 -86
  66. package/core/modules/steps/timeline.js +21 -19
  67. package/core/modules/steps/voice-navigation.js +709 -0
  68. package/package.json +2 -1
@@ -1,34 +1,19 @@
1
1
  import React, { useState, useEffect, useContext, useRef } from 'react';
2
2
 
3
- import { Table, Skeleton, Input, Tag, Modal, message, Pagination, Tooltip } from 'antd';
3
+ import { Table, Skeleton, Input, Modal, message, Pagination, Tag } from 'antd';
4
4
 
5
- import { QrcodeOutlined } from '@ant-design/icons';
6
-
7
- import * as Icons from '@ant-design/icons';
8
-
9
- import { Location, FormCreator, GlobalContext, ExportReactCSV, getExportData, Card, TableComponent, QrScanner } from './../../../../lib/';
5
+ import { Location, FormCreator, GlobalContext, Card } from './../../../../lib/';
10
6
 
11
7
  import { CoreScripts } from './../../../../models/';
12
8
 
13
9
  import moment from 'moment-timezone';
14
10
 
15
- import Button from '../../../../lib/elements/basic/button/button';
16
-
17
- import { Link } from 'react-router-dom';
18
-
19
11
  import './reporting-dashboard.scss';
20
12
 
21
13
  // import MenuDashBoard from '../../../../pages/homepage-api/menu-dashboard';
22
14
  import MenuDashBoardComponent from '../../../../lib/elements/basic/menu-dashboard/menu-dashboard';
23
- import { useHistory } from 'react-router-dom';
24
-
25
- import * as ReportingDashboardComp from '../index';
26
-
27
- // import { isPdfFile } from 'pdfjs-dist';
28
-
29
- var genericComponents = require('./../../../../lib');
30
15
 
31
- const { Search } = Input;
16
+ import ReportingTable from './reporting-table';
32
17
 
33
18
  /**
34
19
  * ReportingDashboard component renders the dashboard and handles patient details,
@@ -58,15 +43,20 @@ export default function ReportingDashboard({
58
43
  const [config, setConfig] = useState({});
59
44
 
60
45
  // State to manage the layout of the form
61
- const [formLayout, setFormLayout] = useState('inline');
46
+ const [formLayout] = useState('inline');
62
47
 
63
48
  const [loading, setLoading] = useState(true);
64
49
 
65
50
  const [cardLoading, setCardLoading] = useState(true);
66
51
 
52
+ const [searchParameters, setSearchParameters] = useState([]);
53
+
54
+ const [searchValues, setSearchValues] = useState({});
55
+
67
56
  const [dashboardVisible, setDashBoardVisible] = useState(false);
68
57
 
69
58
  const [formContents, setformContents] = useState({});
59
+ const [liveFormContents, setLiveFormContents] = useState({});
70
60
 
71
61
  const scriptId = useRef(null);
72
62
 
@@ -80,9 +70,9 @@ export default function ReportingDashboard({
80
70
 
81
71
  const [columns, setColumns] = useState([]); // To set columns
82
72
 
83
- const [summaryColumns, setSummaryColumns] = useState([]);
73
+ const [, setSummaryColumns] = useState([]);
84
74
 
85
- const [patients, setPatients] = useState([]); //Patients list array
75
+ const [reportRequestPayload, setReportRequestPayload] = useState(null);
86
76
 
87
77
  const urlParams = Location.search();
88
78
 
@@ -103,20 +93,9 @@ export default function ReportingDashboard({
103
93
  * @returns {Promise<void>} A promise that resolves when the patient details have been fetched and the state has been updated.
104
94
  */
105
95
  async function getPatientDetails(idOverride) {
106
- setPatients([]);
107
96
  const fetchId = idOverride || id;
108
97
  await CoreScripts.getRecord({ id: fetchId, dbPtr }).then(async ({ result }) => {
109
98
  // Check if display columns are provided from backend
110
- // if (result.display_columns) {
111
- // // Parse and set columns from stored JSON
112
-
113
- // setColumns(JSON.parse(result.display_columns));
114
- // } else {
115
- // console.log("ssssssssssssssssssss")
116
- // // Reset columns if no display columns exist
117
-
118
- // setColumns([]);
119
- // }
120
99
  let parsedColumns = [];
121
100
 
122
101
  if (result.display_columns) {
@@ -124,7 +103,7 @@ export default function ReportingDashboard({
124
103
  }
125
104
  setColumns(parsedColumns);
126
105
 
127
- await prepareInputParameters(result, parsedColumns);
106
+ await prepareInputParameters(result, parsedColumns, fetchId);
128
107
 
129
108
  if (result.summary_columns) {
130
109
  setSummaryColumns(JSON.parse(result.summary_columns));
@@ -169,7 +148,7 @@ export default function ReportingDashboard({
169
148
  */
170
149
 
171
150
  //Prepare input parameters by mapping default values and binding models if needed
172
- async function prepareInputParameters(record, parsedColumns) {
151
+ async function prepareInputParameters(record, parsedColumns, fetchId) {
173
152
  setLoading(true);
174
153
  let urlParams = Location.search();
175
154
 
@@ -178,9 +157,11 @@ export default function ReportingDashboard({
178
157
 
179
158
  let otherDetails = record.other_details1 ? JSON.parse(record.other_details1) : null;
180
159
 
181
- parameters = record.input_parameters ? JSON.parse(record.input_parameters) : null;
160
+ parameters = record.input_parameters ? JSON.parse(record.input_parameters) : [];
182
161
 
183
162
  let formContent = {};
163
+ const searchFields = (parameters || []).filter((p) => p.type === 'search' && p.search_enabled === 'yes');
164
+ setSearchParameters([...searchFields]);
184
165
 
185
166
  parameters = await parameters?.map((record) => {
186
167
  // Only if the url params does have a matching value ,
@@ -224,11 +205,19 @@ export default function ReportingDashboard({
224
205
  if (record.type === 'date' && !formContent[record.field]) {
225
206
  formContent[record.field] = moment().tz(process.env.REACT_APP_TIMEZONE);
226
207
  }
227
-
208
+ if (record.type === 'search') {
209
+ if (!formContent[record.field]) formContent[record.field] = [];
210
+ return {
211
+ ...record,
212
+ reportId: fetchId,
213
+ onReset: () => getPatientDetails(fetchId),
214
+ required: record.required,
215
+ };
216
+ }
228
217
  if (['reference-select', 'reference-search', 'select'].indexOf(record.type) !== -1) {
229
218
  // let model = "";
230
219
  let model = CustomModels[record.modelName];
231
- formContent[record.modelName] = model;
220
+
232
221
  return {
233
222
  ...record,
234
223
  model,
@@ -243,6 +232,7 @@ export default function ReportingDashboard({
243
232
  });
244
233
  // Update form content state
245
234
  setformContents(formContent);
235
+ setLiveFormContents(formContent);
246
236
 
247
237
  // Trigger form submission
248
238
  onFinish(formContent, null, record.input_parameters, parsedColumns);
@@ -254,130 +244,169 @@ export default function ReportingDashboard({
254
244
  // If enabled, clear the details array
255
245
  setDetails([]);
256
246
  } else {
257
- //// Filter the array "parameters" and keep only elements where "type" is present (truthy)
258
- //Keep only parameters that have a "type" → used for UI display
259
- let filter = parameters.filter((ele) => ele.type);
260
- // Update the "details" state with the filtered results
261
-
262
- setDetails([...filter]);
247
+ // Keep all parameters with a type (including search) to render in FormCreator
248
+ setDetails([...parameters.filter((ele) => ele.type)]);
263
249
  }
264
250
  }
265
251
 
266
252
  // Refresh patient details.
267
253
 
268
254
  function refresh() {
269
- getPatientDetails();
255
+ getPatientDetails(scriptId.current || id);
270
256
  }
271
257
 
272
- const fetchReportData = async (id, values, dbPtr, pagination, parsedColumns,paramsString) => {
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;
258
+ const buildReportRequestPayload = (values = {}, paginationOverride) => {
259
+ const pager = paginationOverride || pagination;
260
+ const formattedValues = {};
276
261
 
277
- setLoading(true);
278
- try {
279
- // Prepare payload for backend: format only here
280
- const formattedValues = {};
281
- let inputParams = [];
282
- inputParams = JSON.parse(paramsString);
283
- // Iterate through all keys in the values object
284
-
285
- inputParams.forEach(({ field }) => {
286
- const val = values[field];
287
-
288
- // field exists in values → format & assign
289
- if (val !== undefined) {
290
- formattedValues[field] = moment.isMoment(val) ? val.format('YYYY-MM-DD') : val;
291
- }
292
- // field missing in values → set null
293
- else {
294
- formattedValues[field] = null;
295
- }
296
- });
297
- // });
298
- // Pagination Data
299
- const paginationData = {
300
- page: pagination.current || 1,
301
- limit: pagination.pageSize || 10,
302
- };
303
- // Combine form data + pagination
304
- let formBody = {
262
+ Object.keys(values || {}).forEach((key) => {
263
+ const val = values[key];
264
+ formattedValues[key] = moment.isMoment(val) ? val.format('YYYY-MM-DD') : val;
265
+ });
266
+
267
+ const paginationData = {
268
+ page: pager.current || 1,
269
+ limit: pager.pageSize || 10,
270
+ };
271
+
272
+ if (scope) {
273
+ return {
305
274
  body: {
306
- ...formattedValues,
275
+ ...scope,
307
276
  ...paginationData,
308
277
  },
309
278
  };
310
- // Optional override if `scope` exists
311
- if (scope) {
312
- formBody = { body: { ...scope, ...paginationData } };
313
- }
279
+ }
314
280
 
315
- // Fetch result
316
- const result = await CoreScripts.getReportingLisitng(coreScriptId, formBody, dbPtr);
317
- // Handle both result formats
318
- let resultDetails = result[0];
319
- if (result?.result && result?.result[0]) {
320
- resultDetails = result.result[0];
321
- }
322
- // Update patients
323
- setPatients(resultDetails || []);
324
- // Check if columns are not yet defined
325
- if (parsedColumns.length === 0 && resultDetails.length > 0) {
326
- // Create columns dynamically from resultDetails keys
327
- setColumns((prev) => {
328
- if (prev.length > 0) return prev;
329
- return Object.keys(resultDetails[0]).map((key) => ({
330
- title: key,
331
- field: key,
332
- }));
333
- });
281
+ return {
282
+ body: {
283
+ ...formattedValues,
284
+ ...paginationData,
285
+ },
286
+ };
287
+ };
288
+
289
+ const handleSubmit = (values) => {
290
+ // Extract search fields from the form values
291
+ const searchKeys = searchParameters.map((p) => p.field);
292
+ const currentSearchValues = {};
293
+ const formValues = {};
294
+
295
+ Object.keys(values).forEach((key) => {
296
+ if (searchKeys.includes(key)) {
297
+ currentSearchValues[key] = values[key];
298
+ } else {
299
+ formValues[key] = values[key];
334
300
  }
301
+ });
302
+
303
+ const hasSearchValues = Object.values(currentSearchValues).some((v) => Array.isArray(v) && v.length > 0);
335
304
 
336
- if (result.length) {
337
- // Set Pgination data into URL
338
- Location.search({ ...Location.search(), current, pageSize });
305
+ if (!hasSearchValues) {
306
+ runSubmit(formValues);
307
+ return;
308
+ }
339
309
 
340
- setPagination((prev) => ({
341
- ...prev,
342
- current: pagination.current,
343
- pageSize: pagination.pageSize,
344
- total: resultDetails?.[0]?.TotalCount ?? pagination?.total,
345
- }));
310
+ // Check if main form values (non-search) changed
311
+ const formChanged = Object.keys(formValues).some((key) => {
312
+ const newVal = formValues[key];
313
+ const oldVal = formContents[key];
314
+
315
+ if (moment.isMoment(newVal) && moment.isMoment(oldVal)) {
316
+ return !newVal.isSame(oldVal, 'day');
346
317
  }
347
- } catch (error) {
348
- console.error('Error fetching report data:', error);
349
- message.warn('Please try again');
350
- } finally {
351
- // Always runs, success or error
352
- setLoading(false);
318
+
319
+ return newVal !== oldVal;
320
+ });
321
+
322
+ if (formChanged) {
323
+ Modal.confirm({
324
+ title: 'Filters changed',
325
+ content: 'You changed some filters. Do you want to search using these filters also?',
326
+ okText: 'Yes',
327
+ cancelText: 'No',
328
+
329
+ onOk() {
330
+ // YES → send form values + search condition
331
+ const finalValues = {
332
+ ...formValues,
333
+ search_values: currentSearchValues,
334
+ };
335
+
336
+ runSubmit(finalValues);
337
+ },
338
+
339
+ onCancel() {
340
+ // NO → reset form values
341
+ const resetValues = {};
342
+ Object.keys(formValues).forEach((key) => {
343
+ resetValues[key] = null;
344
+ });
345
+
346
+ const finalValues = {
347
+ ...resetValues,
348
+ search_values: currentSearchValues,
349
+ };
350
+
351
+ runSubmit(finalValues);
352
+ },
353
+ });
354
+ } else {
355
+ // no form change
356
+ const resetValues = {};
357
+ Object.keys(formValues).forEach((key) => {
358
+ resetValues[key] = null;
359
+ });
360
+
361
+ const finalValues = {
362
+ ...resetValues,
363
+ search_values: currentSearchValues,
364
+ };
365
+
366
+ runSubmit(finalValues);
353
367
  }
354
368
  };
355
369
 
356
- // Handle Submit
357
- const handleSubmit = (values) => {
370
+ const runSubmit = (finalValues) => {
358
371
  const { pageSize } = pagination;
359
372
  const resetPage = 1;
360
373
 
361
- // Reset script id on Submit
362
374
  scriptId.current = null;
363
375
 
364
- // Get current query params
365
- const currentUrlParams = Location.search();
366
- const { script_id, selected_card, ...cleanParams } = currentUrlParams;
376
+ const hasSearchValues = finalValues.search_values && Object.values(finalValues.search_values).some((v) => Array.isArray(v) && v.length > 0);
367
377
 
368
- // Construct new query string
369
- const newParams = new URLSearchParams({
370
- ...cleanParams,
371
- current: resetPage,
372
- pageSize,
373
- });
378
+ if (!hasSearchValues) {
379
+ const currentUrlParams = Location.search();
380
+ const { script_id, selected_card, ...cleanParams } = currentUrlParams;
374
381
 
375
- // Replace URL
376
- const newUrl = `${window.location.pathname}?${newParams.toString()}`;
377
- window.history.replaceState({}, '', newUrl);
382
+ const newParams = new URLSearchParams({
383
+ ...cleanParams,
384
+ current: resetPage,
385
+ pageSize,
386
+ });
378
387
 
379
- // Trigger submit handler
380
- onFinish(values, resetPage);
388
+ const newUrl = `${window.location.pathname}?${newParams.toString()}`;
389
+ window.history.replaceState({}, '', newUrl);
390
+ }
391
+
392
+ onFinish(finalValues, resetPage);
393
+ };
394
+
395
+ const selectedSearchFields = details.filter((field) => {
396
+ if (field.type !== 'search') return false;
397
+
398
+ const value = liveFormContents[field.field];
399
+ return Array.isArray(value) ? value.length > 0 : !!value;
400
+ });
401
+
402
+ const handleRemoveSearchField = (fieldName) => {
403
+ const updatedValues = {
404
+ ...liveFormContents,
405
+ [fieldName]: [],
406
+ };
407
+
408
+ setLiveFormContents(updatedValues);
409
+ handleSubmit(updatedValues);
381
410
  };
382
411
 
383
412
  /**
@@ -412,6 +441,12 @@ export default function ReportingDashboard({
412
441
  //Getting url friendly value by matching the url values and input_parameter values
413
442
  let value = values[parameter.field];
414
443
 
444
+ // If the value is missing at the root level (which happens when search fields are moved
445
+ // into search_values during submission), try to retrieve it from the nested object.
446
+ if (value === undefined && values.search_values && values.search_values[parameter.field] !== undefined) {
447
+ value = values.search_values[parameter.field];
448
+ }
449
+
415
450
  // Keep Moment object in state for picker
416
451
  if (parameter.type === 'date' && value) {
417
452
  formContent[parameter.field] = value.isValid ? value : moment(value); // ensure Moment
@@ -432,7 +467,8 @@ export default function ReportingDashboard({
432
467
  Object.entries(urlsToUpdate).filter(([_, value]) => value !== undefined && value !== null && value !== '')
433
468
  );
434
469
 
435
- setformContents((prev) => ({ ...prev, ...formContent }));
470
+ setformContents(formContent);
471
+ setLiveFormContents(formContent);
436
472
  Location.search({ ...Location.search(), ...filteredParams });
437
473
  }
438
474
 
@@ -445,17 +481,12 @@ export default function ReportingDashboard({
445
481
 
446
482
  // Call API
447
483
  try {
448
- await fetchReportData(id, values, dbPtr, paginationData, parsedColumns,paramsString);
449
- } finally {
450
- setLoading(false);
451
- setCardLoading(false);
452
- }
453
- };
454
-
455
- // Pagination Handler
456
- const handlePagination = async (newPagination) => {
457
- try {
458
- await fetchReportData(id, formContents, dbPtr, newPagination);
484
+ setPagination((prev) => ({
485
+ ...prev,
486
+ current: paginationData.current,
487
+ pageSize: paginationData.pageSize,
488
+ }));
489
+ setReportRequestPayload(buildReportRequestPayload(values, paginationData));
459
490
  } finally {
460
491
  setLoading(false);
461
492
  setCardLoading(false);
@@ -470,23 +501,6 @@ export default function ReportingDashboard({
470
501
  <Card className="reporting-dashboard card card-shadow">
471
502
  {/** If dashBoardIds exist and contain elements, render MenuDashBoard*/}
472
503
 
473
- {/* Page Header */}
474
- {/* <div className="page-header">
475
- <div>
476
- <Title style={{ marginBottom: '0px' }} level={4}>
477
- {config.caption || 'Report'}
478
- </Title>
479
- </div>
480
-
481
- <div className="right">
482
- <div className="date-and-fltr">
483
- <Button onClick={refresh} type="secondary" size={'small'}>
484
- <ReloadOutlined />
485
- </Button>
486
- </div>
487
- </div>
488
- </div> */}
489
- {/* Page Header Ends */}
490
504
  {dashBoardIds?.length > 0 ? (
491
505
  <MenuDashBoardComponent
492
506
  dashBoardIds={dashBoardIds} //Pass the available dashboard IDs to the componen
@@ -545,691 +559,50 @@ export default function ReportingDashboard({
545
559
  }}
546
560
  styles={{ paddingRight: '15px', alignItems: 'center' }}
547
561
  fields={details}
562
+ reportId={id}
548
563
  formContent={formContents}
549
564
  // formContent={{ [model]: {} }}
550
565
  modelIndex="requestId"
551
566
  model={model}
552
567
  onSubmit={handleSubmit}
568
+ onFormValuesChange={setLiveFormContents}
553
569
  callback={() => {
554
570
  // history.goBack();
555
571
  }}
556
572
  />
557
573
  ) : null}
558
- {/* </Card> */}
559
574
  </div>
560
575
 
561
- {/** GuestList component start*/}
562
- <GuestList
563
- patients={patients}
576
+ <ReportingTable
564
577
  columns={columns}
565
- summaryColumns={summaryColumns}
566
578
  isFixedIndex={isFixedIndex}
567
579
  showScanner={showScanner}
580
+ reportId={reportId}
581
+ requestId={scriptId.current ? scriptId.current : id}
582
+ requestPayload={reportRequestPayload}
583
+ dbPtr={dbPtr}
568
584
  barcodeFilterKey={barcodeFilterKey}
569
- CustomComponents={{ ...CustomComponents, ...genericComponents, ...ReportingDashboardComp }}
585
+ CustomComponents={CustomComponents}
570
586
  refresh={refresh}
571
587
  config={config}
588
+
572
589
  loading={cardLoading}
573
590
  pagination={pagination}
574
- handlePagination={handlePagination}
591
+ onPaginationChange={(nextPagination) => {
592
+ Location.search({
593
+ ...Location.search(),
594
+ current: nextPagination.current,
595
+ pageSize: nextPagination.pageSize,
596
+ });
597
+ setPagination((prev) => ({
598
+ ...prev,
599
+ ...nextPagination,
600
+ }));
601
+ }}
575
602
  attributes={attributes}
576
- fetchReportData={(paginationUpdate) => fetchReportData(id, formContents, dbPtr, paginationUpdate || pagination)}
577
603
  />
578
- {/** GuestList component end*/}
579
604
  </>
580
605
  )}
581
606
  </Card>
582
607
  );
583
608
  }
584
-
585
- /**
586
- *
587
- * @param root0
588
- * @param root0.patients
589
- * @param root0.CustomComponents
590
- * @param root0.summaryColumns
591
- * @param root0.refresh
592
- * @param root0.isFixedIndex
593
- * @returns {*}
594
- */
595
- //Renders a table displaying a list of patients with dynamic columns
596
- function GuestList({
597
- patients,
598
- columns,
599
- loading,
600
- CustomComponents,
601
- refresh,
602
- isFixedIndex,
603
- barcodeFilterKey,
604
- showScanner,
605
- config,
606
- pagination,
607
- handlePagination,
608
- attributes,
609
- fetchReportData,
610
- }) {
611
-
612
- /**
613
- * @param {*} propValues
614
- */
615
- const propValues = (attributes && JSON.parse(attributes)) || {};
616
-
617
- const { buttonAttributes = [] } = propValues;
618
-
619
- var [query, setQuery] = useState('');
620
-
621
- const [exportData, setExportData] = useState({});
622
-
623
- // const [data, setData] = useState([]);
624
-
625
- //visibility of the QR scanner modal.
626
- const [isScannerVisible, setScannerVisible] = useState(false);
627
-
628
- // Stores the patients filtered specifically by QR scan match.
629
- const [filteredPatients, setFilteredPatients] = useState([]); // Show all initially
630
-
631
- // patient object to redirect to upon successful QR scan.
632
- const [redirectPatient, setRedirectPatient] = useState(null);
633
-
634
- const [visible, setVisible] = useState(false);
635
-
636
- const [ActiveComponent, setActiveComponent] = useState(null);
637
-
638
- let history = useHistory();
639
-
640
- const { isMobile, dispatch } = useContext(GlobalContext);
641
- const [single, setSingle] = useState({});
642
-
643
- const getRedirectLink = (entry, record) => {
644
- let redirectLink = entry.redirect_link;
645
- if (entry.replace_variables) {
646
- entry.replace_variables.forEach((replacement) => {
647
- const value = record[replacement.field] || '';
648
- redirectLink = redirectLink.replace(new RegExp(`@${replacement.field};`, 'g'), value);
649
- });
650
- }
651
- return redirectLink;
652
- };
653
-
654
- // const [view, setView] = useState(isMobile ? true : false); //Need to check this condition
655
- const cols = [
656
- ...[
657
- {
658
- title: '#',
659
- dataIndex: 'index',
660
- key: 'ColumnIndex',
661
- width: 60,
662
- render: (value, item, index) => index + 1,
663
- key: 'ColumnIndex',
664
- fixed: isFixedIndex ? 'left' : null,
665
- },
666
- ],
667
- ...columns.map((entry) => {
668
- // if (entry.sort) {
669
- // return {
670
- // render: (record) => {
671
-
672
- // if (entry.render) {
673
-
674
- // return entry.render(record);
675
- // } else {
676
- // return record[entry.dataIndex]
677
- // }
678
- // },
679
- // title: entry.title,
680
- // key: entry.field,
681
- // sorter: (a, b) => entry.sort(a, b),
682
- // sortDirections: ['ascend', 'descend', 'ascend'],
683
- // };
684
- // } else {
685
- return {
686
- render: (record) => {
687
- let textColor = 'inherit';
688
-
689
- if (entry.color) {
690
- textColor = entry.color;
691
- }
692
- /** We can have x types of components that is to be rendered here */
693
-
694
- /**1. Column Data */
695
-
696
- /**2. Action */
697
-
698
- /**3. Custom Component - In future . */
699
-
700
- if (entry.render) {
701
- return entry.render(record);
702
-
703
- // The type of component can be differentiated by type/ we can also reuse
704
- // any field present in the columns to avoid any additional field
705
- } else if (entry.type === 'link') {
706
- //Cheacking type of action to be done ie,If it contains a type then it will match with the record
707
- // for example initally we are implementing it for a type link
708
- // ie, we will return a link in query with the field as link and type link in display_columns
709
- //So here we match the field returned by query with the type in display_columns
710
-
711
- if (record[entry.field]) {
712
- return (
713
- <a href={record[entry.field]} target="_blank" rel="noopener noreferrer">
714
- View
715
- </a>
716
- );
717
- }
718
- } else if (entry.field === 'action') {
719
- let redirectLink = entry.redirect_link;
720
-
721
- // The variables to be replaced can be maintained
722
- // as a configuration in the entry or the column configuration
723
-
724
- // We iterate through all the variables that are present in the configuration
725
- // and replace the link to generate the final link
726
-
727
- entry.replace_variables.forEach((replacement) => {
728
- redirectLink = redirectLink.replace(new RegExp('@' + replacement.field + ';', 'g'), record[replacement.field]);
729
- });
730
-
731
- return <Link to={`${redirectLink}`}>{entry.display_name_link ? entry.display_name_link : 'View'}</Link>;
732
- } else if (entry.field === 'custom') {
733
- // Make all the components in modules available for use in custom column of core script
734
- // var genericComponents = require('./../../../../../../../nura-api-new/nura-desk/src/modules');
735
- var genericComponents = CustomComponents;
736
-
737
- // Arrive the component name
738
- let componentName = entry.component;
739
-
740
- let LoadedComponent = null;
741
- // If there is custom components mensioned in the display_columns,
742
- // then matching the custom components with the generic components
743
- if (componentName) {
744
- if (componentName && genericComponents[componentName]) {
745
- LoadedComponent = genericComponents[componentName];
746
- }
747
- }
748
-
749
- let propValue = {};
750
- // If there is props value
751
- if (entry.props) {
752
- // Looping the props from the props mensioned in display_columns,
753
- // Matching the field of record props field with the and return the
754
- entry.props.forEach((values) => {
755
- // Matching the fields of record with the props field
756
- let valueCreation = record[values.field];
757
- // Returning the values of field matched by fields
758
- propValue[values.value] = valueCreation;
759
- });
760
- }
761
-
762
- return (
763
- <LoadedComponent
764
- // Configuration will define
765
- {...entry.config}
766
- callback={() => {
767
- refresh();
768
- }}
769
- // record={record}
770
-
771
- {...record}
772
- // assigning the props
773
- {...propValue}
774
- />
775
- );
776
- } else {
777
- // Check if both `color_code` exists in the record and `enableColor` is true
778
- if (record.color_code && entry.enableColor) {
779
- // If the column type is 'tag', render the field inside an Ant Design <Tag> with color
780
- if (entry.columnType === 'tag') {
781
- return <Tag color={record.color_code}>{record[entry.field]}</Tag>;
782
-
783
- // If the column type is 'span', render the field inside a <span> with inline color style
784
- } else if (entry.columnType === 'span') {
785
- return <span style={{ color: record.color_code, overflowWrap: 'break-word', WebkitLineClamp: 3 }}>{record[entry.field]}</span>;
786
- }
787
- } else {
788
- /**
789
- * This code dynamically displays icons based on data and a configuration
790
- * When a column's configuration includes a displayIcons JSON, the code will check the corresponding data field.
791
- * If the field's value (e.g., "Pending") matches a key in that JSON,
792
- * the system will display the specified icon with its defined color and size.
793
- */
794
- if (entry.displayIcons) {
795
- // Get the field value
796
- const fieldValue = record[entry.field]?.toString();
797
-
798
- // Get the configuration for the icon from the JSON.
799
- const displayConfig = entry.displayIcons[fieldValue];
800
- if (displayConfig) {
801
- // Look up the actual component from our iconMap using the name from the JSON.
802
- const DynamicIcon = Icons[displayConfig.icon];
803
- // If a component is found, render it with the specified color and size.
804
- if (DynamicIcon) {
805
- return (
806
- <DynamicIcon
807
- style={{
808
- color: displayConfig.color,
809
- fontSize: displayConfig.size,
810
- }}
811
- />
812
- );
813
- }
814
- }
815
- } else {
816
- //If the value is neither 'Y' nor 'N', return the actual field value
817
- return <span style={{ color: textColor, whiteSpace: 'pre-wrap', overflowWrap: 'break-word' }}>{record[entry.field]}</span>;
818
- }
819
- }
820
- }
821
- },
822
- field: entry.field,
823
- // title: entry.title,
824
- // title: (
825
- // <Tooltip title={entry.title}>
826
- // {entry.title}
827
- // </Tooltip>
828
- // ),
829
- title: (
830
- <Tooltip title={entry.tooltip || entry.title}>
831
- <span>{entry.title}</span>
832
- </Tooltip>
833
- ),
834
- key: entry.field,
835
- width: entry.width ? parseInt(entry.width) : 160,
836
- fixed: entry.isFixedColumn ? entry.isFixedColumn : null, // Conditionally setting the 'fixed' key to 'left' if 'isColumnStatic' is true; otherwise, setting it to null.
837
- // Check if filtering is enabled and patients is an array
838
- filters:
839
- entry.isFilterEnabled && Array.isArray(patients)
840
- ? [...new Set(patients.map((item) => item[entry.field]).filter(Boolean))].map((value) => ({ text: value, value }))
841
- : null,
842
- // Apply the filter only if it's enabled for the column
843
- onFilter: entry.isFilterEnabled ? (value, record) => record[entry.field] === value : null,
844
- //If sorting is enabled for this entry, provide a sorter function
845
- sorter: entry.isSortingEnabled ? (a, b) => String(a[entry.field]).localeCompare(String(b[entry.field])) : null,
846
-
847
- // Apply the filter search
848
- filterSearch: entry.isFilterEnabled ? entry.isFilterEnabled : false,
849
- exportDefinition: (record) => {
850
- //Custom components should not be downloaded
851
- // return entry.field === 'custom' ? null : record[entry.field];
852
- if (entry.field === 'custom') {
853
- // Find the field key in props that corresponds to 'description'
854
- const description = entry.props?.find((p) => p.value === 'description')?.field;
855
-
856
- // Return the value from record.props if it exists
857
- return description && record[description] ? record[description] : null;
858
- }
859
- return record[entry.field];
860
- },
861
- // Add align property based on column type
862
- align: entry.type === 'number' ? 'right' : 'left',
863
- };
864
- // }
865
- }),
866
- // ...[
867
- // {
868
- // title: '',
869
- // key: 'action',
870
- // render: (text, record) => {
871
- // let detail = model.slice(0, model.length - 1);
872
-
873
- // return (
874
- // <Space size="middle">
875
- // {!schema.hideView && !actions.length ? <Link to={`/${city}/${model}/${text.id}`}>View</Link> : null}
876
-
877
- // {actions.map((action) => (
878
- // <Link to={action.url(record)}>{action.caption}</Link>
879
- // ))}
880
- // </Space>
881
- // );
882
- // },
883
- // },
884
- // ],
885
-
886
- // ...[
887
- // {
888
- // title: '',
889
- // key: 'action',
890
- // render: (text, record) => {
891
- // let detail = model.slice(0, model.length - 1);
892
-
893
- // return (
894
- // <Link to={`${menu.path.replace(':id',visitid)}?op_no=${OpNo}`}>
895
- // <Button size={'small'}>
896
- // <PlayCircleOutlined />
897
- // Go to Menu
898
- // </Button>
899
- // </Link>
900
- // );
901
- // },
902
- // },
903
- // ],
904
- ];
905
-
906
- /**
907
- *
908
- * @param {*} result
909
- */
910
-
911
- // function changeView(result) {
912
- // setView(result);
913
- // }
914
-
915
- /**
916
- *
917
- * @param {*} event
918
- */
919
-
920
- function onSearch(event) {
921
- setQuery(event.target.value);
922
- }
923
-
924
- /**
925
- *
926
- */
927
-
928
- useEffect(() => {
929
- //Cheaking if there is patient data exists
930
- if (patients) {
931
- // Commented due to a production issue:
932
- // When displayColumns is not defined, rowIndex and dispatch fields were getting displayed in the table.
933
- // Also, the `data` variable is not used in the code below, and the setData state is already commented out.
934
-
935
- // let data = patients?.map((entry) => {
936
- // entry.rowIndex = entry.opb_id;
937
-
938
- // entry.dispatch = dispatch;
939
-
940
- // return entry;
941
- // });
942
-
943
- // setData(data);
944
-
945
- // Define export data
946
- // Sanitize cols for export to ensure titles are strings
947
- const exportCols = cols.map((col) => {
948
- if (col.title && typeof col.title === 'object' && col.title.props) {
949
- return { ...col, title: col.title.props.title };
950
- }
951
- return col;
952
- });
953
-
954
- let exportDatas = getExportData(patients, exportCols);
955
-
956
- if (exportDatas.exportDataColumns.length && exportDatas.exportDataHeaders.length) {
957
- setExportData({ exportDatas });
958
- }
959
- }
960
- }, [patients, columns]);
961
-
962
- let filtered;
963
-
964
- if (patients) {
965
- filtered = patients.filter((record) => {
966
- if (query) {
967
- // Keys
968
- let keys = Object.keys(record);
969
-
970
- let flag = false;
971
-
972
- keys.forEach((key) => {
973
- let ele = record[key];
974
-
975
- if (ele && typeof ele === 'string' && ele.toLowerCase().indexOf(query.toLowerCase()) !== -1) {
976
- flag = true;
977
- }
978
- });
979
-
980
- /**Will return flag */
981
- return flag;
982
- } else {
983
- return true;
984
- }
985
- });
986
- }
987
-
988
- /**
989
- * Checks for a match in the filtered patient list based on a scanned code,
990
- * updates the relevant state if a match is found, and redirects to the
991
- * patient's detail page. Displays a warning if no match is found.
992
- *
993
- * @param {string} code - The scanned code to match against a specific field of each patient.
994
- */
995
-
996
- const handleScanSuccess = (code) => {
997
- // Filters patients based on the scanned code and the selected barcode key(using attributes 'barcodeFilterKey')
998
- const matched = filtered.filter((patient) => patient[barcodeFilterKey] === code);
999
-
1000
- if (matched.length) {
1001
- const patient = matched[0];
1002
- setFilteredPatients(matched);
1003
- setRedirectPatient(matched);
1004
- message.success(`Match found for ${code}, redirecting...`);
1005
-
1006
- const actionColumn = columns.find((col) => col.field === 'action');
1007
- if (actionColumn) {
1008
- const redirectLink = getRedirectLink(actionColumn, patient);
1009
- // history.push(redirectLink);
1010
- window.location.href = redirectLink;
1011
- }
1012
- } else {
1013
- Modal.warning({
1014
- title: 'No matching records.',
1015
- content: `No match for scanned code: ${code}`,
1016
- });
1017
- }
1018
- };
1019
-
1020
- //open the edit modal
1021
- const handleOpenEdit = (button) => {
1022
- const componentName = button.component;
1023
- const ComponentToRender = ReportingDashboardComp[componentName];
1024
-
1025
- if (!ComponentToRender) {
1026
- console.error(`Component ${componentName} not found!`);
1027
- return;
1028
- }
1029
-
1030
- setSingle({});
1031
- setActiveComponent(() => ComponentToRender);
1032
- setVisible(true);
1033
- };
1034
-
1035
- // close the edit modal
1036
- const handleCloseEdit = () => {
1037
- setShowEdit(false);
1038
- };
1039
- return (
1040
- <>
1041
- <div className="table-header">
1042
- <div className="table-left">
1043
- {/* shwoing caption is not correct so this commented */}
1044
- {/* <span className="menu-caption">{config.caption}</span> */}
1045
- <Search placeholder="Enter Search Value" allowClear onChange={onSearch} />
1046
- {/* <p className="size-hint">{patients.length} records.</p> */}
1047
- </div>
1048
-
1049
- <div className="table-right">
1050
- {/* QR Scan start */}
1051
- {showScanner ? (
1052
- <Button size="small" type="primary" icon={<QrcodeOutlined />} onClick={() => setScannerVisible(true)}>
1053
- Scan QR
1054
- </Button>
1055
- ) : null}
1056
- {/** Add User button */}
1057
- {Array.isArray(buttonAttributes) &&
1058
- buttonAttributes.map((btn, index) => (
1059
- <Button key={index} size="small" type="primary" style={{ marginLeft: 8 }} onClick={() => handleOpenEdit(btn)}>
1060
- {btn.title}
1061
- </Button>
1062
- ))}
1063
-
1064
- <Modal open={visible} onCancel={() => setVisible(false)} footer={null} destroyOnClose width={950} style={{ top: 10 }}>
1065
- {ActiveComponent && (
1066
- <ActiveComponent
1067
- formContent={single}
1068
- callback={() => {
1069
- setVisible(false);
1070
- refresh();
1071
- setVisible(false);
1072
- fetchReportData();
1073
- }}
1074
- // {...dynamicProps}
1075
- />
1076
- )}
1077
- </Modal>
1078
-
1079
- <Modal open={isScannerVisible} title="Scan QR Code" footer={null} onCancel={() => setScannerVisible(false)} destroyOnClose>
1080
- <QrScanner onScanSuccess={handleScanSuccess} onClose={() => setScannerVisible(false)} />
1081
- </Modal>
1082
-
1083
- <div>
1084
- {/* QR Scan End */}
1085
- {/*table data export to csc component*/}
1086
- {exportData.exportDatas && (
1087
- <ExportReactCSV
1088
- title={config.caption}
1089
- headers={exportData.exportDatas.exportDataHeaders}
1090
- csvData={exportData.exportDatas.exportDataColumns}
1091
- />
1092
- )}
1093
- </div>
1094
- </div>
1095
- </div>
1096
-
1097
- <div>
1098
- {/* {view ? (
1099
- <>
1100
- <CardList dataSource={data} columns={columns} />
1101
- </>
1102
- ) : (
1103
- <> */}
1104
- <Card>
1105
- {loading ? (
1106
- <>
1107
- <Skeleton active paragraph={{ rows: 6 }} />
1108
- </>
1109
- ) : (
1110
- <TableComponent
1111
- size="small"
1112
- scroll={{ x: 'max-content', y: '60vh' }}
1113
- tableLayout="fixed"
1114
- sticky
1115
- rowKey={(record) => record.OpNo}
1116
- dataSource={filtered ? filtered : patients} // In case if there is no filtered values we can use patient data
1117
- columns={cols}
1118
- pagination={false}
1119
- // title={config.caption}
1120
- summary={(pageData) => {
1121
- // Variable to save the summary data
1122
- let summary = {};
1123
-
1124
- let summaryColumns = [
1125
- { field: 'opb_amt', title: 'Amount' },
1126
- { field: 'opb_netamt', title: 'Net Amount' },
1127
- ];
1128
-
1129
- let tableColumns = cols;
1130
-
1131
- // Creating a copy of columns to append the summary configuration that is needed to set
1132
- tableColumns.forEach((record, index) => {
1133
- summaryColumns.forEach((inner) => {
1134
- if (record.field === inner.field) {
1135
- tableColumns[index].summary = inner;
1136
- }
1137
- });
1138
- });
1139
-
1140
- // Initialize
1141
- summaryColumns.map((item) => {
1142
- return (summary[item.field] = 0);
1143
- });
1144
-
1145
- // Find the total
1146
- summaryColumns.map((item) => {
1147
- pageData.forEach((entry) => {
1148
- return (summary[item.field] = summary[item.field] + entry[item.field]);
1149
- });
1150
- });
1151
-
1152
- return (
1153
- <>
1154
- <Table.Summary.Row>
1155
- {tableColumns.map((column, key) => {
1156
- return <Table.Summary.Cell key={key}>{column.summary ? <>{summary[column.summary.field]}</> : null}</Table.Summary.Cell>;
1157
- })}
1158
- </Table.Summary.Row>
1159
- </>
1160
- );
1161
- }}
1162
- />
1163
- )}
1164
-
1165
- {/* Pagination aligned to the right */}
1166
- <div style={{ display: 'flex', justifyContent: 'flex-end', marginTop: 8 }}>
1167
- <Pagination
1168
- showSizeChanger
1169
- current={pagination.current}
1170
- pageSize={pagination.pageSize}
1171
- total={pagination.total}
1172
- pageSizeOptions={[20, 30, 50, 100]}
1173
- onChange={(page, pageSize) => handlePagination({ current: page, pageSize })}
1174
- />
1175
- </div>
1176
-
1177
- {/*If patient data exists show the number else to 0 */}
1178
- <p className="size-hint">{patients ? patients.length : 0} records. </p>
1179
- </Card>
1180
- {/* </> */}
1181
- {/* )} */}
1182
- </div>
1183
- </>
1184
- );
1185
- }
1186
-
1187
- // //Mobile view card Section
1188
- // function CardList({ dataSource, url }) {
1189
- // const { user = {}, dispatch } = useContext(GlobalContext);
1190
-
1191
- // function onClick(item) {
1192
- // Location.navigate({
1193
- // url: `/lab-detail/${item.BillID}`,
1194
- // });
1195
-
1196
- // dispatch({ type: 'index', payload: item.rowIndex });
1197
- // }
1198
-
1199
- // return dataSource.map((item, index) => {
1200
- // // to={`/lab-detail/${item.BillID}`}
1201
- // return (
1202
- // <div
1203
- // key={index}
1204
- // className="report-item"
1205
- // onClick={() => {
1206
- // onClick(item);
1207
- // }}
1208
- // >
1209
- // <GuestCard record={item} />
1210
- // </div>
1211
- // );
1212
- // });
1213
- // }
1214
-
1215
- // function GuestCard({ record }) {
1216
- // return (
1217
- // <Card className="card vehicle-card">
1218
- // <div className="card">
1219
- // <h2 className="title">{record.PName}</h2>
1220
-
1221
- // <h4 className="values">{record.OpNo}</h4>
1222
-
1223
- // <h3 className="values">{record.Test}</h3>
1224
-
1225
- // <h3 className="values">Primary Result : {record.PrimaryResult || 'Pending'}</h3>
1226
-
1227
- // <Text type="secondary">{record.Mobile}</Text>
1228
-
1229
- // <h4 className="values">{record.Date}</h4>
1230
- // </div>
1231
- // </Card>
1232
- // );
1233
- // }
1234
- // );
1235
- // }