ui-soxo-bootstrap-core 2.4.24 → 2.4.25-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.
- package/.github/workflows/npm-publish.yml +37 -15
- package/core/components/extra-info/extra-info-details.js +109 -126
- package/core/components/landing-api/landing-api.js +22 -30
- package/core/lib/Store.js +20 -18
- package/core/lib/components/index.js +4 -1
- package/core/lib/components/sidemenu/sidemenu.js +153 -256
- package/core/lib/components/sidemenu/sidemenu.scss +39 -26
- package/core/lib/elements/basic/rangepicker/rangepicker.js +118 -29
- package/core/lib/elements/basic/switch/switch.js +34 -24
- package/core/lib/hooks/index.js +2 -12
- package/core/lib/hooks/use-otp-timer.js +99 -0
- package/core/lib/pages/login/login.js +255 -139
- package/core/lib/pages/login/login.scss +140 -32
- package/core/models/dashboard/dashboard.js +14 -0
- package/core/models/doctor/components/doctor-add/doctor-add.js +403 -0
- package/core/models/doctor/components/doctor-add/doctor-add.scss +32 -0
- package/core/models/menus/components/menu-add/menu-add.js +230 -268
- package/core/models/menus/components/menu-lists/menu-lists.js +126 -89
- package/core/models/menus/components/menu-lists/menu-lists.scss +9 -0
- package/core/models/menus/menus.js +247 -267
- package/core/models/roles/components/role-add/role-add.js +269 -227
- package/core/models/roles/components/role-list/role-list.js +8 -6
- package/core/models/roles/roles.js +182 -174
- package/core/models/staff/components/staff-add/staff-add.js +352 -0
- package/core/models/staff/components/staff-add/staff-add.scss +0 -0
- package/core/models/users/components/user-add/user-add.js +686 -364
- package/core/models/users/components/user-add/user-edit.js +90 -0
- package/core/models/users/users.js +318 -165
- package/core/modules/index.js +5 -8
- package/core/modules/reporting/components/index.js +5 -0
- package/core/modules/reporting/components/reporting-dashboard/reporting-dashboard.js +65 -2
- package/core/modules/steps/action-buttons.js +79 -0
- package/core/modules/steps/steps.js +553 -0
- package/core/modules/steps/steps.scss +158 -0
- package/core/modules/steps/timeline.js +49 -0
- package/package.json +2 -2
|
@@ -43,8 +43,8 @@
|
|
|
43
43
|
width: 17%;
|
|
44
44
|
background: #fff;
|
|
45
45
|
// border-bottom: 1.5px solid #24aeb8;
|
|
46
|
-
&.close{
|
|
47
|
-
width:6% !important
|
|
46
|
+
&.close {
|
|
47
|
+
width: 6% !important;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
.logo-wrapper {
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
|
|
71
71
|
@media only screen and (max-width: 500px) {
|
|
72
72
|
margin-right: 0px;
|
|
73
|
-
// margin: 10px;
|
|
73
|
+
// margin: 10px;
|
|
74
74
|
width: 150px;
|
|
75
75
|
}
|
|
76
76
|
|
|
@@ -160,7 +160,7 @@
|
|
|
160
160
|
|
|
161
161
|
/* Pseudo-element (as before) */
|
|
162
162
|
.menu-collapsed::after {
|
|
163
|
-
content:
|
|
163
|
+
content: '';
|
|
164
164
|
position: absolute;
|
|
165
165
|
top: -100%;
|
|
166
166
|
left: 0;
|
|
@@ -175,26 +175,26 @@
|
|
|
175
175
|
// background-color: #E0EBFF;
|
|
176
176
|
// transform: scale(0.95); /* Apply scale effect on hover */
|
|
177
177
|
}
|
|
178
|
-
/* Style selected item */
|
|
179
|
-
.ant-menu{
|
|
180
|
-
|
|
181
|
-
}
|
|
182
|
-
// .ant-menu-inline .ant-menu-item::after{
|
|
183
|
-
// border-right: none;
|
|
184
|
-
// }
|
|
185
|
-
// .ant-menu-item-selected {
|
|
186
|
-
// background-color: var(--selected-bg-color) !important;
|
|
187
|
-
// color: var(--selected-text-color) !important;
|
|
188
|
-
// border-radius: 12px;
|
|
189
|
-
// // font-weight: 600;
|
|
190
|
-
// // margin: 4px 8px;
|
|
191
|
-
// }
|
|
192
|
-
|
|
193
|
-
// /* Optional: remove hover effects */
|
|
194
|
-
// .ant-menu-item:hover {
|
|
195
|
-
// background-color: transparent !important;
|
|
196
|
-
// color: transparent !important;
|
|
197
|
-
// }
|
|
178
|
+
/* Style selected item */
|
|
179
|
+
.ant-menu {
|
|
180
|
+
background-color: transparent !important;
|
|
181
|
+
}
|
|
182
|
+
// .ant-menu-inline .ant-menu-item::after{
|
|
183
|
+
// border-right: none;
|
|
184
|
+
// }
|
|
185
|
+
// .ant-menu-item-selected {
|
|
186
|
+
// background-color: var(--selected-bg-color) !important;
|
|
187
|
+
// color: var(--selected-text-color) !important;
|
|
188
|
+
// border-radius: 12px;
|
|
189
|
+
// // font-weight: 600;
|
|
190
|
+
// // margin: 4px 8px;
|
|
191
|
+
// }
|
|
192
|
+
|
|
193
|
+
// /* Optional: remove hover effects */
|
|
194
|
+
// .ant-menu-item:hover {
|
|
195
|
+
// background-color: transparent !important;
|
|
196
|
+
// color: transparent !important;
|
|
197
|
+
// }
|
|
198
198
|
|
|
199
199
|
/* River flow effect */
|
|
200
200
|
.menu-collapsed:hover::after {
|
|
@@ -248,9 +248,7 @@
|
|
|
248
248
|
padding: 0px;
|
|
249
249
|
// width: 100% !important;
|
|
250
250
|
// padding: 10px 16px;
|
|
251
|
-
|
|
252
251
|
}
|
|
253
|
-
|
|
254
252
|
|
|
255
253
|
.menu-item {
|
|
256
254
|
width: 100% !important;
|
|
@@ -262,3 +260,18 @@
|
|
|
262
260
|
}
|
|
263
261
|
}
|
|
264
262
|
}
|
|
263
|
+
|
|
264
|
+
.ant-menu-item:hover {
|
|
265
|
+
background-color: #e6f7ff !important;
|
|
266
|
+
color: #1677ff !important;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/* Tooltip styling if needed */
|
|
270
|
+
.ant-tooltip-inner {
|
|
271
|
+
max-width: 200px;
|
|
272
|
+
white-space: nowrap;
|
|
273
|
+
overflow: hidden;
|
|
274
|
+
text-overflow: ellipsis;
|
|
275
|
+
background-color: #ffffff !important;
|
|
276
|
+
color: #000000 !important;
|
|
277
|
+
}
|
|
@@ -1,46 +1,135 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @file rangepicker.js
|
|
3
|
+
* @description A reusable, enhanced Ant Design RangePicker component.
|
|
4
|
+
*
|
|
5
|
+
* Features:
|
|
6
|
+
* - Manual selections require "Apply" button confirmation
|
|
7
|
+
* - Preset ranges apply immediately without confirmation
|
|
8
|
+
* - Optimized state management and event handling
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { useCallback, useRef, useState } from 'react';
|
|
12
|
+
import { DatePicker, Space } from 'antd';
|
|
2
13
|
import moment from 'moment-timezone';
|
|
3
14
|
import PropTypes from 'prop-types';
|
|
15
|
+
import { useTranslation } from 'react-i18next';
|
|
16
|
+
import Button from '../button/button';
|
|
4
17
|
import './rangepicker.scss';
|
|
5
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Enhanced RangePicker with Apply/Cancel controls for manual selections.
|
|
21
|
+
* @param {object} props
|
|
22
|
+
* @param {moment.Moment[]} props.value - Current applied date range
|
|
23
|
+
* @param {function(moment.Moment[]): void} props.onChange - Callback when range is applied
|
|
24
|
+
* @param {string} [props.format='DD/MM/YYYY'] - Date display format
|
|
25
|
+
* @param {object} [props.ranges] - Preset date ranges
|
|
26
|
+
* @param {boolean} [props.allowClear=false] - Whether to show clear button
|
|
27
|
+
* @param {boolean} [props.inputReadOnly=true] - Whether input is read-only
|
|
28
|
+
*/
|
|
29
|
+
|
|
6
30
|
const { RangePicker } = DatePicker;
|
|
7
31
|
|
|
8
|
-
export default function RangePickerComponent({
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
32
|
+
export default function RangePickerComponent({
|
|
33
|
+
value,
|
|
34
|
+
onChange,
|
|
35
|
+
format = 'DD/MM/YYYY',
|
|
36
|
+
ranges,
|
|
37
|
+
allowClear = false,
|
|
38
|
+
inputReadOnly = true,
|
|
39
|
+
...restProps
|
|
40
|
+
}) {
|
|
41
|
+
const { t } = useTranslation();
|
|
42
|
+
|
|
43
|
+
// Temporary range during selection (before Apply is clicked)
|
|
44
|
+
const [tempRange, setTempRange] = useState(null);
|
|
45
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
46
|
+
|
|
47
|
+
// Ref to track if a selection just happened, to prevent the picker from closing.
|
|
48
|
+
const selectionJustHappened = useRef(false);
|
|
12
49
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
50
|
+
/**
|
|
51
|
+
* Handles any completed date selection (both manual and preset).
|
|
52
|
+
* It updates the temporary range and sets a flag to keep the picker open.
|
|
53
|
+
*/
|
|
54
|
+
const handleChange = useCallback((dates) => {
|
|
55
|
+
setTempRange(dates || []);
|
|
56
|
+
selectionJustHappened.current = true;
|
|
57
|
+
}, []);
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Handles picker open/close events.
|
|
61
|
+
* Prevents closing after a selection, requiring user to click Apply/Cancel.
|
|
62
|
+
*/
|
|
63
|
+
const handleOpenChange = useCallback((open) => {
|
|
64
|
+
// If the picker is trying to close, check if it was due to a selection.
|
|
65
|
+
if (!open && selectionJustHappened.current) {
|
|
66
|
+
// It was a selection, so ignore the close request and reset the flag.
|
|
67
|
+
selectionJustHappened.current = false;
|
|
16
68
|
return;
|
|
17
69
|
}
|
|
18
70
|
|
|
19
|
-
//
|
|
20
|
-
|
|
71
|
+
// For all other cases (opening, or closing by clicking outside), proceed as normal.
|
|
72
|
+
if (open) {
|
|
73
|
+
// Reset temp range when opening for a fresh selection.
|
|
74
|
+
setTempRange(null);
|
|
75
|
+
}
|
|
76
|
+
setIsOpen(open);
|
|
77
|
+
}, []);
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Applies the temporary range and closes picker
|
|
81
|
+
*/
|
|
82
|
+
const handleApply = useCallback(() => {
|
|
83
|
+
if (tempRange?.length === 2 && onChange) {
|
|
84
|
+
onChange(tempRange);
|
|
85
|
+
}
|
|
86
|
+
selectionJustHappened.current = false; // Clear the flag
|
|
87
|
+
setIsOpen(false);
|
|
88
|
+
}, [tempRange, onChange]);
|
|
21
89
|
|
|
22
|
-
|
|
23
|
-
|
|
90
|
+
/**
|
|
91
|
+
* Cancels selection and closes picker
|
|
92
|
+
*/
|
|
93
|
+
const handleCancel = useCallback(() => {
|
|
94
|
+
selectionJustHappened.current = false; // Clear the flag
|
|
95
|
+
setTempRange(null);
|
|
96
|
+
setIsOpen(false);
|
|
97
|
+
}, []);
|
|
24
98
|
|
|
25
|
-
//
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
99
|
+
// Memoized preset ranges - use provided ranges or default set
|
|
100
|
+
const defaultRanges = useRef({
|
|
101
|
+
Today: [moment(), moment()],
|
|
102
|
+
Yesterday: [moment().subtract(1, 'days').startOf('day'), moment().subtract(1, 'days').endOf('day')],
|
|
103
|
+
'This Week': [moment().startOf('week'), moment().endOf('week')],
|
|
104
|
+
'Last Week': [moment().subtract(1, 'week').startOf('week'), moment().subtract(1, 'week').endOf('week')],
|
|
105
|
+
'This Month': [moment().startOf('month'), moment().endOf('month')],
|
|
106
|
+
'Last Month': [moment().subtract(1, 'month').startOf('month'), moment().subtract(1, 'month').endOf('month')],
|
|
107
|
+
}).current;
|
|
30
108
|
|
|
31
109
|
return (
|
|
32
|
-
<
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
110
|
+
<RangePicker
|
|
111
|
+
allowClear={allowClear}
|
|
112
|
+
inputReadOnly={inputReadOnly}
|
|
113
|
+
format={format}
|
|
114
|
+
value={tempRange || value}
|
|
115
|
+
open={isOpen}
|
|
116
|
+
onOpenChange={handleOpenChange}
|
|
117
|
+
onChange={handleChange}
|
|
118
|
+
ranges={ranges !== undefined ? ranges : defaultRanges}
|
|
119
|
+
{...restProps}
|
|
120
|
+
renderExtraFooter={() => (
|
|
121
|
+
<div style={{ width: '100%', display: 'flex', justifyContent: 'flex-end', order: 1 }}>
|
|
122
|
+
<Space>
|
|
123
|
+
<Button size="small" onClick={handleCancel}>
|
|
124
|
+
{t('Cancel')}
|
|
125
|
+
</Button>
|
|
126
|
+
<Button type="primary" size="small" onClick={handleApply}>
|
|
127
|
+
{t('Apply')}
|
|
128
|
+
</Button>
|
|
129
|
+
</Space>
|
|
130
|
+
</div>
|
|
131
|
+
)}
|
|
132
|
+
/>
|
|
44
133
|
);
|
|
45
134
|
}
|
|
46
135
|
|
|
@@ -1,36 +1,46 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Component for antd switch
|
|
3
3
|
*/
|
|
4
|
-
|
|
5
|
-
import React, { useState, useContext, useEffect } from 'react';
|
|
6
|
-
import { GlobalContext } from '../../../Store';
|
|
7
|
-
|
|
4
|
+
import React, { useContext } from 'react';
|
|
8
5
|
import { Switch as AntdSwitch } from 'antd';
|
|
9
|
-
|
|
6
|
+
import { GlobalContext } from '../../../Store';
|
|
10
7
|
import PropTypes from 'prop-types';
|
|
11
8
|
|
|
12
|
-
export default function Switch({
|
|
9
|
+
export default function Switch({
|
|
10
|
+
checked,
|
|
11
|
+
defaultChecked,
|
|
12
|
+
onChange,
|
|
13
|
+
disabled,
|
|
14
|
+
id,
|
|
15
|
+
size,
|
|
16
|
+
checkedChildren,
|
|
17
|
+
unCheckedChildren,
|
|
18
|
+
className,
|
|
19
|
+
style,
|
|
20
|
+
}) {
|
|
13
21
|
const { state } = useContext(GlobalContext);
|
|
14
22
|
|
|
15
|
-
useEffect(() => {
|
|
16
|
-
// Reacting to theme changes
|
|
17
|
-
}, [state.theme.colors]);
|
|
18
|
-
|
|
19
23
|
return (
|
|
20
|
-
<
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
24
|
+
<AntdSwitch
|
|
25
|
+
id={id}
|
|
26
|
+
checked={checked}
|
|
27
|
+
defaultChecked={defaultChecked}
|
|
28
|
+
onChange={onChange}
|
|
29
|
+
disabled={disabled}
|
|
30
|
+
size={size}
|
|
31
|
+
checkedChildren={checkedChildren}
|
|
32
|
+
unCheckedChildren={unCheckedChildren}
|
|
33
|
+
className={className}
|
|
34
|
+
style={{
|
|
35
|
+
backgroundColor: checked
|
|
36
|
+
? state.theme.colors.primaryButtonBg
|
|
37
|
+
: state.theme.colors.primaryButtonDisabledBg,
|
|
38
|
+
borderColor: checked
|
|
39
|
+
? state.theme.colors.primaryButtonBg
|
|
40
|
+
: state.theme.colors.primaryButtonDisabledBg,
|
|
41
|
+
...style,
|
|
42
|
+
}}
|
|
43
|
+
/>
|
|
34
44
|
);
|
|
35
45
|
}
|
|
36
46
|
|
package/core/lib/hooks/index.js
CHANGED
|
@@ -1,19 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
1
|
import useDeviceDetect from './device-detect';
|
|
5
2
|
|
|
6
3
|
import useWindowSize from './use-window-size';
|
|
7
4
|
|
|
8
5
|
import useFormUtils from './../utils/form/form.utils';
|
|
9
6
|
import useLocation from './use-location';
|
|
7
|
+
import { useOtpTimer } from './use-otp-timer';
|
|
10
8
|
|
|
11
|
-
|
|
12
|
-
export {
|
|
13
|
-
useDeviceDetect,
|
|
14
|
-
useWindowSize,
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
useFormUtils,
|
|
18
|
-
useLocation
|
|
19
|
-
}
|
|
9
|
+
export { useDeviceDetect, useWindowSize, useOtpTimer, useFormUtils, useLocation };
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { useState, useEffect, useRef } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Custom hook to manage an OTP (One-Time Password) countdown timer.
|
|
5
|
+
*
|
|
6
|
+
* Features:
|
|
7
|
+
* - Can start a timer directly with seconds.
|
|
8
|
+
* - Can start a timer using an expiry timestamp (from backend).
|
|
9
|
+
* - Provides formatted remaining time (MM:SS).
|
|
10
|
+
* - Tracks whether the timer has expired.
|
|
11
|
+
*
|
|
12
|
+
* @returns {Object} API
|
|
13
|
+
* @returns {number} API.remaining - Remaining seconds.
|
|
14
|
+
* @returns {boolean} API.expired - Whether the timer has expired.
|
|
15
|
+
* @returns {string} API.formatted - Formatted remaining time (MM:SS).
|
|
16
|
+
* @returns {(seconds: number) => void} API.start - Start timer with seconds.
|
|
17
|
+
* @returns {(expirytime: string) => void} API.startFromExpiry - Start timer using expiry string (e.g. "2025-09-04T12:13:09.000Z").
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* const { remaining, expired, formatted, start, startFromExpiry } = useOtpTimer();
|
|
21
|
+
*
|
|
22
|
+
* // Start with 30 seconds
|
|
23
|
+
* useEffect(() => {
|
|
24
|
+
* start(30);
|
|
25
|
+
* }, []);
|
|
26
|
+
*
|
|
27
|
+
* // OR start from backend expiry timestamp
|
|
28
|
+
* useEffect(() => {
|
|
29
|
+
* startFromExpiry("2025-09-04T12:13:09.000Z");
|
|
30
|
+
* }, []);
|
|
31
|
+
*
|
|
32
|
+
* return (
|
|
33
|
+
* <div>
|
|
34
|
+
* <p>Time left: {formatted}</p>
|
|
35
|
+
* {expired && <p>OTP expired!</p>}
|
|
36
|
+
* </div>
|
|
37
|
+
* );
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
// helper to format time
|
|
41
|
+
const formatTime = (seconds) => {
|
|
42
|
+
const minutes = Math.floor((seconds % 3600) / 60);
|
|
43
|
+
const secs = seconds % 60;
|
|
44
|
+
return `${String(minutes).padStart(2, '0')}:${String(secs).padStart(2, '0')}`;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const useOtpTimer = () => {
|
|
48
|
+
const [remaining, setRemaining] = useState(0);
|
|
49
|
+
const [expired, setExpired] = useState(false);
|
|
50
|
+
const intervalRef = useRef(null);
|
|
51
|
+
|
|
52
|
+
// Start timer by passing expiry string from backend
|
|
53
|
+
const startFromExpiry = (expirytime) => {
|
|
54
|
+
if (!expirytime) {
|
|
55
|
+
setRemaining(0);
|
|
56
|
+
setExpired(true);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Handle backend expiry string
|
|
61
|
+
const expiryTime = new Date(expirytime).getTime();
|
|
62
|
+
const now = Date.now();
|
|
63
|
+
const seconds = Math.max(0, Math.floor((expiryTime - now) / 1000));
|
|
64
|
+
|
|
65
|
+
start(seconds);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// Generic start (seconds)
|
|
69
|
+
const start = (seconds) => {
|
|
70
|
+
clearInterval(intervalRef.current);
|
|
71
|
+
|
|
72
|
+
if (!seconds || seconds <= 0) {
|
|
73
|
+
setRemaining(0);
|
|
74
|
+
setExpired(true);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
setRemaining(seconds);
|
|
79
|
+
setExpired(false);
|
|
80
|
+
|
|
81
|
+
intervalRef.current = setInterval(() => {
|
|
82
|
+
setRemaining((prev) => {
|
|
83
|
+
if (prev <= 1) {
|
|
84
|
+
clearInterval(intervalRef.current);
|
|
85
|
+
setExpired(true);
|
|
86
|
+
return 0;
|
|
87
|
+
}
|
|
88
|
+
return prev - 1;
|
|
89
|
+
});
|
|
90
|
+
}, 1000);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// Cleanup on unmount
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
return () => clearInterval(intervalRef.current);
|
|
96
|
+
}, []);
|
|
97
|
+
|
|
98
|
+
return { remaining, expired, formatted: formatTime(remaining), start, startFromExpiry };
|
|
99
|
+
};
|