ui-soxo-bootstrap-core 2.6.48 → 2.6.50-dev.0
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.
- package/core/components/index.js +2 -2
- package/core/components/landing-api/landing-api.js +29 -21
- package/core/components/license-management/license-alert.js +87 -87
- package/core/lib/components/global-header/global-header.js +16 -16
- package/core/lib/utils/http/http.utils.js +5 -1
- package/core/modules/reporting/components/reporting-dashboard/display-columns/display-cell-renderer.js +3 -47
- package/core/modules/reporting/components/reporting-dashboard/display-columns/display-cell-renderer.test.js +3 -23
- package/core/modules/reporting/components/reporting-dashboard/reporting-table.js +4 -4
- package/package.json +1 -1
package/core/components/index.js
CHANGED
|
@@ -7,8 +7,8 @@ import RootApplicationAPI from './root-application-api/root-application-api';
|
|
|
7
7
|
import { HomePageAPI } from '../modules';
|
|
8
8
|
|
|
9
9
|
import { ExternalWindow } from './external-window/external-window';
|
|
10
|
-
|
|
10
|
+
import LicenseAlert from './license-management/license-alert';
|
|
11
11
|
|
|
12
12
|
export { LandingAPI, RootApplicationAPI, ExtraInfoDetail, HomePageAPI, ExternalWindow,
|
|
13
|
-
|
|
13
|
+
LicenseAlert
|
|
14
14
|
};
|
|
@@ -58,28 +58,36 @@ export default function LandingApi({ history, CustomComponents, CustomModels, ap
|
|
|
58
58
|
|
|
59
59
|
const [meta, setMeta] = useState({});
|
|
60
60
|
const [loadingMessage, setLoadingMessage] = useState('');
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
const [licenseData, setLicenseData] = useState(null);
|
|
62
|
+
const [licAlert, setLicAlert] = useState(false);
|
|
63
63
|
|
|
64
64
|
// const [reports, setReports] = useState([]);
|
|
65
65
|
|
|
66
66
|
var config = {};
|
|
67
67
|
|
|
68
|
-
//
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
//
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
68
|
+
// Variable decides whether license-summary fetching/alerting runs at all
|
|
69
|
+
let disableLicense = false;
|
|
70
|
+
|
|
71
|
+
if (process.env.REACT_APP_DISABLE_LICENSE) {
|
|
72
|
+
disableLicense = JSON.parse(process.env.REACT_APP_DISABLE_LICENSE);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
//fetch license summary
|
|
76
|
+
const fetchSummary = async () => {
|
|
77
|
+
if (disableLicense) return;
|
|
78
|
+
try {
|
|
79
|
+
const res = await MenusAPI.getSummary();
|
|
80
|
+
if (res?.data) {
|
|
81
|
+
setLicenseData(res?.data);
|
|
82
|
+
setLicAlert(true);
|
|
83
|
+
} else {
|
|
84
|
+
setLicenseData(null);
|
|
85
|
+
setLicAlert(false);
|
|
86
|
+
}
|
|
87
|
+
} catch (err) {
|
|
88
|
+
console.error(err);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
83
91
|
|
|
84
92
|
// Variable decides the control of homepage
|
|
85
93
|
// #TODO This is a temporary fix - Homemage
|
|
@@ -285,7 +293,7 @@ export default function LandingApi({ history, CustomComponents, CustomModels, ap
|
|
|
285
293
|
|
|
286
294
|
await loadMenus(report);
|
|
287
295
|
// fetch license summary
|
|
288
|
-
|
|
296
|
+
fetchSummary();
|
|
289
297
|
}
|
|
290
298
|
|
|
291
299
|
// const keyMap = {
|
|
@@ -307,7 +315,7 @@ export default function LandingApi({ history, CustomComponents, CustomModels, ap
|
|
|
307
315
|
setLoader(true);
|
|
308
316
|
|
|
309
317
|
// setReports(report)
|
|
310
|
-
|
|
318
|
+
fetchSummary();
|
|
311
319
|
const result = await MenusAPI.getMenus(user);
|
|
312
320
|
|
|
313
321
|
// console.log(result);
|
|
@@ -437,8 +445,8 @@ export default function LandingApi({ history, CustomComponents, CustomModels, ap
|
|
|
437
445
|
modules={allModules}
|
|
438
446
|
user={user}
|
|
439
447
|
history={history}
|
|
440
|
-
|
|
441
|
-
|
|
448
|
+
licenseData={licenseData}
|
|
449
|
+
licAlert={licAlert}
|
|
442
450
|
>
|
|
443
451
|
{loader ? (
|
|
444
452
|
<Card className="skeleton-card">
|
|
@@ -1,97 +1,97 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { Alert } from 'antd';
|
|
2
|
+
import React, { useState, useEffect } from 'react';
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
//
|
|
6
|
-
|
|
7
|
-
//
|
|
8
|
-
|
|
9
|
-
//
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
4
|
+
export default function LicenseAlert({ data }) {
|
|
5
|
+
// setting visibility of alert based on license status
|
|
6
|
+
const [visible, setVisible] = useState(true);
|
|
7
|
+
// resolve alert configuration based on license data
|
|
8
|
+
const alertConfig = resolveLicenseAlert(data);
|
|
9
|
+
// auto-hide alert after 10 seconds or when data changes
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
if (alertConfig) {
|
|
12
|
+
setVisible(true);
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
const timer = setTimeout(() => {
|
|
15
|
+
setVisible(false);
|
|
16
|
+
}, 10000); // 10 seconds
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
//
|
|
24
|
-
|
|
18
|
+
return () => {
|
|
19
|
+
clearTimeout(timer);
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
}, [data]);
|
|
23
|
+
// if no alert configuration or not visible, render nothing
|
|
24
|
+
if (!alertConfig || !visible) return null;
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
//
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
//
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
//
|
|
42
|
-
|
|
26
|
+
return (
|
|
27
|
+
// render the alert with appropriate type, message, and description
|
|
28
|
+
<Alert
|
|
29
|
+
type={alertConfig.type}
|
|
30
|
+
message={alertConfig.message}
|
|
31
|
+
description={alertConfig.description}
|
|
32
|
+
showIcon
|
|
33
|
+
closable
|
|
34
|
+
onClose={() => setVisible(false)}
|
|
35
|
+
/>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
// function to determine alert configuration based on license data
|
|
39
|
+
function resolveLicenseAlert(data) {
|
|
40
|
+
if (!data) return null;
|
|
41
|
+
// destructure relevant fields from license data
|
|
42
|
+
const { status, expiresInDays, isExpiringSoon, gracePeriod } = data;
|
|
43
43
|
|
|
44
|
-
//
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
44
|
+
// ===== NOT INSTALLED =====
|
|
45
|
+
if (status === 'NOT_INSTALLED') {
|
|
46
|
+
return {
|
|
47
|
+
type: 'error',
|
|
48
|
+
message: 'License not found',
|
|
49
|
+
description: 'Please install a valid license to continue.',
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
52
|
|
|
53
|
-
//
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
53
|
+
// ===== GRACE PERIOD =====
|
|
54
|
+
if (gracePeriod) {
|
|
55
|
+
return {
|
|
56
|
+
type: 'warning',
|
|
57
|
+
message: 'Grace period mode',
|
|
58
|
+
description: 'License expired. Running in read-only mode.',
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
61
|
|
|
62
|
-
//
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
//
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
62
|
+
// ===== EXPIRING SOON =====
|
|
63
|
+
if (status === 'ACTIVE' && isExpiringSoon) {
|
|
64
|
+
let descriptionText = '';
|
|
65
|
+
// customize message based on how soon the license is expiring
|
|
66
|
+
if (expiresInDays === 0) {
|
|
67
|
+
descriptionText = 'Your license will expire today. Please renew immediately.';
|
|
68
|
+
} else {
|
|
69
|
+
descriptionText = `Your license will expire in ${expiresInDays} days. Please plan for renewal.`;
|
|
70
|
+
}
|
|
71
71
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
72
|
+
return {
|
|
73
|
+
type: 'warning',
|
|
74
|
+
message: 'License expiring soon',
|
|
75
|
+
description: descriptionText,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
78
|
|
|
79
|
-
//
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
//
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
79
|
+
// ===== NOT INSTALLED =====
|
|
80
|
+
if (status === 'NOT_INSTALLED') {
|
|
81
|
+
return {
|
|
82
|
+
type: 'error',
|
|
83
|
+
message: 'License not found',
|
|
84
|
+
description: 'Please install a valid license to continue.',
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
// =====EXPIRED=====
|
|
88
|
+
if (status === 'EXPIRED') {
|
|
89
|
+
return {
|
|
90
|
+
type: 'error',
|
|
91
|
+
message: 'License expired',
|
|
92
|
+
description: 'Your license has expired. Please renew or install a new license.',
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
95
|
|
|
96
|
-
|
|
97
|
-
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
@@ -20,12 +20,12 @@ import LanguageSwitcher from '../language-switcher/language-switcher';
|
|
|
20
20
|
import { useTranslation } from 'react-i18next';
|
|
21
21
|
import SettingsUtil from '../../../utils/settings.utils';
|
|
22
22
|
import SpotlightSearch from '../spotlight-search/spotlight-search.component';
|
|
23
|
-
|
|
23
|
+
import LicenseAlert from '../../../components/license-management/license-alert';
|
|
24
24
|
|
|
25
25
|
const { Title } = Typography;
|
|
26
26
|
function GlobalHeaderContent({ loading, appSettings, children, isConnected, history, modules = [], sidemenu = [], reload, meta = {},
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
licAlert,
|
|
28
|
+
licenseData,
|
|
29
29
|
...props }) {
|
|
30
30
|
let location = useLocation();
|
|
31
31
|
// let location = {};
|
|
@@ -244,19 +244,19 @@ function GlobalHeaderContent({ loading, appSettings, children, isConnected, hist
|
|
|
244
244
|
|
|
245
245
|
|
|
246
246
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
)}
|
|
247
|
+
{licAlert && licenseData && (
|
|
248
|
+
<div
|
|
249
|
+
style={{
|
|
250
|
+
top: 0,
|
|
251
|
+
marginTop: '3rem',
|
|
252
|
+
right: '2%',
|
|
253
|
+
position: 'absolute',
|
|
254
|
+
zIndex: 1008,
|
|
255
|
+
}}
|
|
256
|
+
>
|
|
257
|
+
<LicenseAlert data={licenseData} />
|
|
258
|
+
</div>
|
|
259
|
+
)}
|
|
260
260
|
</>
|
|
261
261
|
);
|
|
262
262
|
}
|
|
@@ -14,6 +14,10 @@ import FirebaseUtils from '../firebase.utils';
|
|
|
14
14
|
import { Location } from '../location/location.utils';
|
|
15
15
|
import { getRefreshToken, queueRequest, refreshAccessToken } from './auth.helper';
|
|
16
16
|
|
|
17
|
+
const isLicenseDisabled = process.env.REACT_APP_DISABLE_LICENSE
|
|
18
|
+
? JSON.parse(process.env.REACT_APP_DISABLE_LICENSE)
|
|
19
|
+
: false;
|
|
20
|
+
|
|
17
21
|
let headers = {
|
|
18
22
|
// 'Accept': 'application/json',
|
|
19
23
|
'Content-Type': 'application/json',
|
|
@@ -128,7 +132,7 @@ export async function ApiCall({ url, formBody, method, settings, ...props }) {
|
|
|
128
132
|
const res = await fetch(baseUrl + url, payload);
|
|
129
133
|
const refreshToken = getRefreshToken();
|
|
130
134
|
|
|
131
|
-
if (res.status === 403) {
|
|
135
|
+
if (res.status === 403 && !isLicenseDisabled) {
|
|
132
136
|
message.warn('Your license has expired. Please contact support.');
|
|
133
137
|
}
|
|
134
138
|
|
|
@@ -90,41 +90,6 @@ export function getRedirectLink(entry = {}, record = {}, CustomComponents = {})
|
|
|
90
90
|
return redirectLink;
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
/**
|
|
94
|
-
* Resolves the PDF/file location for file-based action entries.
|
|
95
|
-
*
|
|
96
|
-
* Supports either:
|
|
97
|
-
* - `entry.file_location` as a direct URL/path
|
|
98
|
-
* - `entry.file_location` as a record field name
|
|
99
|
-
*
|
|
100
|
-
* @param {DisplayColumnEntry} entry
|
|
101
|
-
* @param {DisplayRecord} record
|
|
102
|
-
* @returns {string}
|
|
103
|
-
*/
|
|
104
|
-
export function getFileLocation(entry = {}, record = {}) {
|
|
105
|
-
if (entry.file_location && typeof entry.file_location === 'string' && record[entry.file_location]) {
|
|
106
|
-
return record[entry.file_location];
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
return record.file_location || entry.file_location || '';
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Resolves a value from the row when a config field points to a record key,
|
|
114
|
-
* otherwise returns the provided literal value.
|
|
115
|
-
*
|
|
116
|
-
* @param {*} value
|
|
117
|
-
* @param {DisplayRecord} record
|
|
118
|
-
* @returns {*}
|
|
119
|
-
*/
|
|
120
|
-
function resolveRecordValue(value, record = {}) {
|
|
121
|
-
if (typeof value === 'string' && Object.prototype.hasOwnProperty.call(record, value)) {
|
|
122
|
-
return record[value];
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
return value;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
93
|
/**
|
|
129
94
|
* Resolves action label text with backward-compatible precedence.
|
|
130
95
|
*
|
|
@@ -219,26 +184,17 @@ function renderCustomComponent({ entry, record, CustomComponents, refresh }) {
|
|
|
219
184
|
*/
|
|
220
185
|
function FileActionLink({ entry, record, CustomComponents }) {
|
|
221
186
|
const [isPdfVisible, setIsPdfVisible] = useState(false);
|
|
222
|
-
const fileUrl = getFileLocation(entry, record);
|
|
223
187
|
const actionLabel = getActionLabel(entry, record);
|
|
224
188
|
const FileLoaderComponent = CustomComponents?.FileLoader;
|
|
225
189
|
|
|
226
|
-
|
|
227
|
-
return null;
|
|
228
|
-
}
|
|
190
|
+
|
|
229
191
|
|
|
230
192
|
const fileLoaderProps = {
|
|
231
|
-
url:
|
|
232
|
-
type: resolveRecordValue(entry.file_type, record) || 'pdf',
|
|
233
|
-
defaultScale: resolveRecordValue(entry.default_scale, record),
|
|
234
|
-
viewerType: resolveRecordValue(entry.viewer_type, record),
|
|
193
|
+
url: record?.file_location + record?.file_name,
|
|
235
194
|
config: {
|
|
236
|
-
requireLinuxPath: resolveRecordValue(entry.require_linux_path, record),
|
|
237
|
-
replaceBranch: resolveRecordValue(entry.replace_branch, record),
|
|
238
195
|
...(entry.file_loader_config || {}),
|
|
239
196
|
},
|
|
240
|
-
|
|
241
|
-
record,
|
|
197
|
+
type:record?.Native_filetype,
|
|
242
198
|
};
|
|
243
199
|
|
|
244
200
|
return (
|
|
@@ -3,7 +3,6 @@ import {
|
|
|
3
3
|
isLegacyActionEntry,
|
|
4
4
|
isActionTypeEntry,
|
|
5
5
|
getRedirectLink,
|
|
6
|
-
getFileLocation,
|
|
7
6
|
getActionLabel,
|
|
8
7
|
renderDisplayCell,
|
|
9
8
|
} from './display-cell-renderer';
|
|
@@ -29,13 +28,6 @@ describe('display-cell-renderer', () => {
|
|
|
29
28
|
expect(link).toBe('/bill/10/visit/OP100');
|
|
30
29
|
});
|
|
31
30
|
|
|
32
|
-
test('resolves file location from direct config value or record field', () => {
|
|
33
|
-
expect(getFileLocation({ file_location: 'https://example.com/report.pdf' }, {})).toBe('https://example.com/report.pdf');
|
|
34
|
-
expect(getFileLocation({ file_location: 'document_url' }, { document_url: 'https://example.com/record.pdf' })).toBe(
|
|
35
|
-
'https://example.com/record.pdf',
|
|
36
|
-
);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
31
|
test('keeps legacy action label behavior unchanged', () => {
|
|
40
32
|
const entry = {
|
|
41
33
|
field: 'action',
|
|
@@ -111,24 +103,15 @@ describe('display-cell-renderer', () => {
|
|
|
111
103
|
type: 'action',
|
|
112
104
|
field: 'action_text',
|
|
113
105
|
redirect_link_type: 'file',
|
|
114
|
-
file_location: 'document_path',
|
|
115
|
-
file_type: 'document_type',
|
|
116
|
-
default_scale: 'zoom_level',
|
|
117
|
-
viewer_type: 'inline_viewer',
|
|
118
|
-
require_linux_path: 'needs_linux_path',
|
|
119
|
-
replace_branch: 'should_replace_branch',
|
|
120
106
|
file_loader_config: {
|
|
121
107
|
sample: true,
|
|
122
108
|
},
|
|
123
109
|
},
|
|
124
110
|
record: {
|
|
125
111
|
action_text: 'Preview',
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
inline_viewer: true,
|
|
130
|
-
needs_linux_path: true,
|
|
131
|
-
should_replace_branch: false,
|
|
112
|
+
file_location: '\\\\server\\reports\\',
|
|
113
|
+
file_name: 'doc.pdf',
|
|
114
|
+
Native_filetype: 'pdf',
|
|
132
115
|
},
|
|
133
116
|
refresh: jest.fn(),
|
|
134
117
|
CustomComponents: { FileUpload },
|
|
@@ -140,14 +123,11 @@ describe('display-cell-renderer', () => {
|
|
|
140
123
|
expect(modalContent.type).toBe(FileUpload);
|
|
141
124
|
expect(modalContent.props.url).toBe('\\\\server\\reports\\doc.pdf');
|
|
142
125
|
expect(modalContent.props.type).toBe('pdf');
|
|
143
|
-
expect(modalContent.props.defaultScale).toBe(1.25);
|
|
144
|
-
expect(modalContent.props.viewerType).toBe(true);
|
|
145
126
|
expect(modalContent.props.config).toEqual({
|
|
146
127
|
requireLinuxPath: true,
|
|
147
128
|
replaceBranch: false,
|
|
148
129
|
sample: true,
|
|
149
130
|
});
|
|
150
|
-
expect(modalContent.props.record.document_path).toBe('\\\\server\\reports\\doc.pdf');
|
|
151
131
|
});
|
|
152
132
|
|
|
153
133
|
test('renders styled tag/span/icon/text for non-action path', () => {
|
|
@@ -474,7 +474,7 @@ export default function ReportingTable({
|
|
|
474
474
|
dataSource={filtered}
|
|
475
475
|
columns={cols}
|
|
476
476
|
sticky
|
|
477
|
-
pagination={
|
|
477
|
+
pagination={true}
|
|
478
478
|
summary={(pageData) => {
|
|
479
479
|
const summaryCols = columns.filter((col) => col.enable_summary);
|
|
480
480
|
if (!summaryCols.length) return null;
|
|
@@ -505,14 +505,14 @@ export default function ReportingTable({
|
|
|
505
505
|
/>
|
|
506
506
|
)}
|
|
507
507
|
<div style={{ display: 'flex', justifyContent: 'flex-end', marginTop: 8 }}>
|
|
508
|
-
<Pagination
|
|
508
|
+
{/* <Pagination
|
|
509
509
|
showSizeChanger
|
|
510
510
|
current={pagination?.current}
|
|
511
511
|
pageSize={pagination?.pageSize}
|
|
512
512
|
total={pagination?.total}
|
|
513
513
|
pageSizeOptions={[20, 30, 50, 100]}
|
|
514
|
-
onChange={(page, pageSize) => handlePaginationInternal({ current: page, pageSize })}
|
|
515
|
-
/>
|
|
514
|
+
onChange={(page, pageSize) => handlePaginationInternal({ current: page, pageSize })} */}
|
|
515
|
+
{/* /> */}
|
|
516
516
|
</div>
|
|
517
517
|
<p className="size-hint">{patients ? patients.length : 0} records.</p>
|
|
518
518
|
</Card>
|