ui-soxo-bootstrap-core 2.6.1-dev.1 → 2.6.1-dev.10

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 (51) hide show
  1. package/core/components/extra-info/extra-info-details.js +2 -2
  2. package/core/lib/Store.js +3 -3
  3. package/core/lib/components/global-header/global-header.js +2 -2
  4. package/core/lib/components/sidemenu/sidemenu.js +19 -13
  5. package/core/lib/elements/basic/country-phone-input/country-phone-input.js +14 -8
  6. package/core/lib/elements/basic/dragabble-wrapper/draggable-wrapper.js +1 -1
  7. package/core/lib/elements/basic/menu-tree/menu-tree.js +26 -13
  8. package/core/lib/models/forms/components/form-creator/form-creator.scss +5 -4
  9. package/core/lib/models/menus/components/menu-list/menu-list.js +424 -467
  10. package/core/lib/pages/change-password/change-password.js +17 -24
  11. package/core/lib/pages/change-password/change-password.scss +45 -48
  12. package/core/lib/pages/login/commnication-mode-selection.js +46 -0
  13. package/core/lib/pages/login/communication-mode-selection.scss +60 -0
  14. package/core/lib/pages/login/login.js +126 -22
  15. package/core/lib/pages/login/login.scss +229 -334
  16. package/core/lib/pages/login/reset-password.js +124 -0
  17. package/core/lib/pages/login/reset-password.scss +31 -0
  18. package/core/lib/pages/profile/themes.json +4 -4
  19. package/core/lib/utils/api/api.utils.js +30 -18
  20. package/core/lib/utils/common/common.utils.js +85 -0
  21. package/core/lib/utils/http/http.utils.js +1 -0
  22. package/core/lib/utils/index.js +4 -1
  23. package/core/models/base/base.js +7 -3
  24. package/core/models/core-scripts/core-scripts.js +9 -0
  25. package/core/models/doctor/components/doctor-add/doctor-add.js +9 -4
  26. package/core/models/menus/components/menu-add/menu-add.js +1 -1
  27. package/core/models/menus/components/menu-lists/menu-lists.js +5 -9
  28. package/core/models/menus/menus.js +21 -2
  29. package/core/models/roles/components/role-add/role-add.js +92 -59
  30. package/core/models/roles/components/role-list/role-list.js +1 -1
  31. package/core/models/staff/components/staff-add/staff-add.js +20 -32
  32. package/core/models/users/components/assign-role/assign-role.js +145 -50
  33. package/core/models/users/components/assign-role/assign-role.scss +209 -45
  34. package/core/models/users/components/assign-role/avatar-props.js +45 -0
  35. package/core/models/users/components/user-add/user-add.js +46 -55
  36. package/core/models/users/components/user-add/user-edit.js +25 -4
  37. package/core/models/users/users.js +16 -1
  38. package/core/modules/dashboard/components/dashboard-card/menu-dashboard-card.js +1 -1
  39. package/core/modules/reporting/components/reporting-dashboard/README.md +316 -0
  40. package/core/modules/reporting/components/reporting-dashboard/adavance-search/advance-search.js +120 -0
  41. package/core/modules/reporting/components/reporting-dashboard/display-columns/build-display-columns.js +75 -0
  42. package/core/modules/reporting/components/reporting-dashboard/display-columns/build-display-columns.test.js +74 -0
  43. package/core/modules/reporting/components/reporting-dashboard/display-columns/display-cell-renderer.js +252 -0
  44. package/core/modules/reporting/components/reporting-dashboard/display-columns/display-cell-renderer.test.js +126 -0
  45. package/core/modules/reporting/components/reporting-dashboard/reporting-dashboard.js +210 -395
  46. package/core/modules/steps/action-buttons.js +42 -44
  47. package/core/modules/steps/action-buttons.scss +35 -6
  48. package/core/modules/steps/steps.js +12 -10
  49. package/core/modules/steps/steps.scss +229 -31
  50. package/core/modules/steps/timeline.js +21 -19
  51. package/package.json +2 -1
@@ -0,0 +1,252 @@
1
+ import React from 'react';
2
+ import { Tag } from 'antd';
3
+ import * as Icons from '@ant-design/icons';
4
+ import { Link } from 'react-router-dom';
5
+
6
+ /**
7
+ * Utilities for rendering Reporting Dashboard display columns.
8
+ *
9
+ * Backward-compatibility contract for action columns:
10
+ * 1. `entry.field === 'action'` is always treated as legacy action behavior.
11
+ * 2. `entry.type === 'action' && entry.field !== 'action'` is treated as the new action layer.
12
+ * 3. All other entries follow existing non-action render logic.
13
+ */
14
+
15
+ /**
16
+ * @typedef {Object} ReplaceVariable
17
+ * @property {string} field Field name from row data used to replace `@field;` placeholders in `redirect_link`.
18
+ */
19
+
20
+ /**
21
+ * @typedef {Object} DisplayColumnEntry
22
+ * @property {string} [field] Source field name from row data.
23
+ * @property {string} [type] Supported values include `link`, `action`, `number`.
24
+ * @property {string} [title] Display title.
25
+ * @property {string} [tooltip] Optional tooltip text for table header.
26
+ * @property {string} [color] Static text color for default text rendering.
27
+ * @property {boolean} [enableColor] Enables color-based rendering using `record.color_code`.
28
+ * @property {string} [columnType] When `enableColor` is true, supports `tag` and `span`.
29
+ * @property {Object.<string, {icon: string, color: string, size: string}>} [displayIcons] Icon mapping keyed by row value.
30
+ * @property {string} [display_name_link] Legacy action link label fallback.
31
+ * @property {string} [label] New action link label fallback.
32
+ * @property {string} [redirect_link] Redirect template (example: `/path/@opb_id;`).
33
+ * @property {ReplaceVariable[]} [replace_variables] Placeholder replacement config for `redirect_link`.
34
+ * @property {string} [component] Custom component name for `field === 'custom'`.
35
+ * @property {{field: string, value: string}[]} [props] Mapping from record fields to custom component prop keys.
36
+ * @property {Object} [config] Static props passed to custom component.
37
+ * @property {(record: DisplayRecord) => React.ReactNode} [render] Optional custom render override.
38
+ */
39
+
40
+ /**
41
+ * @typedef {Object.<string, any>} DisplayRecord
42
+ */
43
+
44
+ /**
45
+ * Checks if the column entry is a legacy action configuration.
46
+ *
47
+ * @param {DisplayColumnEntry} entry
48
+ * @returns {boolean}
49
+ */
50
+ export function isLegacyActionEntry(entry = {}) {
51
+ return entry.field === 'action';
52
+ }
53
+
54
+ /**
55
+ * Checks if the column entry is a new action-type configuration.
56
+ *
57
+ * @param {DisplayColumnEntry} entry
58
+ * @returns {boolean}
59
+ */
60
+ export function isActionTypeEntry(entry = {}) {
61
+ return entry.type === 'action' && entry.field !== 'action';
62
+ }
63
+
64
+ /**
65
+ * Builds redirect link by replacing configured placeholders with row values.
66
+ *
67
+ * Placeholder format in `redirect_link` is `@field;`.
68
+ * Example:
69
+ * redirect_link: `/visit/@opb_id;/bill/@opno;`
70
+ * replace_variables: [{ field: 'opb_id' }, { field: 'opno' }]
71
+ * result: `/visit/123/bill/OP-100`
72
+ *
73
+ * @param {DisplayColumnEntry} entry
74
+ * @param {DisplayRecord} record
75
+ * @returns {string}
76
+ */
77
+ export function getRedirectLink(entry = {}, record = {}) {
78
+ let redirectLink = entry.redirect_link || '';
79
+
80
+ if (Array.isArray(entry.replace_variables)) {
81
+ entry.replace_variables.forEach((replacement) => {
82
+ const value = record[replacement.field] || '';
83
+ redirectLink = redirectLink.replace(new RegExp(`@${replacement.field};`, 'g'), value);
84
+ });
85
+ }
86
+
87
+ return redirectLink;
88
+ }
89
+
90
+ /**
91
+ * Resolves action label text with backward-compatible precedence.
92
+ *
93
+ * Legacy action (`field === 'action'`):
94
+ * - `entry.display_name_link`
95
+ * - `"View"`
96
+ *
97
+ * New action layer (`type === 'action' && field !== 'action'`):
98
+ * - `record[entry.field]`
99
+ * - `entry.label`
100
+ * - `entry.display_name_link`
101
+ * - `"View"`
102
+ *
103
+ * @param {DisplayColumnEntry} entry
104
+ * @param {DisplayRecord} record
105
+ * @returns {string}
106
+ */
107
+ export function getActionLabel(entry = {}, record = {}) {
108
+ // Legacy action path should stay unchanged.
109
+ if (isLegacyActionEntry(entry)) {
110
+ return entry.display_name_link ? entry.display_name_link : 'View';
111
+ }
112
+
113
+ const fieldValue = entry.field ? record[entry.field] : null;
114
+ if (fieldValue !== undefined && fieldValue !== null && fieldValue !== '') {
115
+ return fieldValue;
116
+ }
117
+
118
+ if (entry.label) {
119
+ return entry.label;
120
+ }
121
+
122
+ if (entry.display_name_link) {
123
+ return entry.display_name_link;
124
+ }
125
+
126
+ return 'View';
127
+ }
128
+
129
+ /**
130
+ * Renders configured custom component cells (`field === 'custom'`).
131
+ *
132
+ * Each `entry.props` item maps a record field to a prop name:
133
+ * `{ field: 'status_text', value: 'description' }` becomes
134
+ * `LoadedComponent({ description: record.status_text })`.
135
+ *
136
+ * @param {Object} root0
137
+ * @param {DisplayColumnEntry} root0.entry
138
+ * @param {DisplayRecord} root0.record
139
+ * @param {Object.<string, React.ComponentType<any>>} root0.CustomComponents
140
+ * @param {Function} root0.refresh
141
+ * @returns {React.ReactNode}
142
+ */
143
+ function renderCustomComponent({ entry, record, CustomComponents, refresh }) {
144
+ const componentName = entry.component;
145
+ const genericComponents = CustomComponents || {};
146
+
147
+ if (!componentName || !genericComponents[componentName]) {
148
+ return null;
149
+ }
150
+
151
+ const LoadedComponent = genericComponents[componentName];
152
+ const propValue = {};
153
+
154
+ if (Array.isArray(entry.props)) {
155
+ entry.props.forEach((values) => {
156
+ const valueCreation = record[values.field];
157
+ propValue[values.value] = valueCreation;
158
+ });
159
+ }
160
+
161
+ return (
162
+ <LoadedComponent
163
+ {...entry.config}
164
+ callback={() => {
165
+ refresh();
166
+ }}
167
+ {...record}
168
+ {...propValue}
169
+ />
170
+ );
171
+ }
172
+
173
+ /**
174
+ * Renders table cell content for a configured display column.
175
+ *
176
+ * Render priority:
177
+ * 1. `entry.render(record)` override
178
+ * 2. `type === 'link'`
179
+ * 3. action columns (legacy or new action layer)
180
+ * 4. custom component (`field === 'custom'`)
181
+ * 5. color tag/span via `record.color_code` + `enableColor`
182
+ * 6. dynamic icon mapping (`displayIcons`)
183
+ * 7. default text span
184
+ *
185
+ * @param {Object} root0
186
+ * @param {DisplayColumnEntry} root0.entry
187
+ * @param {DisplayRecord} root0.record
188
+ * @param {Object.<string, React.ComponentType<any>>} root0.CustomComponents
189
+ * @param {Function} root0.refresh
190
+ * @returns {React.ReactNode}
191
+ */
192
+ export function renderDisplayCell({ entry, record, CustomComponents, refresh }) {
193
+ let textColor = 'inherit';
194
+
195
+ if (entry.color) {
196
+ textColor = entry.color;
197
+ }
198
+
199
+ if (entry.render) {
200
+ return entry.render(record);
201
+ }
202
+
203
+ if (entry.type === 'link') {
204
+ if (record[entry.field]) {
205
+ return (
206
+ <a href={record[entry.field]} target="_blank" rel="noopener noreferrer">
207
+ View
208
+ </a>
209
+ );
210
+ }
211
+ return null;
212
+ }
213
+
214
+ if (isLegacyActionEntry(entry) || isActionTypeEntry(entry)) {
215
+ const redirectLink = getRedirectLink(entry, record);
216
+ return <Link to={`${redirectLink}`}>{getActionLabel(entry, record)}</Link>;
217
+ }
218
+
219
+ if (entry.field === 'custom') {
220
+ return renderCustomComponent({ entry, record, CustomComponents, refresh });
221
+ }
222
+
223
+ if (record.color_code && entry.enableColor) {
224
+ if (entry.columnType === 'tag') {
225
+ return <Tag color={record.color_code}>{record[entry.field]}</Tag>;
226
+ }
227
+ if (entry.columnType === 'span') {
228
+ return <span style={{ color: record.color_code, overflowWrap: 'break-word', WebkitLineClamp: 3 }}>{record[entry.field]}</span>;
229
+ }
230
+ }
231
+
232
+ if (entry.displayIcons) {
233
+ const fieldValue = record[entry.field]?.toString();
234
+ const displayConfig = entry.displayIcons[fieldValue];
235
+ if (displayConfig) {
236
+ const DynamicIcon = Icons[displayConfig.icon];
237
+ if (DynamicIcon) {
238
+ return (
239
+ <DynamicIcon
240
+ style={{
241
+ color: displayConfig.color,
242
+ fontSize: displayConfig.size,
243
+ }}
244
+ />
245
+ );
246
+ }
247
+ }
248
+ return null;
249
+ }
250
+
251
+ return <span style={{ color: textColor, whiteSpace: 'pre-wrap', overflowWrap: 'break-word' }}>{record[entry.field]}</span>;
252
+ }
@@ -0,0 +1,126 @@
1
+ import React from 'react';
2
+ import {
3
+ isLegacyActionEntry,
4
+ isActionTypeEntry,
5
+ getRedirectLink,
6
+ getActionLabel,
7
+ renderDisplayCell,
8
+ } from './display-cell-renderer';
9
+
10
+ describe('display-cell-renderer', () => {
11
+ test('identifies legacy and new action entries correctly', () => {
12
+ expect(isLegacyActionEntry({ field: 'action' })).toBe(true);
13
+ expect(isLegacyActionEntry({ field: 'action_text', type: 'action' })).toBe(false);
14
+
15
+ expect(isActionTypeEntry({ field: 'action_text', type: 'action' })).toBe(true);
16
+ expect(isActionTypeEntry({ field: 'action', type: 'action' })).toBe(false);
17
+ });
18
+
19
+ test('builds redirect link from replace variables', () => {
20
+ const link = getRedirectLink(
21
+ {
22
+ redirect_link: '/bill/@opb_id;/visit/@opno;',
23
+ replace_variables: [{ field: 'opb_id' }, { field: 'opno' }],
24
+ },
25
+ { opb_id: 10, opno: 'OP100' },
26
+ );
27
+
28
+ expect(link).toBe('/bill/10/visit/OP100');
29
+ });
30
+
31
+ test('keeps legacy action label behavior unchanged', () => {
32
+ const entry = {
33
+ field: 'action',
34
+ display_name_link: 'Open',
35
+ };
36
+
37
+ expect(getActionLabel(entry, { action: 'Dynamic Value' })).toBe('Open');
38
+ expect(getActionLabel({ field: 'action' }, { action: 'Dynamic Value' })).toBe('View');
39
+ });
40
+
41
+ test('resolves new action label with fallback precedence', () => {
42
+ expect(getActionLabel({ type: 'action', field: 'action_text' }, { action_text: 'Review' })).toBe('Review');
43
+ expect(getActionLabel({ type: 'action', field: 'action_text', label: 'Open' }, {})).toBe('Open');
44
+ expect(getActionLabel({ type: 'action', field: 'action_text', display_name_link: 'Inspect' }, {})).toBe('Inspect');
45
+ expect(getActionLabel({ type: 'action', field: 'action_text' }, {})).toBe('View');
46
+ });
47
+
48
+ test('renders link column as anchor', () => {
49
+ const element = renderDisplayCell({
50
+ entry: { type: 'link', field: 'report_url' },
51
+ record: { report_url: 'https://example.com/report' },
52
+ refresh: jest.fn(),
53
+ CustomComponents: {},
54
+ });
55
+
56
+ expect(element.type).toBe('a');
57
+ expect(element.props.href).toBe('https://example.com/report');
58
+ });
59
+
60
+ test('renders custom component with mapped props', () => {
61
+ const MockComponent = () => null;
62
+ const element = renderDisplayCell({
63
+ entry: {
64
+ field: 'custom',
65
+ component: 'MockComponent',
66
+ props: [{ field: 'status', value: 'description' }],
67
+ config: { sample: true },
68
+ },
69
+ record: { status: 'Pending', opb_id: 1 },
70
+ refresh: jest.fn(),
71
+ CustomComponents: { MockComponent },
72
+ });
73
+
74
+ expect(element.type).toBe(MockComponent);
75
+ expect(element.props.description).toBe('Pending');
76
+ expect(element.props.sample).toBe(true);
77
+ expect(element.props.opb_id).toBe(1);
78
+ });
79
+
80
+ test('renders styled tag/span/icon/text for non-action path', () => {
81
+ const tagElement = renderDisplayCell({
82
+ entry: { field: 'status', enableColor: true, columnType: 'tag' },
83
+ record: { color_code: 'green', status: 'Done' },
84
+ refresh: jest.fn(),
85
+ CustomComponents: {},
86
+ });
87
+ expect(tagElement.props.color).toBe('green');
88
+ expect(tagElement.props.children).toBe('Done');
89
+
90
+ const spanElement = renderDisplayCell({
91
+ entry: { field: 'status', enableColor: true, columnType: 'span' },
92
+ record: { color_code: '#ff0000', status: 'Hold' },
93
+ refresh: jest.fn(),
94
+ CustomComponents: {},
95
+ });
96
+ expect(spanElement.type).toBe('span');
97
+ expect(spanElement.props.style.color).toBe('#ff0000');
98
+
99
+ const iconElement = renderDisplayCell({
100
+ entry: {
101
+ field: 'state',
102
+ displayIcons: {
103
+ Pending: {
104
+ icon: 'ClockCircleOutlined',
105
+ color: 'orange',
106
+ size: '14px',
107
+ },
108
+ },
109
+ },
110
+ record: { state: 'Pending' },
111
+ refresh: jest.fn(),
112
+ CustomComponents: {},
113
+ });
114
+ expect(iconElement.props.style.color).toBe('orange');
115
+
116
+ const textElement = renderDisplayCell({
117
+ entry: { field: 'remarks', color: '#333' },
118
+ record: { remarks: 'All good' },
119
+ refresh: jest.fn(),
120
+ CustomComponents: {},
121
+ });
122
+ expect(textElement.type).toBe('span');
123
+ expect(textElement.props.children).toBe('All good');
124
+ });
125
+ });
126
+