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
@@ -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
+ }
@@ -0,0 +1,90 @@
1
+ import React from 'react';
2
+ import { Tooltip } from 'antd';
3
+ import { renderDisplayCell } from './display-cell-renderer';
4
+
5
+ /**
6
+ * Resolves export value definition for a configured display column.
7
+ *
8
+ * @param {Object} entry
9
+ * @param {Object} record
10
+ * @returns {*}
11
+ */
12
+ function getExportDefinition(entry, record) {
13
+ if (entry.field === 'custom') {
14
+ const description = entry.props?.find((p) => p.value === 'description')?.field;
15
+ return description && record[description] ? record[description] : null;
16
+ }
17
+
18
+ return record[entry.field];
19
+ }
20
+
21
+ /**
22
+ * Builds Ant Design table columns from display column configuration.
23
+ *
24
+ * @param {Object} root0
25
+ * @param {Array} root0.columns
26
+ * @param {Array} root0.patients
27
+ * @param {boolean} root0.isFixedIndex
28
+ * @param {Object.<string, React.ComponentType<any>>} root0.CustomComponents
29
+ * @param {Function} root0.refresh
30
+ * @param {Object} [root0.otherDetails={}] - Optional details from the report configuration.
31
+ * @param {boolean} [root0.otherDetails.isFilterEnabled] - Fallback to enable filtering on all columns.
32
+ * @param {boolean} [root.otherDetails.isSortingEnabled] - Fallback to enable sorting on all columns.
33
+ * @param {boolean} [root0.otherDetails.isHeaderWrapEnabled] - Fallback to enable header text wrapping for all columns.
34
+ * @returns {Array}
35
+ */
36
+ export default function buildDisplayColumns({ columns = [], patients = [], isFixedIndex, CustomComponents, refresh, otherDetails = {} }) {
37
+ const displayColumns = [
38
+ {
39
+ title: '#',
40
+ dataIndex: 'index',
41
+ render: (value, item, index) => index + 1,
42
+ key: 'ColumnIndex',
43
+ fixed: isFixedIndex ? 'left' : null,
44
+ width: 2,
45
+ },
46
+ ];
47
+
48
+ columns.forEach((entry, index) => {
49
+ const isFilterEnabled = entry.isFilterEnabled || otherDetails?.isFilterEnabled;
50
+ const isSortingEnabled = entry.isSortingEnabled || otherDetails?.isSortingEnabled;
51
+ const isHeaderWrapEnabled = otherDetails?.isHeaderWrapEnabled;
52
+
53
+ const titleStyle = isHeaderWrapEnabled ? { whiteSpace: 'pre-wrap', overflowWrap: 'break-word' } : {};
54
+ displayColumns.push({
55
+ render: (record) =>
56
+ renderDisplayCell({
57
+ entry,
58
+ record,
59
+ CustomComponents,
60
+ refresh,
61
+ }),
62
+ field: entry.field,
63
+ title: (
64
+ <Tooltip
65
+ title={entry.tooltip || entry.title}
66
+ overlayInnerStyle={{
67
+ whiteSpace: 'normal',
68
+ overflowWrap: 'break-word',
69
+ }}
70
+ >
71
+ <span style={titleStyle}>{entry.title}</span>
72
+ </Tooltip>
73
+ ),
74
+ key: entry.field || `display_column_${index}`,
75
+ width: entry.width ? parseInt(entry.width, 10) : 160,
76
+ fixed: entry.isFixedColumn ? entry.isFixedColumn : null,
77
+ filters:
78
+ isFilterEnabled && Array.isArray(patients)
79
+ ? [...new Set(patients.map((item) => item[entry.field]).filter(Boolean))].map((value) => ({ text: value, value }))
80
+ : null,
81
+ onFilter: isFilterEnabled ? (value, record) => record[entry.field] === value : null,
82
+ sorter: isSortingEnabled ? (a, b) => String(a[entry.field]).localeCompare(String(b[entry.field])) : null,
83
+ filterSearch: isFilterEnabled ? isFilterEnabled : false,
84
+ exportDefinition: (record) => getExportDefinition(entry, record),
85
+ align: entry.type === 'number' ? 'right' : 'left',
86
+ });
87
+ });
88
+
89
+ return displayColumns;
90
+ }
@@ -0,0 +1,74 @@
1
+ import React from 'react';
2
+ import buildDisplayColumns from './build-display-columns';
3
+
4
+ describe('build-display-columns', () => {
5
+ test('creates index column plus configured columns', () => {
6
+ const columns = buildDisplayColumns({
7
+ columns: [{ title: 'OP NO', field: 'opno' }],
8
+ patients: [{ opno: 'OP-1' }],
9
+ isFixedIndex: true,
10
+ CustomComponents: {},
11
+ refresh: jest.fn(),
12
+ });
13
+
14
+ expect(columns.length).toBe(2);
15
+ expect(columns[0].key).toBe('ColumnIndex');
16
+ expect(columns[0].fixed).toBe('left');
17
+ expect(columns[1].field).toBe('opno');
18
+ });
19
+
20
+ test('preserves legacy action behavior and supports new action dynamic label', () => {
21
+ const refresh = jest.fn();
22
+ const columns = buildDisplayColumns({
23
+ columns: [
24
+ {
25
+ title: 'Legacy Action',
26
+ field: 'action',
27
+ redirect_link: '/legacy/@opb_id;',
28
+ replace_variables: [{ field: 'opb_id' }],
29
+ display_name_link: 'Open',
30
+ },
31
+ {
32
+ title: 'New Action',
33
+ type: 'action',
34
+ field: 'action_text',
35
+ redirect_link: '/new/@opb_id;',
36
+ replace_variables: [{ field: 'opb_id' }],
37
+ label: 'Fallback',
38
+ },
39
+ ],
40
+ patients: [{ opb_id: 10, action: 'Should not replace legacy label', action_text: 'Review' }],
41
+ isFixedIndex: false,
42
+ CustomComponents: {},
43
+ refresh,
44
+ });
45
+
46
+ const row = { opb_id: 10, action: 'Should not replace legacy label', action_text: 'Review' };
47
+
48
+ const legacyElement = columns[1].render(row);
49
+ const newLayerElement = columns[2].render(row);
50
+
51
+ expect(legacyElement.props.children).toBe('Open');
52
+ expect(newLayerElement.props.children).toBe('Review');
53
+ });
54
+
55
+ test('custom exportDefinition returns mapped description field value', () => {
56
+ const columns = buildDisplayColumns({
57
+ columns: [
58
+ {
59
+ title: 'Info',
60
+ field: 'custom',
61
+ props: [{ field: 'status_text', value: 'description' }],
62
+ },
63
+ ],
64
+ patients: [],
65
+ isFixedIndex: false,
66
+ CustomComponents: {},
67
+ refresh: jest.fn(),
68
+ });
69
+
70
+ const exportValue = columns[1].exportDefinition({ status_text: 'Ready' });
71
+ expect(exportValue).toBe('Ready');
72
+ });
73
+ });
74
+