ui-soxo-bootstrap-core 2.6.20 → 2.6.22

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.
@@ -1,6 +1,6 @@
1
1
  import React, { useState, useEffect, useContext, useRef } from 'react';
2
2
 
3
- import { Table, Skeleton, Input, Modal, message, Pagination } from 'antd';
3
+ import { Table, Skeleton, Input, Modal, message, Pagination, Tag } from 'antd';
4
4
 
5
5
  import { QrcodeOutlined } from '@ant-design/icons';
6
6
 
@@ -17,10 +17,10 @@ import './reporting-dashboard.scss';
17
17
  // import MenuDashBoard from '../../../../pages/homepage-api/menu-dashboard';
18
18
  import MenuDashBoardComponent from '../../../../lib/elements/basic/menu-dashboard/menu-dashboard';
19
19
  import { useHistory } from 'react-router-dom';
20
-
21
20
  import * as ReportingDashboardComp from '../index';
22
21
  import buildDisplayColumns from './display-columns/build-display-columns';
23
22
  import { getRedirectLink } from './display-columns/display-cell-renderer';
23
+ import AdvancedSearchSelect from './adavance-search/advance-search';
24
24
 
25
25
  // import { isPdfFile } from 'pdfjs-dist';
26
26
 
@@ -56,15 +56,20 @@ export default function ReportingDashboard({
56
56
  const [config, setConfig] = useState({});
57
57
 
58
58
  // State to manage the layout of the form
59
- const [formLayout, setFormLayout] = useState('inline');
59
+ const [formLayout, setFormLayout] = useState('vertical');
60
60
 
61
61
  const [loading, setLoading] = useState(true);
62
62
 
63
63
  const [cardLoading, setCardLoading] = useState(true);
64
64
 
65
+ const [searchParameters, setSearchParameters] = useState([]);
66
+
67
+ const [searchValues, setSearchValues] = useState({});
68
+
65
69
  const [dashboardVisible, setDashBoardVisible] = useState(false);
66
70
 
67
71
  const [formContents, setformContents] = useState({});
72
+ const [liveFormContents, setLiveFormContents] = useState({});
68
73
 
69
74
  const scriptId = useRef(null);
70
75
 
@@ -112,7 +117,7 @@ export default function ReportingDashboard({
112
117
  }
113
118
  setColumns(parsedColumns);
114
119
 
115
- await prepareInputParameters(result, parsedColumns);
120
+ await prepareInputParameters(result, parsedColumns, fetchId);
116
121
 
117
122
  if (result.summary_columns) {
118
123
  setSummaryColumns(JSON.parse(result.summary_columns));
@@ -157,7 +162,7 @@ export default function ReportingDashboard({
157
162
  */
158
163
 
159
164
  //Prepare input parameters by mapping default values and binding models if needed
160
- async function prepareInputParameters(record, parsedColumns) {
165
+ async function prepareInputParameters(record, parsedColumns, fetchId) {
161
166
  setLoading(true);
162
167
  let urlParams = Location.search();
163
168
 
@@ -166,9 +171,11 @@ export default function ReportingDashboard({
166
171
 
167
172
  let otherDetails = record.other_details1 ? JSON.parse(record.other_details1) : null;
168
173
 
169
- parameters = record.input_parameters ? JSON.parse(record.input_parameters) : null;
174
+ parameters = record.input_parameters ? JSON.parse(record.input_parameters) : [];
170
175
 
171
176
  let formContent = {};
177
+ const searchFields = (parameters || []).filter((p) => p.type === 'search' && p.search_enabled === 'yes');
178
+ setSearchParameters([...searchFields]);
172
179
 
173
180
  parameters = await parameters?.map((record) => {
174
181
  // Only if the url params does have a matching value ,
@@ -212,7 +219,15 @@ export default function ReportingDashboard({
212
219
  if (record.type === 'date' && !formContent[record.field]) {
213
220
  formContent[record.field] = moment().tz(process.env.REACT_APP_TIMEZONE);
214
221
  }
215
-
222
+ if (record.type === 'search') {
223
+ if (!formContent[record.field]) formContent[record.field] = [];
224
+ return {
225
+ ...record,
226
+ reportId: fetchId,
227
+ onReset: () => getPatientDetails(fetchId),
228
+ required: record.required,
229
+ };
230
+ }
216
231
  if (['reference-select', 'reference-search', 'select'].indexOf(record.type) !== -1) {
217
232
  // let model = "";
218
233
  let model = CustomModels[record.modelName];
@@ -231,6 +246,7 @@ export default function ReportingDashboard({
231
246
  });
232
247
  // Update form content state
233
248
  setformContents(formContent);
249
+ setLiveFormContents(formContent);
234
250
 
235
251
  // Trigger form submission
236
252
  onFinish(formContent, null, record.input_parameters, parsedColumns);
@@ -242,12 +258,8 @@ export default function ReportingDashboard({
242
258
  // If enabled, clear the details array
243
259
  setDetails([]);
244
260
  } else {
245
- //// Filter the array "parameters" and keep only elements where "type" is present (truthy)
246
- //Keep only parameters that have a "type" → used for UI display
247
- let filter = parameters.filter((ele) => ele.type);
248
- // Update the "details" state with the filtered results
249
-
250
- setDetails([...filter]);
261
+ // Keep all parameters with a type (including search) to render in FormCreator
262
+ setDetails([...parameters.filter((ele) => ele.type)]);
251
263
  }
252
264
  }
253
265
 
@@ -261,6 +273,7 @@ export default function ReportingDashboard({
261
273
  const { current, pageSize } = pagination || {};
262
274
  // If card script id is exist load that id otherwise load id
263
275
  const coreScriptId = scriptId.current ? scriptId.current : id;
276
+ const normalizedColumns = Array.isArray(parsedColumns) ? parsedColumns : Array.isArray(columns) ? columns : [];
264
277
 
265
278
  setLoading(true);
266
279
  try {
@@ -286,6 +299,7 @@ export default function ReportingDashboard({
286
299
  if (scope) {
287
300
  formBody = { body: { ...scope, ...paginationData } };
288
301
  }
302
+
289
303
  // Fetch result
290
304
  const result = await CoreScripts.getReportingLisitng(coreScriptId, formBody, dbPtr);
291
305
 
@@ -299,8 +313,8 @@ export default function ReportingDashboard({
299
313
  // Update patients
300
314
  setPatients(resultDetails || []);
301
315
 
302
- // Check if columns are not yet defined
303
- if (parsedColumns.length === 0 && resultDetails.length > 0) {
316
+ // When display_columns is missing, build columns from the response keys.
317
+ if (normalizedColumns.length === 0 && resultDetails.length > 0) {
304
318
  // Create columns dynamically from resultDetails keys
305
319
  setColumns((prev) => {
306
320
  if (prev.length > 0) return prev;
@@ -330,31 +344,127 @@ export default function ReportingDashboard({
330
344
  }
331
345
  };
332
346
 
333
- // Handle Submit
334
347
  const handleSubmit = (values) => {
348
+ // Extract search fields from the form values
349
+ const searchKeys = searchParameters.map((p) => p.field);
350
+ const currentSearchValues = {};
351
+ const formValues = {};
352
+
353
+ Object.keys(values).forEach((key) => {
354
+ if (searchKeys.includes(key)) {
355
+ currentSearchValues[key] = values[key];
356
+ } else {
357
+ formValues[key] = values[key];
358
+ }
359
+ });
360
+
361
+ const hasSearchValues = Object.values(currentSearchValues).some((v) => Array.isArray(v) && v.length > 0);
362
+
363
+ if (!hasSearchValues) {
364
+ runSubmit(formValues);
365
+ return;
366
+ }
367
+
368
+ // Check if main form values (non-search) changed
369
+ const formChanged = Object.keys(formValues).some((key) => {
370
+ const newVal = formValues[key];
371
+ const oldVal = formContents[key];
372
+
373
+ if (moment.isMoment(newVal) && moment.isMoment(oldVal)) {
374
+ return !newVal.isSame(oldVal, 'day');
375
+ }
376
+
377
+ return newVal !== oldVal;
378
+ });
379
+
380
+ if (formChanged) {
381
+ Modal.confirm({
382
+ title: 'Filters changed',
383
+ content: 'You changed some filters. Do you want to search using these filters also?',
384
+ okText: 'Yes',
385
+ cancelText: 'No',
386
+
387
+ onOk() {
388
+ // YES → send form values + search condition
389
+ const finalValues = {
390
+ ...formValues,
391
+ search_values: currentSearchValues,
392
+ };
393
+
394
+ runSubmit(finalValues);
395
+ },
396
+
397
+ onCancel() {
398
+ // NO → reset form values
399
+ const resetValues = {};
400
+ Object.keys(formValues).forEach((key) => {
401
+ resetValues[key] = null;
402
+ });
403
+
404
+ const finalValues = {
405
+ ...resetValues,
406
+ search_values: currentSearchValues,
407
+ };
408
+
409
+ runSubmit(finalValues);
410
+ },
411
+ });
412
+ } else {
413
+ // no form change
414
+ const resetValues = {};
415
+ Object.keys(formValues).forEach((key) => {
416
+ resetValues[key] = null;
417
+ });
418
+
419
+ const finalValues = {
420
+ ...resetValues,
421
+ search_values: currentSearchValues,
422
+ };
423
+
424
+ runSubmit(finalValues);
425
+ }
426
+ };
427
+
428
+ const runSubmit = (finalValues) => {
335
429
  const { pageSize } = pagination;
336
430
  const resetPage = 1;
337
431
 
338
- // Reset script id on Submit
339
432
  scriptId.current = null;
340
433
 
341
- // Get current query params
342
- const currentUrlParams = Location.search();
343
- const { script_id, selected_card, ...cleanParams } = currentUrlParams;
434
+ const hasSearchValues = finalValues.search_values && Object.values(finalValues.search_values).some((v) => Array.isArray(v) && v.length > 0);
344
435
 
345
- // Construct new query string
346
- const newParams = new URLSearchParams({
347
- ...cleanParams,
348
- current: resetPage,
349
- pageSize,
350
- });
436
+ if (!hasSearchValues) {
437
+ const currentUrlParams = Location.search();
438
+ const { script_id, selected_card, ...cleanParams } = currentUrlParams;
351
439
 
352
- // Replace URL
353
- const newUrl = `${window.location.pathname}?${newParams.toString()}`;
354
- window.history.replaceState({}, '', newUrl);
440
+ const newParams = new URLSearchParams({
441
+ ...cleanParams,
442
+ current: resetPage,
443
+ pageSize,
444
+ });
445
+
446
+ const newUrl = `${window.location.pathname}?${newParams.toString()}`;
447
+ window.history.replaceState({}, '', newUrl);
448
+ }
355
449
 
356
- // Trigger submit handler
357
- onFinish(values, resetPage);
450
+ onFinish(finalValues, resetPage);
451
+ };
452
+
453
+ const selectedSearchFields = details.filter((field) => {
454
+ if (field.type !== 'search') return false;
455
+
456
+ const value = liveFormContents[field.field];
457
+ return Array.isArray(value) ? value.length > 0 : !!value;
458
+ });
459
+
460
+ const handleRemoveSearchField = (fieldName) => {
461
+ const updatedValues = {
462
+ ...liveFormContents,
463
+ [fieldName]: [],
464
+ };
465
+
466
+ setLiveFormContents(updatedValues);
467
+ handleSubmit(updatedValues);
358
468
  };
359
469
 
360
470
  /**
@@ -389,6 +499,12 @@ export default function ReportingDashboard({
389
499
  //Getting url friendly value by matching the url values and input_parameter values
390
500
  let value = values[parameter.field];
391
501
 
502
+ // If the value is missing at the root level (which happens when search fields are moved
503
+ // into search_values during submission), try to retrieve it from the nested object.
504
+ if (value === undefined && values.search_values && values.search_values[parameter.field] !== undefined) {
505
+ value = values.search_values[parameter.field];
506
+ }
507
+
392
508
  // Keep Moment object in state for picker
393
509
  if (parameter.type === 'date' && value) {
394
510
  formContent[parameter.field] = value.isValid ? value : moment(value); // ensure Moment
@@ -410,6 +526,7 @@ export default function ReportingDashboard({
410
526
  );
411
527
 
412
528
  setformContents(formContent);
529
+ setLiveFormContents(formContent);
413
530
  Location.search({ ...Location.search(), ...filteredParams });
414
531
  }
415
532
 
@@ -505,17 +622,18 @@ export default function ReportingDashboard({
505
622
  }}
506
623
  styles={{ paddingRight: '15px', alignItems: 'center' }}
507
624
  fields={details}
625
+ reportId={id}
508
626
  formContent={formContents}
509
627
  // formContent={{ [model]: {} }}
510
628
  modelIndex="requestId"
511
629
  model={model}
512
630
  onSubmit={handleSubmit}
631
+ onFormValuesChange={setLiveFormContents}
513
632
  callback={() => {
514
633
  // history.goBack();
515
634
  }}
516
635
  />
517
636
  ) : null}
518
- {/* </Card> */}
519
637
  </div>
520
638
 
521
639
  {/** GuestList component start*/}
@@ -533,6 +651,8 @@ export default function ReportingDashboard({
533
651
  pagination={pagination}
534
652
  handlePagination={handlePagination}
535
653
  attributes={attributes}
654
+ selectedSearchFields={selectedSearchFields}
655
+ handleRemoveSearchField={handleRemoveSearchField}
536
656
  fetchReportData={(paginationUpdate) => fetchReportData(id, formContents, dbPtr, paginationUpdate || pagination)}
537
657
  />
538
658
  {/** GuestList component end*/}
@@ -566,6 +686,8 @@ function GuestList({
566
686
  pagination,
567
687
  handlePagination,
568
688
  attributes,
689
+ selectedSearchFields,
690
+ handleRemoveSearchField,
569
691
  fetchReportData,
570
692
  }) {
571
693
  /**
@@ -824,13 +946,31 @@ function GuestList({
824
946
  <>
825
947
  <div className="table-header">
826
948
  <div className="table-left">
827
- {/* shwoing caption is not correct so this commented */}
828
- {/* <span className="menu-caption">{config.caption}</span> */}
829
- <Search placeholder="Enter Search Value" allowClear onChange={onSearch} />
830
- {/* <p className="size-hint">{patients.length} records.</p> */}
949
+ {/* {selectedSearchFields?.length > 0 ? (
950
+ <div className="search-tags-container">
951
+ {selectedSearchFields.map((field) => (
952
+ <Tag key={field.field} closable color="blue" onClose={() => handleRemoveSearchField(field.field)}>
953
+ {field.caption}
954
+ </Tag>
955
+ ))}
956
+ </div>
957
+ ) : null} */}
831
958
  </div>
832
959
 
833
960
  <div className="table-right">
961
+ {/* shwoing caption is not correct so this commented */}
962
+ {/* <span className="menu-caption">{config.caption}</span> */}
963
+ <Search className="table-search-input" placeholder="Enter Search Value" allowClear onChange={onSearch} />
964
+ <div className="table-export-button">
965
+ {exportData.exportDatas && (
966
+ <ExportReactCSV
967
+ title={config.caption}
968
+ headers={exportData.exportDatas.exportDataHeaders}
969
+ csvData={exportData.exportDatas.exportDataColumns}
970
+ />
971
+ )}
972
+ </div>
973
+
834
974
  {/* QR Scan start */}
835
975
  {showScanner ? (
836
976
  <Button size="small" type="primary" icon={<QrcodeOutlined />} onClick={() => setScannerVisible(true)}>
@@ -863,19 +1003,7 @@ function GuestList({
863
1003
  <Modal open={isScannerVisible} title="Scan QR Code" footer={null} onCancel={() => setScannerVisible(false)} destroyOnClose>
864
1004
  <QrScanner onScanSuccess={handleScanSuccess} onClose={() => setScannerVisible(false)} />
865
1005
  </Modal>
866
-
867
- <div>
868
- {/* QR Scan End */}
869
- {/*table data export to csc component*/}
870
- {exportData.exportDatas && (
871
- <ExportReactCSV
872
- title={config.caption}
873
- fileName={`${(config.caption || 'Report').trim().replace(/\s+/g, '_')}_${moment().format('YYYY-MM-DD-HH-mm-ss-SSS')}.xlsx`}
874
- headers={exportData.exportDatas.exportDataHeaders}
875
- csvData={exportData.exportDatas.exportDataColumns}
876
- />
877
- )}
878
- </div>
1006
+ {/* QR Scan End */}
879
1007
  </div>
880
1008
  </div>
881
1009
 
@@ -16,12 +16,17 @@
16
16
  display: flex;
17
17
  justify-content: space-between;
18
18
  align-items: center;
19
+ gap: 12px;
19
20
  padding-bottom: 10px;
20
21
  padding-top: 10px;
21
22
 
22
23
  .table-left {
23
24
  display: flex;
24
25
  align-items: center;
26
+ gap: 8px;
27
+ flex: 1;
28
+ min-width: 0;
29
+ margin-left: 6px;
25
30
 
26
31
  .menu-caption {
27
32
  min-width: 100px;
@@ -31,9 +36,23 @@
31
36
 
32
37
  .table-right{
33
38
  display: flex;
39
+ align-items: center;
40
+ justify-content: flex-end;
41
+ flex-wrap: nowrap;
34
42
  gap: 4px;
35
43
  }
36
44
  }
45
+
46
+ .table-search-input {
47
+ width: 220px;
48
+ min-width: 220px;
49
+ }
50
+
51
+ .table-export-button {
52
+ display: flex;
53
+ align-items: center;
54
+ flex-shrink: 0;
55
+ }
37
56
 
38
57
 
39
58
  .form-card {
@@ -48,6 +67,23 @@
48
67
  padding: 0px;
49
68
  }
50
69
 
70
+ .search-tags-container {
71
+ display: flex;
72
+ align-items: center;
73
+ flex-wrap: nowrap;
74
+ gap: 8px;
75
+ min-width: 0;
76
+ max-width: 100%;
77
+ overflow-x: auto;
78
+ overflow-y: hidden;
79
+ white-space: nowrap;
80
+ }
81
+
82
+ .search-tags-container .ant-tag {
83
+ flex-shrink: 0;
84
+ margin-inline-end: 0;
85
+ }
86
+
51
87
  // margin: 0px 10px;
52
88
 
53
89
  .ant-skeleton {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ui-soxo-bootstrap-core",
3
- "version": "2.6.20",
3
+ "version": "2.6.22",
4
4
  "description": "All the Core Components for you to start",
5
5
  "keywords": [
6
6
  "all in one"