ui-soxo-bootstrap-core 2.6.21 → 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,10 +1,10 @@
1
- import React, { useState, useEffect, useContext, useRef } from 'react';
1
+ import { useContext, useEffect, useRef, useState } from 'react';
2
2
 
3
3
  import { Route, Switch } from 'react-router-dom';
4
4
 
5
5
  import { Skeleton } from 'antd';
6
6
 
7
- import { GlobalHeader, ChangePassword, useTranslation, GlobalContext, ModuleRoutes, SpotlightSearch, SettingsUtil, Profile, Card } from '../../lib';
7
+ import { Card, ChangePassword, GlobalContext, GlobalHeader, ModuleRoutes, Profile, SettingsUtil, SpotlightSearch, useTranslation } from '../../lib';
8
8
 
9
9
  import './landing-api.scss';
10
10
 
@@ -16,7 +16,7 @@ import ReportingDashboard from '../../modules/reporting/components/reporting-das
16
16
 
17
17
  import PropTypes from 'prop-types';
18
18
 
19
- import { MenusAPI, CoreScripts } from '../../models';
19
+ import { CoreScripts, MenusAPI } from '../../models';
20
20
 
21
21
  const motivatingMessages = [
22
22
  'Setting things up for a great start...',
@@ -159,7 +159,6 @@ export default function LandingApi({ history, CustomComponents, CustomModels, ap
159
159
  * @param reports
160
160
  */
161
161
  async function loadMenus(reports) {
162
-
163
162
  setLoader(true);
164
163
 
165
164
  // setReports(report)
@@ -169,16 +168,12 @@ export default function LandingApi({ history, CustomComponents, CustomModels, ap
169
168
  // console.log(result);
170
169
 
171
170
  if (result && Array.isArray(result.result) && result.result.length) {
172
-
173
171
  // setModules(result.result);
174
-
175
172
  // result.result.map((ele) => {
176
173
  // let languageString = JSON.parse(ele.attributes)
177
174
  // console.log('language_string', languageString);
178
175
  // if (languageString && languageString.languages) {
179
-
180
176
  // const language = i18n.language;
181
-
182
177
  // i18n.addResourceBundle(language, 'translation', languageString.languages[i18n.language]);
183
178
  // }
184
179
  // })
@@ -189,7 +184,6 @@ export default function LandingApi({ history, CustomComponents, CustomModels, ap
189
184
  dispatch({ type: 'settings', payload: result.result.settings });
190
185
  }
191
186
 
192
-
193
187
  // Reports length
194
188
  if (reports.length) {
195
189
  reportMenus = [
@@ -224,7 +218,6 @@ export default function LandingApi({ history, CustomComponents, CustomModels, ap
224
218
  //If there is no roles assigned to the user
225
219
  setAllModules([...coreModules]);
226
220
  }
227
-
228
221
  } else {
229
222
  // for nura
230
223
  if (result && result.result.menus && reportMenus) {
@@ -233,14 +226,10 @@ export default function LandingApi({ history, CustomComponents, CustomModels, ap
233
226
  //If there is no roles assigned to the user
234
227
  setAllModules([...coreModules]);
235
228
  }
236
-
237
-
238
229
  }
239
230
  setLoader(false);
240
-
241
231
  }
242
232
 
243
-
244
233
  /**
245
234
  * Load the scripts
246
235
  *
@@ -505,16 +494,18 @@ function DefineRoute({ menus, CustomComponents, CustomModels, settings, callback
505
494
  function printRoute(menu, index, CustomComponents, CustomModels, callback) {
506
495
  // From the path we remove any query parameters that exist in the url
507
496
  //
508
- let paths;
497
+ let paths = [];
509
498
 
510
499
  //There will be path for normal menus
511
- if (menu.path) {
500
+ if (menu?.path) {
512
501
  paths = menu.path.split('?');
513
- } else {
514
- /** In case if there is no path , ie, in case of reports menu */
515
- menu.sub_menus.map((menus) => {
516
- paths = menus.path.split('?');
517
- });
502
+ } else if (Array.isArray(menu?.sub_menus)) {
503
+ for (const subMenu of menu.sub_menus) {
504
+ if (subMenu?.path) {
505
+ paths = subMenu.path.split('?');
506
+ break;
507
+ }
508
+ }
518
509
  }
519
510
 
520
511
  // Find the route path
@@ -35,6 +35,8 @@ import { FieldCustomizer, TabCustomizer } from "./../../../../";
35
35
 
36
36
  import { prepareAndExecuteScript } from './../../../../utils/script.utils';
37
37
 
38
+ import AdvancedSearchSelect from "../../../../../modules/reporting/components/reporting-dashboard/adavance-search/advance-search";
39
+
38
40
  const { TextArea } = Input;
39
41
 
40
42
  const { Option } = Select;
@@ -59,8 +61,10 @@ function FormCreator({
59
61
  formContent = {},
60
62
  onSubmit,
61
63
  fields = [],
64
+ reportId,
62
65
  callback,
63
66
  layout,
67
+ onFormValuesChange,
64
68
  // Below are arguments for use in form display
65
69
  onFieldUpdate,
66
70
  // onListUpdate,
@@ -69,7 +73,6 @@ function FormCreator({
69
73
  const [form] = Form.useForm();
70
74
 
71
75
  let layoutValue;
72
-
73
76
  if (layout) {
74
77
  layoutValue = layout
75
78
  } else {
@@ -194,11 +197,15 @@ function FormCreator({
194
197
  values = await prepareAndExecuteScript(values, null, fieldConfiguration.on_change);
195
198
 
196
199
  }
200
+
201
+ if (onFormValuesChange) {
202
+ onFormValuesChange(values);
203
+ }
197
204
  }
198
205
 
199
206
  return (
200
207
  <section className="form-creator">
201
- <div>
208
+
202
209
  {/* <DragDropContext onDragEnd={onDragEnd}>
203
210
  <Droppable droppableId="droppable">
204
211
 
@@ -251,6 +258,8 @@ function FormCreator({
251
258
  });
252
259
 
253
260
  }}
261
+ // layout="inline"
262
+
254
263
  onFieldsChange={onFieldsChange}
255
264
 
256
265
  onValuesChange={onValuesChange}
@@ -265,6 +274,7 @@ function FormCreator({
265
274
  {/* Mapper maps each fields to build the form */}
266
275
  <FieldMapper
267
276
  fields={fields}
277
+ reportId={reportId}
268
278
  onChange={onChange}
269
279
  selectedInformation={selectedInformation}
270
280
  onUpload={onUpload}
@@ -278,7 +288,7 @@ function FormCreator({
278
288
  SUBMIT
279
289
  </Button>
280
290
  </Form>
281
- </div>
291
+
282
292
  {/* </div>)}
283
293
 
284
294
  </Droppable>
@@ -299,6 +309,7 @@ export default FormCreator;
299
309
  */
300
310
  function FieldMapper({
301
311
  fields = [],
312
+ reportId,
302
313
  onChange,
303
314
  selectedInformation,
304
315
  onUpload,
@@ -337,6 +348,7 @@ function FieldMapper({
337
348
  {/* Mapper maps each fields to build the form */}
338
349
  <FieldMapper
339
350
  fields={tab.fields}
351
+ reportId={reportId}
340
352
  onChange={onChange}
341
353
  onUpload={onUpload}
342
354
  onFieldUpdate={onFieldUpdate}
@@ -358,6 +370,7 @@ function FieldMapper({
358
370
  ?
359
371
  <UserInput
360
372
  onChange={onChange}
373
+ reportId={reportId}
361
374
  index={index}
362
375
  key={index}
363
376
  onUpload={onUpload}
@@ -371,6 +384,7 @@ function FieldMapper({
371
384
  } else {
372
385
  return <UserInput
373
386
  onChange={onChange}
387
+ reportId={reportId}
374
388
  key={index}
375
389
  selectedInformation={selectedInformation}
376
390
  index={index}
@@ -397,7 +411,7 @@ function FieldMapper({
397
411
  *
398
412
  * @param {*} param0
399
413
  */
400
- function UserInput({ field, onUpload, selectedInformation, onChange, onFieldUpdate, onFieldRemove, index }) {
414
+ function UserInput({ field, onUpload, selectedInformation, onChange, onFieldUpdate, onFieldRemove, index, reportId }) {
401
415
 
402
416
  let props = {};
403
417
 
@@ -423,8 +437,11 @@ function UserInput({ field, onUpload, selectedInformation, onChange, onFieldUpda
423
437
 
424
438
  switch (field.type) {
425
439
 
440
+ case 'search':
441
+ return <AdvancedSearchSelect {...field} reportId={reportId} style={{ width: '100%' }} />
442
+
426
443
  case 'number':
427
- return <InputNumber required={field.required} />
444
+ return <InputNumber required={field.required} />
428
445
 
429
446
  case 'input':
430
447
  return <Input required={field.required} />
@@ -459,7 +476,7 @@ function UserInput({ field, onUpload, selectedInformation, onChange, onFieldUpda
459
476
  return <TextArea rows={4} required={field.required} />
460
477
 
461
478
  case 'boolean':
462
- return <Select style={{ width: 120 }} required={field.required}>
479
+ return <Select style={{ width: '120' }} required={field.required}>
463
480
  {[true, false].map((option, key) => <Option key={key} value={option}>{option ? 'Yes' : 'No'}</Option>)}
464
481
  </Select>
465
482
 
@@ -495,7 +512,7 @@ function UserInput({ field, onUpload, selectedInformation, onChange, onFieldUpda
495
512
  return (
496
513
  <Select defaultValue={defaultValue}
497
514
  required={field.required}
498
- style={{ width: 120 }}
515
+ style={{ width: '120' }}
499
516
  onChange={(value) => onChange(field, value)}
500
517
  >
501
518
  {field.options.map((option, key) => (
@@ -605,7 +622,13 @@ function UserInput({ field, onUpload, selectedInformation, onChange, onFieldUpda
605
622
  <FieldCustomizer field={field} onFieldUpdate={onFieldUpdate} index={index} onFieldRemove={onFieldRemove} />
606
623
  {/* Customizes the form Ends */}
607
624
 
608
- <Form.Item {...props} name={field.field} label={field.caption} rules={[{ required: field.required, message: field.placeholder || 'Please enter ' + field.caption }]}>
625
+ <Form.Item
626
+ {...props}
627
+ name={field.field}
628
+ label={field.caption}
629
+ rules={[{ required: field.required, message: field.placeholder || 'Please enter ' + field.caption }]}
630
+ style={{ marginBottom: '12px' }}
631
+ >
609
632
 
610
633
  {inputElement(field, onChange)}
611
634
  {/* <InputElement field={field} /> */}
@@ -1,31 +1,34 @@
1
1
  .form-creator {
2
- .ant-input-number {
3
- width: 100%;
4
- }
5
-
6
- .new-record {
7
- gap: 10px !important;
8
- }
2
+ .ant-input-number {
3
+ width: 100%;
4
+ }
9
5
 
10
- .form-item-element {
11
- position: relative;
12
- padding: 8px;
13
- border-radius: 4px;
14
- // border: 1px solid #e3e3e3;
15
- margin-bottom: 3px;
16
-
17
- .field-customizer {
18
- .actions {
19
- // display: none;
20
- float: right;
21
- // position: absolute;
22
- // right: 0px;
23
- }
24
- }
6
+ .new-record {
7
+ display: flex;
8
+ flex-wrap: wrap;
9
+ align-items: flex-start;
10
+ gap: 10px !important;
11
+ }
25
12
 
13
+ .form-item-element {
14
+ position: relative;
15
+ padding: 4px;
16
+ // border-radius: 4px;
17
+ // border: 1px solid #e3e3e3;
18
+ // margin-bottom: 3px;
19
+ min-width: 140px;
20
+ // flex: 0 1 auto;
21
+ .field-customizer {
22
+ .actions {
23
+ // display: none;
24
+ float: right;
25
+ // position: absolute;
26
+ // right: 0px;
27
+ }
26
28
  }
29
+ }
27
30
 
28
- .submit-button {
29
- margin-top: 10px;
30
- }
31
- }
31
+ .submit-button {
32
+ margin-top: 34px;
33
+ }
34
+ }
@@ -9,142 +9,150 @@ import React from 'react';
9
9
 
10
10
  import Base from '../base/base';
11
11
 
12
- import { ApiUtils } from './../../lib/'
13
-
12
+ import { ApiUtils } from './../../lib/';
14
13
 
15
14
  class CoreScript extends Base {
15
+ constructor() {
16
+ super();
16
17
 
17
- constructor() {
18
- super();
19
-
20
- this.fields = [
21
- {
22
- field: 'name',
23
- caption: 'Name'
24
- },
25
- {
26
- field: 'amount',
27
- caption: 'Amount'
28
- }
29
- ];
30
-
31
- // this.columns = ;
32
- }
33
-
34
- get id() {
35
- return 'id';
36
- }
37
-
38
- get getEndpoint() {
39
- return 'core-scripts';
40
- }
41
-
42
-
43
- get modelName() {
44
- return `core-scripts`;
45
- }
46
-
47
- get columns() {
48
- return [
49
- {
50
- caption: 'Staff',
51
- field: 'staff.name',
52
- key: 'staff'
53
- },
54
- {
55
- caption: 'Weight',
56
- field: 'weight',
57
- key: 'weight'
58
- },
59
- {
60
- caption: 'Date',
61
- field: 'created_at',
62
- key: 'created_at'
63
- }
64
- ];
65
- }
66
-
67
-
68
- /**
69
- *Getting core_script data
70
- * @returns
71
- */
72
- getReportingLisitng = (id, formBody, dbPtr = null) => {
73
-
74
- // Settings db pointer
75
- if (!dbPtr) dbPtr = localStorage.db_ptr;
76
- return ApiUtils.post({
77
- url: `core-scripts/dashboardquery/${id}`,
78
- formBody,
79
- headers: {
80
- 'Content-Type': 'application/json',
81
- Authorization: 'Bearer ' + localStorage.access_token,
82
- db_ptr: dbPtr
18
+ this.fields = [
19
+ {
20
+ field: 'name',
21
+ caption: 'Name',
83
22
  },
84
- });
85
- };
86
-
23
+ {
24
+ field: 'amount',
25
+ caption: 'Amount',
26
+ },
27
+ ];
87
28
 
88
- /**
89
- *Updating user details
90
- * @returns
91
- */
92
- getUserDetailsLisitng = (id, formBody) => {
29
+ // this.columns = ;
30
+ }
93
31
 
94
- return ApiUtils.post({
95
- url: `core-scripts/update-user-deatils/${id}`,
96
- formBody,
97
- });
98
- };
32
+ get id() {
33
+ return 'id';
34
+ }
99
35
 
36
+ get getEndpoint() {
37
+ return 'core-scripts';
38
+ }
100
39
 
101
- /**
102
- *
103
- */
104
- getReportMenu = async ({ id }) => {
105
-
106
- const result = await this.getRecord({ id });
107
-
108
- const report = result.result
109
-
110
- return {
111
- ...report,
112
- caption: report.caption,
113
- path: '/reports/' + report.id,
114
- is_visible: true
115
- }
116
- }
117
-
118
- /**
119
- * To get extra script data corresponding to mode
120
- *
121
- * @param {*} mode
122
- * @returns
123
- */
124
- getExtraInfo = (mode) => {
125
- return ApiUtils.get({
126
- url: `core-scripts/get-script-data?mode=${mode}`,
127
- });
128
- };
40
+ get modelName() {
41
+ return `core-scripts`;
42
+ }
129
43
 
130
-
131
- /**
132
- * To get executed data
133
- *
134
- * @param {*} mode
135
- * @returns
136
- */
137
-
138
- getSelectedExtraInfo = (formBody) => {
139
- return ApiUtils.post({
140
- url: `core-scripts/get-selected-script-data`,
141
- formBody,
142
- headers: {
143
- 'Content-Type': 'application/json',
144
- Authorization: 'Bearer ' + localStorage.access_token,
145
- },
146
- });
44
+ get columns() {
45
+ return [
46
+ {
47
+ caption: 'Staff',
48
+ field: 'staff.name',
49
+ key: 'staff',
50
+ },
51
+ {
52
+ caption: 'Weight',
53
+ field: 'weight',
54
+ key: 'weight',
55
+ },
56
+ {
57
+ caption: 'Date',
58
+ field: 'created_at',
59
+ key: 'created_at',
60
+ },
61
+ ];
62
+ }
63
+
64
+ /**
65
+ *Getting core_script data
66
+ * @returns
67
+ */
68
+ getReportingLisitng = (id, formBody, dbPtr = null) => {
69
+ // Settings db pointer
70
+ if (!dbPtr) dbPtr = localStorage.db_ptr;
71
+ return ApiUtils.post({
72
+ // baseUrl: 'http://localhost:8002/dev/',
73
+ url: `core-scripts/dashboardquery/${id}`,
74
+ formBody,
75
+ headers: {
76
+ 'Content-Type': 'application/json',
77
+ Authorization: 'Bearer ' + localStorage.access_token,
78
+ db_ptr: dbPtr,
79
+ },
80
+ });
81
+ };
82
+
83
+ /**
84
+ *Updating user details
85
+ * @returns
86
+ */
87
+ getUserDetailsLisitng = (id, formBody) => {
88
+ return ApiUtils.post({
89
+ url: `core-scripts/update-user-deatils/${id}`,
90
+ formBody,
91
+ });
92
+ };
93
+
94
+ getQuery = (formBody) => {
95
+ return ApiUtils.post({
96
+ url: `core-scripts/execute-script-api`,
97
+ formBody,
98
+ });
99
+ };
100
+ /**
101
+ *
102
+ */
103
+ getQuerySeacch = (formBody) => {
104
+ return ApiUtils.post({
105
+ // baseUrl: 'http://localhost:8002/dev/',
106
+ url: `core-scripts/execute-script-by-search`,
107
+ formBody,
108
+ });
109
+ };
110
+
111
+ /**
112
+ *
113
+ */
114
+ getReportMenu = async ({ id }) => {
115
+ const result = await this.getRecord({ id });
116
+
117
+ const report = result.result;
118
+
119
+ return {
120
+ ...report,
121
+ caption: report.caption,
122
+ path: '/reports/' + report.id,
123
+ is_visible: true,
147
124
  };
125
+ };
126
+
127
+ /**
128
+ * To get extra script data corresponding to mode
129
+ *
130
+ * @param {*} mode
131
+ * @returns
132
+ */
133
+ getExtraInfo = (mode) => {
134
+ return ApiUtils.get({
135
+ url: `core-scripts/get-script-data?mode=${mode}`,
136
+ });
137
+ };
138
+
139
+ /**
140
+ * To get executed data
141
+ *
142
+ * @param {*} mode
143
+ * @returns
144
+ */
145
+
146
+ getSelectedExtraInfo = (formBody) => {
147
+ return ApiUtils.post({
148
+ url: `core-scripts/get-selected-script-data`,
149
+ formBody,
150
+ headers: {
151
+ 'Content-Type': 'application/json',
152
+ Authorization: 'Bearer ' + localStorage.access_token,
153
+ },
154
+ });
155
+ };
148
156
  }
149
157
 
150
158
  export default CoreScript;
@@ -0,0 +1,174 @@
1
+
2
+ import React, { useState, useEffect, useRef } from "react";
3
+ import { Select, Checkbox, Input, Spin } from "antd";
4
+ import { CoreScripts } from "../../../../../models";
5
+ import "./advance-search.scss";
6
+
7
+ const { Search } = Input;
8
+
9
+ export default function AdvancedSearchSelect({ reportId, onReset, field, value, onChange, style, ...rest }) {
10
+ // Normalize the configuration.
11
+ // If 'field' is an object, we are in "legacy" mode. If it's a string, we are using the spread props from rest.
12
+ const isObjectMode = typeof field === "object";
13
+ const config = isObjectMode ? field : { ...rest, field };
14
+
15
+ const finalReportId = reportId || config.reportId;
16
+ const finalOnReset = onReset || config.onReset;
17
+ const isSearchQuery = !!config.search_query;
18
+ const fieldName = isObjectMode ? field.field : field;
19
+ const caption = config.caption;
20
+
21
+ const safeValue = Array.isArray(value) ? value : [];
22
+
23
+ const [searchResults, setSearchResults] = useState([]);
24
+ const [displayOptions, setDisplayOptions] = useState([]);
25
+ const [loading, setLoading] = useState(false);
26
+ const debounceRef = useRef(null);
27
+
28
+ const loadOptions = async (searchText = "") => {
29
+ try {
30
+ setLoading(true);
31
+
32
+ const formBody = {
33
+ script_id: finalReportId,
34
+ search_field: fieldName,
35
+ search_condition: searchText,
36
+ };
37
+
38
+ const res = await CoreScripts.getQuerySeacch(formBody);
39
+ const data = Array.isArray(res.data) ? res.data : [];
40
+ const apiValues = data?.map((r) => r[fieldName]) || [];
41
+
42
+ setSearchResults(apiValues);
43
+ // Refresh display options only when new search results arrive to keep the list stable during interaction
44
+ setDisplayOptions([
45
+ ...safeValue,
46
+ ...apiValues.filter((item) => !safeValue.includes(item)),
47
+ ]);
48
+ } catch (error) {
49
+ console.error("Search API error", error);
50
+ } finally {
51
+ setLoading(false);
52
+ }
53
+ };
54
+
55
+ useEffect(() => {
56
+ // Clear search results when the field or report context changes,
57
+ // but do not load automatically on mount.
58
+ setSearchResults([]);
59
+ }, [fieldName, finalReportId]);
60
+
61
+ const handleSearch = (text) => {
62
+ if (debounceRef.current) clearTimeout(debounceRef.current);
63
+ debounceRef.current = setTimeout(() => loadOptions(text), 400);
64
+ };
65
+
66
+ const toggleValue = (checked, item) => {
67
+ let newValues;
68
+
69
+ if (checked) newValues = [...safeValue, item];
70
+ else newValues = safeValue.filter((v) => v !== item);
71
+
72
+ onChange(newValues);
73
+ };
74
+
75
+ const handleReset = () => {
76
+ onChange([]);
77
+ if (finalOnReset) {
78
+ finalOnReset(); // 🔥 trigger dashboard refresh
79
+ }
80
+ };
81
+
82
+ const isActive = safeValue.length > 0;
83
+
84
+ // -------- INPUT MODE --------
85
+ if (!isSearchQuery) {
86
+ return (
87
+ <Input
88
+ allowClear
89
+ className={`advanced-search-input ${isActive ? "advanced-search-active" : ""}`}
90
+ placeholder={`Search ${caption}`}
91
+ style={style}
92
+ // The parent provides an array for `value`.
93
+ // We take the first element for the input's display value.
94
+ value={safeValue[0] || ""}
95
+ onChange={(e) => {
96
+ const text = e?.target?.value;
97
+ // Always pass an array back to the parent to be consistent with the Select mode.
98
+ onChange(text ? [text] : []);
99
+ if (!text && finalOnReset) {
100
+ finalOnReset();
101
+ }
102
+ }}
103
+ />
104
+ );
105
+ }
106
+
107
+ // -------- SELECT MODE --------
108
+ return (
109
+ <Select
110
+ className={`advanced-search-select ${isActive ? "advanced-search-active" : ""}`}
111
+ style={style}
112
+ mode="multiple"
113
+ value={safeValue}
114
+ placeholder={caption}
115
+ onDropdownVisibleChange={(open) => {
116
+ if (open) {
117
+ loadOptions("");
118
+ } else {
119
+ // When closing, re-sort selected items to the top so they are visible next time it opens
120
+ const currentResults = searchResults.length > 0 ? searchResults : displayOptions;
121
+ setDisplayOptions([
122
+ ...safeValue,
123
+ ...currentResults.filter((item) => !safeValue.includes(item)),
124
+ ]);
125
+ }
126
+ }}
127
+ allowClear
128
+ maxTagCount={1}
129
+ onChange={onChange}
130
+ maxTagPlaceholder={(omittedValues) => (
131
+ <span className="tag-placeholder-count">
132
+ +{omittedValues.length}
133
+ </span>
134
+ )}
135
+ dropdownRender={() => (
136
+ <div className="dropdown-content">
137
+ <Search
138
+ placeholder={`Search ${caption}`}
139
+ onChange={(e) => handleSearch(e.target.value)}
140
+ />
141
+
142
+ <div className="dropdown-options-list">
143
+ {loading ? (
144
+ <Spin />
145
+ ) : (
146
+ displayOptions.map((item, idx) => (
147
+ <div
148
+ key={`${item}-${idx}`}
149
+ className="dropdown-option-item"
150
+ onClick={() => toggleValue(!safeValue.includes(item), item)}
151
+ style={{ cursor: 'pointer' }}
152
+ >
153
+ <Checkbox
154
+ checked={safeValue.includes(item)}
155
+ onClick={(e) => e.stopPropagation()} // Prevent double trigger when clicking the checkbox box specifically
156
+ onChange={(e) => toggleValue(e.target.checked, item)}
157
+ >
158
+ {item}
159
+ </Checkbox>
160
+ </div>
161
+ ))
162
+ )}
163
+ </div>
164
+
165
+ <div className="dropdown-footer">
166
+ <span className="dropdown-reset-button" onClick={handleReset}>
167
+ Reset
168
+ </span>
169
+ </div>
170
+ </div>
171
+ )}
172
+ />
173
+ );
174
+ }
@@ -0,0 +1,76 @@
1
+ // .advanced-search-select,
2
+ /* Base select width */
3
+ .advanced-search-select {
4
+ width: 160px;
5
+ // width: 100%;
6
+ }
7
+ .advanced-search-input {
8
+ width: 160px;
9
+ }
10
+ /* Active state (when value selected) */
11
+ .advanced-search-select.advanced-search-active .ant-select-selector {
12
+ border-color: #1677ff !important;
13
+ box-shadow: 0 0 0 2px rgba(22, 119, 255, 0.2) !important;
14
+ border-radius: 6px !important;
15
+ }
16
+
17
+ /* Input active state */
18
+ .advanced-search-input.advanced-search-active {
19
+ border-color: #1677ff !important;
20
+ box-shadow: 0 0 0 2px rgba(22, 119, 255, 0.2);
21
+ border-radius: 6px;
22
+ }
23
+ // .advanced-search-input {
24
+ // width: 250px;
25
+
26
+ // &.advanced-search-active {
27
+ // // Antd Select and Input don't share the same border-radius property,
28
+ // // so we apply a wrapper style that works for both.
29
+ // // For more specific control, you might need to target internal antd classes.
30
+ // border: 1px solid #91caff !important;
31
+ // box-shadow: 0 0 0 1px #91caff;
32
+ // border-radius: 6px;
33
+ // }
34
+
35
+ .tag-placeholder {
36
+ display: flex;
37
+ align-items: center;
38
+ gap: 8px;
39
+
40
+ .tag-placeholder-count {
41
+ background: #e6f0ff;
42
+ color: #1677ff;
43
+ border-radius: 10px;
44
+ padding: 0 8px;
45
+ font-weight: 600;
46
+ font-size: 12px;
47
+ }
48
+ }
49
+
50
+ .dropdown-content {
51
+ padding: 10px;
52
+
53
+ .dropdown-options-list {
54
+ max-height: 200px;
55
+ overflow-y: auto;
56
+ margin-top: 10px;
57
+
58
+ .dropdown-option-item {
59
+ margin-bottom: 6px;
60
+ }
61
+ }
62
+
63
+ .dropdown-footer {
64
+ display: flex;
65
+ justify-content: flex-end;
66
+ margin-top: 10px;
67
+ border-top: 1px solid #f0f0f0;
68
+ padding-top: 8px;
69
+
70
+ .dropdown-reset-button {
71
+ color: #1677ff;
72
+ cursor: pointer;
73
+ font-weight: 500;
74
+ }
75
+ }
76
+ }
@@ -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.21",
3
+ "version": "2.6.22",
4
4
  "description": "All the Core Components for you to start",
5
5
  "keywords": [
6
6
  "all in one"