ui-soxo-bootstrap-core 2.6.14 → 2.6.16
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/landing-api/landing-api.js +66 -2
- package/core/components/landing-api/landing-api.scss +22 -0
- package/core/lib/elements/basic/country-phone-input/country-phone-input.js +2 -2
- package/core/lib/elements/basic/dragabble-wrapper/draggable-wrapper.js +1 -1
- package/core/lib/elements/basic/menu-tree/menu-tree.js +2 -2
- package/core/lib/utils/common/common.utils.js +49 -0
- package/core/lib/utils/index.js +4 -1
- package/core/models/roles/components/role-add/role-add.js +2 -2
- package/core/models/staff/components/staff-add/staff-add.js +20 -32
- package/core/models/users/components/user-add/user-add.js +9 -17
- package/core/models/users/components/user-add/user-edit.js +5 -2
- package/core/modules/steps/action-buttons.js +6 -0
- package/core/modules/steps/action-buttons.scss +9 -3
- package/core/modules/steps/steps.js +46 -5
- package/core/modules/steps/steps.scss +82 -13
- package/package.json +2 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useState, useEffect, useContext } from 'react';
|
|
1
|
+
import React, { useState, useEffect, useContext, useRef } from 'react';
|
|
2
2
|
|
|
3
3
|
import { Route, Switch } from 'react-router-dom';
|
|
4
4
|
|
|
@@ -18,13 +18,33 @@ import PropTypes from 'prop-types';
|
|
|
18
18
|
|
|
19
19
|
import { MenusAPI, CoreScripts } from '../../models';
|
|
20
20
|
|
|
21
|
+
const motivatingMessages = [
|
|
22
|
+
'Setting things up for a great start...',
|
|
23
|
+
'Good things are loading. Stay with us.',
|
|
24
|
+
'Almost there. Preparing your workspace.',
|
|
25
|
+
'You are one moment away from your dashboard.',
|
|
26
|
+
'Getting everything ready for you.',
|
|
27
|
+
'Great care takes a second. Loading now.',
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
function getRandomMessage(previousMessage = '') {
|
|
31
|
+
const options = motivatingMessages.filter((message) => message !== previousMessage);
|
|
32
|
+
|
|
33
|
+
if (!options.length) {
|
|
34
|
+
return motivatingMessages[0];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const randomIndex = Math.floor(Math.random() * options.length);
|
|
38
|
+
return options[randomIndex];
|
|
39
|
+
}
|
|
40
|
+
|
|
21
41
|
/**
|
|
22
42
|
* Landing API
|
|
23
43
|
*
|
|
24
44
|
* @param {*} param0
|
|
25
45
|
* @returns
|
|
26
46
|
*/
|
|
27
|
-
export default function LandingApi({ history, CustomComponents, CustomModels, appSettings, ...props }) {
|
|
47
|
+
export default function LandingApi({ history, CustomComponents, CustomModels, appSettings, transitionPending = false, onHomeReady, ...props }) {
|
|
28
48
|
const [loader, setLoader] = useState(false);
|
|
29
49
|
|
|
30
50
|
// const [modules, setModules] = useState([]);
|
|
@@ -34,8 +54,11 @@ export default function LandingApi({ history, CustomComponents, CustomModels, ap
|
|
|
34
54
|
const { dispatch, user = {} } = useContext(GlobalContext);
|
|
35
55
|
|
|
36
56
|
const [allModules, setAllModules] = useState();
|
|
57
|
+
const homeReadyNotifiedRef = useRef(false);
|
|
58
|
+
const messageIntervalRef = useRef(null);
|
|
37
59
|
|
|
38
60
|
const [meta, setMeta] = useState({});
|
|
61
|
+
const [loadingMessage, setLoadingMessage] = useState('');
|
|
39
62
|
|
|
40
63
|
// const [reports, setReports] = useState([]);
|
|
41
64
|
|
|
@@ -70,6 +93,46 @@ export default function LandingApi({ history, CustomComponents, CustomModels, ap
|
|
|
70
93
|
initializeUserMenus();
|
|
71
94
|
}, []);
|
|
72
95
|
|
|
96
|
+
useEffect(() => {
|
|
97
|
+
if (!transitionPending) {
|
|
98
|
+
homeReadyNotifiedRef.current = false;
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (!loader && allModules && !homeReadyNotifiedRef.current) {
|
|
103
|
+
homeReadyNotifiedRef.current = true;
|
|
104
|
+
if (typeof onHomeReady === 'function') {
|
|
105
|
+
onHomeReady();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}, [transitionPending, loader, allModules, onHomeReady]);
|
|
109
|
+
|
|
110
|
+
useEffect(() => {
|
|
111
|
+
if (!loader) {
|
|
112
|
+
setLoadingMessage('');
|
|
113
|
+
|
|
114
|
+
if (messageIntervalRef.current) {
|
|
115
|
+
clearInterval(messageIntervalRef.current);
|
|
116
|
+
messageIntervalRef.current = null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
setLoadingMessage((previousMessage) => getRandomMessage(previousMessage));
|
|
123
|
+
|
|
124
|
+
messageIntervalRef.current = setInterval(() => {
|
|
125
|
+
setLoadingMessage((previousMessage) => getRandomMessage(previousMessage));
|
|
126
|
+
}, 2200);
|
|
127
|
+
|
|
128
|
+
return () => {
|
|
129
|
+
if (messageIntervalRef.current) {
|
|
130
|
+
clearInterval(messageIntervalRef.current);
|
|
131
|
+
messageIntervalRef.current = null;
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
}, [loader]);
|
|
135
|
+
|
|
73
136
|
/**
|
|
74
137
|
* Initialize the user menus
|
|
75
138
|
*/
|
|
@@ -245,6 +308,7 @@ export default function LandingApi({ history, CustomComponents, CustomModels, ap
|
|
|
245
308
|
<Card className="skeleton-card">
|
|
246
309
|
<div className="skeleton-wrapper">
|
|
247
310
|
<Skeleton paragraph={{ rows: 4 }} />
|
|
311
|
+
<p className="motivating-text">{loadingMessage}</p>
|
|
248
312
|
</div>
|
|
249
313
|
</Card>
|
|
250
314
|
) : (
|
|
@@ -11,9 +11,31 @@
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
.skeleton-wrapper {
|
|
14
|
+
.motivating-text {
|
|
15
|
+
margin-top: 14px;
|
|
16
|
+
margin-bottom: 4px;
|
|
17
|
+
font-size: 14px;
|
|
18
|
+
line-height: 20px;
|
|
19
|
+
text-align: center;
|
|
20
|
+
color: #5e6d86;
|
|
21
|
+
font-weight: 500;
|
|
22
|
+
animation: skeletonTextFade 0.4s ease-in-out;
|
|
23
|
+
}
|
|
14
24
|
}
|
|
15
25
|
|
|
16
26
|
.wrapper-loader {
|
|
17
27
|
margin: 20px;
|
|
18
28
|
}
|
|
19
29
|
}
|
|
30
|
+
|
|
31
|
+
@keyframes skeletonTextFade {
|
|
32
|
+
from {
|
|
33
|
+
opacity: 0;
|
|
34
|
+
transform: translateY(2px);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
to {
|
|
38
|
+
opacity: 1;
|
|
39
|
+
transform: translateY(0);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -16,7 +16,7 @@ import PropTypes from "prop-types";
|
|
|
16
16
|
import './phone-input.scss';
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
export default function CountryPhoneInput({ value, onChange, disabled, disableCountryCode, required, defaultCountryCode, onBlur }) {
|
|
19
|
+
export default function CountryPhoneInput({ value, onChange, disabled, disableCountryCode, required, defaultCountryCode, onBlur, onKeyDown }) {
|
|
20
20
|
|
|
21
21
|
let inputRef = useRef();
|
|
22
22
|
|
|
@@ -85,7 +85,7 @@ export default function CountryPhoneInput({ value, onChange, disabled, disableCo
|
|
|
85
85
|
// country={disableCountryCode ? false : 'in'}
|
|
86
86
|
value={phone}
|
|
87
87
|
onChange={handleInput}
|
|
88
|
-
inputProps={{ required: required }}
|
|
88
|
+
inputProps={{ required: required, onKeyDown: onKeyDown }}
|
|
89
89
|
disabled={disabled}
|
|
90
90
|
onBlur={onBlur}
|
|
91
91
|
/>
|
|
@@ -102,7 +102,7 @@ export default function DraggableWrapper({ id, index, movePanel, item, dragEnabl
|
|
|
102
102
|
>
|
|
103
103
|
<div style={{ flex: 1 }}>
|
|
104
104
|
{dragEnabled && <span style={{ marginRight: 8 }}>⋮⋮</span>}
|
|
105
|
-
<span>{item.
|
|
105
|
+
<span>{item.caption}</span>
|
|
106
106
|
{dragEnabled ? (
|
|
107
107
|
<span style={{ marginLeft: 8, fontSize: 11, color: '#999' }}>(Level {level})</span>
|
|
108
108
|
) : (
|
|
@@ -79,7 +79,7 @@ export const MenuTree = ({ menus, selectedMenus = [], toggleMenu, parentId = nul
|
|
|
79
79
|
{/* Left Side */}
|
|
80
80
|
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
|
81
81
|
{showCheckbox && <Checkbox checked={selectedMenus.includes(menu.id)} onChange={(e) => onParentChange(e.target.checked)} />}
|
|
82
|
-
<span>{menu.
|
|
82
|
+
<span>{menu.caption}</span>
|
|
83
83
|
</div>
|
|
84
84
|
<Tag color={menu.is_visible === true ? 'green' : 'blue'}>{menu.is_visible === true ? 'VISIBLE' : 'HIDDEN'}</Tag>{' '}
|
|
85
85
|
</div>
|
|
@@ -110,7 +110,7 @@ export const MenuTree = ({ menus, selectedMenus = [], toggleMenu, parentId = nul
|
|
|
110
110
|
onChange={(e) => onParentChange(e.target.checked)}
|
|
111
111
|
/>
|
|
112
112
|
)}
|
|
113
|
-
<span>{menu.
|
|
113
|
+
<span>{menu.caption}</span>
|
|
114
114
|
</div>
|
|
115
115
|
<Tag color={menu.is_visible === true ? 'green' : 'blue'}>{menu.is_visible === true ? 'VISIBLE' : 'HIDDEN'}</Tag>{' '}
|
|
116
116
|
</div>
|
|
@@ -4,6 +4,7 @@ Implements utility functions to be used across project
|
|
|
4
4
|
|
|
5
5
|
/*eslint no-useless-escape:"off",eqeqeq: "off"*/
|
|
6
6
|
import moment from 'moment';
|
|
7
|
+
import { parsePhoneNumberFromString, isValidPhoneNumber } from 'libphonenumber-js';
|
|
7
8
|
|
|
8
9
|
export function IsObjectHaveKeys(obj) {
|
|
9
10
|
return obj && typeof obj == 'object' && Object.keys(obj).length;
|
|
@@ -221,3 +222,51 @@ export function safeJSON(value) {
|
|
|
221
222
|
return null;
|
|
222
223
|
}
|
|
223
224
|
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Convert +countrycode phone into form compatible structure
|
|
228
|
+
* @param {string} phone
|
|
229
|
+
* @returns {object|null}
|
|
230
|
+
*/
|
|
231
|
+
export const formatPhoneForForm = (phone) => {
|
|
232
|
+
if (!phone) return null;
|
|
233
|
+
|
|
234
|
+
const parsed = parsePhoneNumberFromString(phone);
|
|
235
|
+
|
|
236
|
+
if (!parsed) return null;
|
|
237
|
+
|
|
238
|
+
return {
|
|
239
|
+
value: parsed.nationalNumber,
|
|
240
|
+
code: {
|
|
241
|
+
dialCode: parsed.countryCallingCode,
|
|
242
|
+
countryCode: parsed.country?.toLowerCase(),
|
|
243
|
+
},
|
|
244
|
+
valid: true,
|
|
245
|
+
};
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Validates a phone number from CountryPhoneInput.
|
|
250
|
+
* Builds full number using country dial code and checks validity.
|
|
251
|
+
*
|
|
252
|
+
* @param {Object} _ - Ant Design rule parameter (unused)
|
|
253
|
+
* @param {Object} val - Phone value object from form
|
|
254
|
+
* @returns {Promise<void>}
|
|
255
|
+
*/
|
|
256
|
+
export const phoneValidator = (_, val) => {
|
|
257
|
+
if (!val || !val.value || !val?.code?.dialCode) {
|
|
258
|
+
return Promise.resolve();
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const fullPhone = `+${val.code.dialCode}${val.value}`;
|
|
262
|
+
|
|
263
|
+
return isValidPhoneNumber(fullPhone) ? Promise.resolve() : Promise.reject('Invalid mobile number');
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
// Returns phone number with country code (e.g., +919876543210)
|
|
267
|
+
export const formatPhoneWithCountryCode = (phone) => {
|
|
268
|
+
if (phone && typeof phone === 'object' && phone.code) {
|
|
269
|
+
return `+${phone.code.dialCode}${phone.value}`;
|
|
270
|
+
}
|
|
271
|
+
return phone;
|
|
272
|
+
};
|
package/core/lib/utils/index.js
CHANGED
|
@@ -14,7 +14,7 @@ import { GetData, PostData, PutData, DeleteData } from './http/http.utils';
|
|
|
14
14
|
|
|
15
15
|
import { getExportData } from './generic/generic.utils';
|
|
16
16
|
|
|
17
|
-
import { ConvertBytesToArray, safeJSON } from './common/common.utils';
|
|
17
|
+
import { ConvertBytesToArray, safeJSON, formatPhoneForForm, phoneValidator, formatPhoneWithCountryCode } from './common/common.utils';
|
|
18
18
|
|
|
19
19
|
import SettingsUtil from './setting.utils';
|
|
20
20
|
|
|
@@ -37,4 +37,7 @@ export {
|
|
|
37
37
|
SettingsUtil,
|
|
38
38
|
FormUtils,
|
|
39
39
|
safeJSON,
|
|
40
|
+
formatPhoneForForm,
|
|
41
|
+
phoneValidator,
|
|
42
|
+
formatPhoneWithCountryCode,
|
|
40
43
|
};
|
|
@@ -301,7 +301,7 @@ const MenuTree = ({ menus, selectedMenus, toggleMenu, parentId = null }) => {
|
|
|
301
301
|
}
|
|
302
302
|
}}
|
|
303
303
|
/>
|
|
304
|
-
<span>{menu.
|
|
304
|
+
<span>{menu.caption}</span>
|
|
305
305
|
</div>
|
|
306
306
|
<Tag color={menu.is_visible === true ? 'green' : 'blue'}>{menu.is_visible === true ? 'VISIBLE' : 'HIDDEN'}</Tag>{' '}
|
|
307
307
|
</div>
|
|
@@ -327,7 +327,7 @@ const MenuTree = ({ menus, selectedMenus, toggleMenu, parentId = null }) => {
|
|
|
327
327
|
onClick={(e) => e.stopPropagation()}
|
|
328
328
|
>
|
|
329
329
|
<Checkbox checked={parentChecked} indeterminate={parentIndeterminate} onChange={(e) => onParentChange(e.target.checked)} />
|
|
330
|
-
<span>{menu.
|
|
330
|
+
<span>{menu.caption}</span>
|
|
331
331
|
</div>
|
|
332
332
|
<Tag color={menu.is_visible === true ? 'green' : 'blue'}>{menu.is_visible === true ? 'VISIBLE' : 'HIDDEN'}</Tag>{' '}
|
|
333
333
|
</div>
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import React, { useState, useEffect, useRef } from 'react';
|
|
2
2
|
|
|
3
3
|
import { Modal, Tabs, Input, Form, Row, Col, message, Checkbox, Select } from 'antd';
|
|
4
|
-
import { useTranslation, Button } from './../../../../lib/';
|
|
4
|
+
import { useTranslation, Button, CountryPhoneInput, phoneValidator as libPhoneValidator, formatPhoneForForm } from './../../../../lib/';
|
|
5
5
|
import { UsersAPI } from '../../..';
|
|
6
|
+
import { formatPhoneWithCountryCode } from '../../../../lib/utils/common/common.utils';
|
|
6
7
|
|
|
7
8
|
const StaffAdd = ({ visible, onCancel, staffId, staffData, onSuccess }) => {
|
|
8
9
|
const [form] = Form.useForm();
|
|
@@ -61,18 +62,6 @@ const StaffAdd = ({ visible, onCancel, staffId, staffData, onSuccess }) => {
|
|
|
61
62
|
}
|
|
62
63
|
}, [visible]);
|
|
63
64
|
|
|
64
|
-
const phoneValidator = (_, value) => {
|
|
65
|
-
if (!value) {
|
|
66
|
-
return Promise.resolve(); // not required
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (!/^\d{10}$/.test(value)) {
|
|
70
|
-
return Promise.reject('Phone number must be 10 digits');
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return Promise.resolve();
|
|
74
|
-
};
|
|
75
|
-
|
|
76
65
|
/** -------------------------------
|
|
77
66
|
* API CALL – Designations
|
|
78
67
|
* ------------------------------- */
|
|
@@ -201,8 +190,8 @@ const StaffAdd = ({ visible, onCancel, staffId, staffData, onSuccess }) => {
|
|
|
201
190
|
shortName: data.shortName,
|
|
202
191
|
description: data.description,
|
|
203
192
|
designation: data.designationPtr,
|
|
204
|
-
phone1: data.phone || data.mobile,
|
|
205
|
-
phone2: data.alternateMobile,
|
|
193
|
+
phone1: formatPhoneForForm(data.phone || data.mobile),
|
|
194
|
+
phone2: formatPhoneForForm(data.alternateMobile),
|
|
206
195
|
email1: data.email,
|
|
207
196
|
email2: data.alternateEmail,
|
|
208
197
|
slno: data.slNo,
|
|
@@ -223,14 +212,17 @@ const StaffAdd = ({ visible, onCancel, staffId, staffData, onSuccess }) => {
|
|
|
223
212
|
const handleFinish = async (values) => {
|
|
224
213
|
setLoading(true);
|
|
225
214
|
|
|
215
|
+
let phone1Value = formatPhoneWithCountryCode(values.phone1);
|
|
216
|
+
let phone2Value = formatPhoneWithCountryCode(values.phone2);
|
|
217
|
+
|
|
226
218
|
const payload = {
|
|
227
219
|
id: values.id,
|
|
228
220
|
shortName: values.shortName,
|
|
229
221
|
description: values.description,
|
|
230
222
|
designationPtr: values.designation ?? existing.designationPtr ?? null,
|
|
231
|
-
phone:
|
|
232
|
-
mobile:
|
|
233
|
-
alternateMobile:
|
|
223
|
+
phone: phone1Value ?? existing.phone ?? null,
|
|
224
|
+
mobile: phone1Value ?? existing.mobile ?? null,
|
|
225
|
+
alternateMobile: phone2Value ?? existing.alternateMobile ?? null,
|
|
234
226
|
email: values.email1 ?? existing.email ?? null,
|
|
235
227
|
alternateEmail: values.email2 ?? existing.alternateEmail ?? null,
|
|
236
228
|
remarks: values.remarks ?? existing.remarks ?? null,
|
|
@@ -331,26 +323,22 @@ const StaffAdd = ({ visible, onCancel, staffId, staffData, onSuccess }) => {
|
|
|
331
323
|
</Form.Item>
|
|
332
324
|
</Col> */}
|
|
333
325
|
<Col span={6}>
|
|
334
|
-
<Form.Item label="Phone Number" name="phone1" rules={[{ validator:
|
|
335
|
-
<
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
inputMode="numeric"
|
|
340
|
-
onChange={(e) => (e.target.value = e.target.value.replace(/\D/g, ''))}
|
|
326
|
+
<Form.Item label="Phone Number" name="phone1" validateTrigger="onBlur" rules={[{ validator: libPhoneValidator }]}>
|
|
327
|
+
<CountryPhoneInput
|
|
328
|
+
defaultCountryCode={process.env.REACT_APP_COUNTRYCODE}
|
|
329
|
+
enableSearch
|
|
330
|
+
inputStyle={{ width: '100%' }}
|
|
341
331
|
onKeyDown={handleEnterKey}
|
|
342
332
|
/>
|
|
343
333
|
</Form.Item>
|
|
344
334
|
</Col>
|
|
345
335
|
|
|
346
336
|
<Col span={6}>
|
|
347
|
-
<Form.Item label="Alternate Phone Number" name="phone2" rules={[{ validator:
|
|
348
|
-
<
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
inputMode="numeric"
|
|
353
|
-
onChange={(e) => (e.target.value = e.target.value.replace(/\D/g, ''))}
|
|
337
|
+
<Form.Item label="Alternate Phone Number" name="phone2" validateTrigger="onBlur" rules={[{ validator: libPhoneValidator }]}>
|
|
338
|
+
<CountryPhoneInput
|
|
339
|
+
defaultCountryCode={process.env.REACT_APP_COUNTRYCODE}
|
|
340
|
+
enableSearch
|
|
341
|
+
inputStyle={{ width: '100%' }}
|
|
354
342
|
onKeyDown={handleEnterKey}
|
|
355
343
|
/>
|
|
356
344
|
</Form.Item>
|
|
@@ -6,7 +6,7 @@ import { Skeleton, Typography, message, Switch, Form, Input, Select, Checkbox, R
|
|
|
6
6
|
|
|
7
7
|
import AsyncSelect from 'react-select/async';
|
|
8
8
|
|
|
9
|
-
import { Table, Card, Button, JSONInput, GlobalContext, safeJSON } from './../../../../lib/';
|
|
9
|
+
import { Table, Card, Button, JSONInput, GlobalContext, safeJSON, CountryPhoneInput, phoneValidator } from './../../../../lib/';
|
|
10
10
|
|
|
11
11
|
import { ModelsAPI, PagesAPI, RolesAPI } from '../../..';
|
|
12
12
|
|
|
@@ -357,6 +357,10 @@ const UserAdd = ({ model, callback, edit, history, formContent, match, additiona
|
|
|
357
357
|
* Submit values
|
|
358
358
|
*/
|
|
359
359
|
const onSubmit = (values) => {
|
|
360
|
+
const mobileData = values.mobile;
|
|
361
|
+
|
|
362
|
+
const mobileWithCountryCode = `+${mobileData.code.dialCode}${mobileData.value}`;
|
|
363
|
+
|
|
360
364
|
values.defaultBranch = String(values.defaultBranch);
|
|
361
365
|
|
|
362
366
|
/**If PanelOpen is open and edit mode then password will be existing password else new password*/
|
|
@@ -378,6 +382,7 @@ const UserAdd = ({ model, callback, edit, history, formContent, match, additiona
|
|
|
378
382
|
values = {
|
|
379
383
|
...values,
|
|
380
384
|
auth_type: 'LDAP',
|
|
385
|
+
mobile: mobileWithCountryCode,
|
|
381
386
|
FA: authentication,
|
|
382
387
|
};
|
|
383
388
|
|
|
@@ -597,23 +602,10 @@ const UserAdd = ({ model, callback, edit, history, formContent, match, additiona
|
|
|
597
602
|
<Form.Item
|
|
598
603
|
name="mobile"
|
|
599
604
|
label="Mobile"
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
{
|
|
603
|
-
pattern: /^[0-9]{10}$/,
|
|
604
|
-
message: 'Mobile number must be exactly 10 digits',
|
|
605
|
-
},
|
|
606
|
-
]}
|
|
605
|
+
validateTrigger="onBlur"
|
|
606
|
+
rules={[{ required: true, message: 'Mobile number required' }, { validator: phoneValidator }]}
|
|
607
607
|
>
|
|
608
|
-
<
|
|
609
|
-
placeholder="Enter Mobile"
|
|
610
|
-
maxLength={10}
|
|
611
|
-
onKeyPress={(e) => {
|
|
612
|
-
if (!/[0-9]/.test(e.key)) {
|
|
613
|
-
e.preventDefault();
|
|
614
|
-
}
|
|
615
|
-
}}
|
|
616
|
-
/>
|
|
608
|
+
<CountryPhoneInput defaultCountryCode={process.env.REACT_APP_COUNTRYCODE} enableSearch inputStyle={{ width: '100%' }} />
|
|
617
609
|
</Form.Item>
|
|
618
610
|
</Col>
|
|
619
611
|
<Col span={8}>
|
|
@@ -3,6 +3,7 @@ import { Modal, Button, Skeleton } from 'antd';
|
|
|
3
3
|
import { EditOutlined } from '@ant-design/icons';
|
|
4
4
|
import UserAdd from '../../../../models/users/components/user-add/user-add';
|
|
5
5
|
import { UsersAPI } from '../../..';
|
|
6
|
+
import { formatPhoneForForm } from '../../../../lib';
|
|
6
7
|
|
|
7
8
|
export default function UserEdit(record) {
|
|
8
9
|
const [visible, setVisible] = useState(false);
|
|
@@ -44,13 +45,15 @@ export default function UserEdit(record) {
|
|
|
44
45
|
// extract dbPtr (branch code)
|
|
45
46
|
const defaultBranchCode = defaultBranchObj?.branch_id ? Number(defaultBranchObj.branch_id) : undefined;
|
|
46
47
|
|
|
48
|
+
const formattedMobile = formatPhoneForForm(apiData.mobile);
|
|
49
|
+
|
|
47
50
|
// Construct mapped data object
|
|
48
51
|
const mappedData = {
|
|
49
52
|
id: apiData.id,
|
|
50
53
|
user_type: otherDetails.user_type || apiData.user_type,
|
|
51
54
|
name: apiData.name,
|
|
52
55
|
email: apiData.email,
|
|
53
|
-
mobile:
|
|
56
|
+
mobile: formattedMobile,
|
|
54
57
|
designation: apiData.designation_code,
|
|
55
58
|
department: Number(apiData.department_id),
|
|
56
59
|
// Handle selected branches and default branch
|
|
@@ -63,7 +66,7 @@ export default function UserEdit(record) {
|
|
|
63
66
|
// Handle doctor codes
|
|
64
67
|
default_code: apiData.doctor_code,
|
|
65
68
|
doctor_code: apiData.doctor_code,
|
|
66
|
-
staff_code:apiData.staff_id,
|
|
69
|
+
staff_code: apiData.staff_id,
|
|
67
70
|
auth_type: apiData.auth_type,
|
|
68
71
|
FA: apiData.FA,
|
|
69
72
|
active: apiData.active ? true : false,
|
|
@@ -13,6 +13,8 @@ export default function ActionButtons({
|
|
|
13
13
|
steps,
|
|
14
14
|
activeStep,
|
|
15
15
|
isStepCompleted,
|
|
16
|
+
isFullscreen,
|
|
17
|
+
onToggleFullscreen,
|
|
16
18
|
renderDynamicComponent,
|
|
17
19
|
handlePrevious,
|
|
18
20
|
handleNext,
|
|
@@ -35,6 +37,10 @@ export default function ActionButtons({
|
|
|
35
37
|
Back
|
|
36
38
|
</Button>
|
|
37
39
|
|
|
40
|
+
<Button type="default" onClick={onToggleFullscreen}>
|
|
41
|
+
{isFullscreen ? 'Exit Full Screen' : 'Switch to Full Screen'}
|
|
42
|
+
</Button>
|
|
43
|
+
|
|
38
44
|
{/* Skip button */}
|
|
39
45
|
{steps.length > 0 && steps[activeStep]?.allow_skip === 'Y' && (
|
|
40
46
|
<Button type="default" onClick={handleSkip} disabled={activeStep === steps.length - 1}>
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
overflow-y: auto;
|
|
12
12
|
overflow-x: hidden;
|
|
13
13
|
overscroll-behavior: contain;
|
|
14
|
+
|
|
14
15
|
padding-bottom: 8px;
|
|
15
16
|
}
|
|
16
17
|
|
|
@@ -27,7 +28,6 @@
|
|
|
27
28
|
border-top: 1px solid #f0f0f0;
|
|
28
29
|
box-shadow: 0 -2px 10px rgba(15, 23, 42, 0.04);
|
|
29
30
|
|
|
30
|
-
|
|
31
31
|
width: 61%;
|
|
32
32
|
padding: 10px;
|
|
33
33
|
position: fixed;
|
|
@@ -37,9 +37,15 @@
|
|
|
37
37
|
display: flex;
|
|
38
38
|
border-radius: 4px;
|
|
39
39
|
box-shadow: -1px -4px 10px 2px #f7f7f76e;
|
|
40
|
-
|
|
40
|
+
flex-wrap: wrap;
|
|
41
41
|
|
|
42
42
|
.ant-btn {
|
|
43
43
|
border-radius: 4px;
|
|
44
44
|
}
|
|
45
|
-
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.process-steps-page.is-fullscreen .action-buttons-container {
|
|
48
|
+
width: calc(100% - 24px);
|
|
49
|
+
left: 12px;
|
|
50
|
+
right: 12px;
|
|
51
|
+
}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* - Handles process submission and optional chaining to the next process.
|
|
9
9
|
* - Provides a collapsible timeline view and action controls.
|
|
10
10
|
*/
|
|
11
|
-
import React, { useEffect, useState } from 'react';
|
|
11
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
12
12
|
import { Row, Col, Empty } from 'antd';
|
|
13
13
|
import { Card } from './../../lib';
|
|
14
14
|
import * as genericComponents from './../../lib';
|
|
@@ -36,8 +36,11 @@ export default function ProcessStepsPage({ match, CustomComponents = {}, ...prop
|
|
|
36
36
|
const [timelineCollapsed, setTimelineCollapsed] = useState(false);
|
|
37
37
|
const [showExternalWindow, setShowExternalWindow] = useState(false);
|
|
38
38
|
const [externalWin, setExternalWin] = useState(null);
|
|
39
|
+
const [isFullscreen, setIsFullscreen] = useState(false);
|
|
40
|
+
const processStepsRef = useRef(null);
|
|
39
41
|
|
|
40
42
|
const urlParams = Location.search();
|
|
43
|
+
const isConsultationMode = String(urlParams?.consultation).toLowerCase() === 'true';
|
|
41
44
|
let processId = urlParams.processId;
|
|
42
45
|
const [currentProcessId, setCurrentProcessId] = useState(processId);
|
|
43
46
|
// Load process details based on the current process ID
|
|
@@ -271,13 +274,49 @@ export default function ProcessStepsPage({ match, CustomComponents = {}, ...prop
|
|
|
271
274
|
};
|
|
272
275
|
}, [activeStep, steps, externalWin]);
|
|
273
276
|
|
|
277
|
+
useEffect(() => {
|
|
278
|
+
const syncFullscreenState = () => {
|
|
279
|
+
setIsFullscreen(document.fullscreenElement === processStepsRef.current);
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
document.addEventListener('fullscreenchange', syncFullscreenState);
|
|
283
|
+
syncFullscreenState();
|
|
284
|
+
|
|
285
|
+
return () => {
|
|
286
|
+
document.removeEventListener('fullscreenchange', syncFullscreenState);
|
|
287
|
+
};
|
|
288
|
+
}, []);
|
|
289
|
+
|
|
290
|
+
const handleToggleFullscreen = async () => {
|
|
291
|
+
const element = processStepsRef.current;
|
|
292
|
+
if (!element) return;
|
|
293
|
+
|
|
294
|
+
try {
|
|
295
|
+
if (document.fullscreenElement === element) {
|
|
296
|
+
if (document.exitFullscreen) {
|
|
297
|
+
await document.exitFullscreen();
|
|
298
|
+
}
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (element.requestFullscreen) {
|
|
303
|
+
await element.requestFullscreen();
|
|
304
|
+
}
|
|
305
|
+
} catch (error) {
|
|
306
|
+
console.error('Unable to toggle full screen for steps page:', error);
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
|
|
274
310
|
/**
|
|
275
311
|
* Renders the main process UI including timeline, step details,
|
|
276
312
|
* and action buttons. This content is reused in both normal view
|
|
277
313
|
* and external window view.
|
|
278
314
|
*/
|
|
279
|
-
const renderContent = () => (
|
|
280
|
-
<div
|
|
315
|
+
const renderContent = (attachRef = true) => (
|
|
316
|
+
<div
|
|
317
|
+
ref={attachRef ? processStepsRef : null}
|
|
318
|
+
className={`process-steps-page ${isConsultationMode ? 'consultation-mode' : ''} ${isFullscreen ? 'is-fullscreen' : ''}`}
|
|
319
|
+
>
|
|
281
320
|
<Card className="process-steps-card">
|
|
282
321
|
<Row gutter={20} className="process-steps-row" align="stretch">
|
|
283
322
|
<Col xs={24} sm={24} lg={timelineCollapsed ? 2 : 6} className="process-steps-timeline-col">
|
|
@@ -302,6 +341,8 @@ export default function ProcessStepsPage({ match, CustomComponents = {}, ...prop
|
|
|
302
341
|
steps={steps}
|
|
303
342
|
activeStep={activeStep}
|
|
304
343
|
isStepCompleted={isStepCompleted}
|
|
344
|
+
isFullscreen={isFullscreen}
|
|
345
|
+
onToggleFullscreen={handleToggleFullscreen}
|
|
305
346
|
renderDynamicComponent={DynamicComponent}
|
|
306
347
|
handlePrevious={handlePrevious}
|
|
307
348
|
handleNext={handleNext}
|
|
@@ -335,9 +376,9 @@ export default function ProcessStepsPage({ match, CustomComponents = {}, ...prop
|
|
|
335
376
|
width={props.ExternalWindowWidth || 1000}
|
|
336
377
|
height={props.ExternalWindowHeight || 1000}
|
|
337
378
|
>
|
|
338
|
-
{renderContent()}
|
|
379
|
+
{renderContent(false)}
|
|
339
380
|
</ExternalWindow>
|
|
340
|
-
{renderContent()}
|
|
381
|
+
{renderContent(true)}
|
|
341
382
|
</>
|
|
342
383
|
);
|
|
343
384
|
}
|
|
@@ -3,6 +3,25 @@
|
|
|
3
3
|
display: flex;
|
|
4
4
|
flex-direction: column;
|
|
5
5
|
overflow: hidden;
|
|
6
|
+
|
|
7
|
+
&.consultation-mode {
|
|
8
|
+
background: linear-gradient(180deg, #c8d2d6 0%, #d5d7d1 21%, #d9d8c6 40%, #c6cebe 58%, #a8c0c0 71%, #6e9fbb 84%, #367495 93%, #0a4d6e 100%);
|
|
9
|
+
padding: 12px;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
&.is-fullscreen {
|
|
13
|
+
padding: 12px;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.process-steps-page.consultation-mode .process-steps-card {
|
|
18
|
+
border: none;
|
|
19
|
+
background: transparent;
|
|
20
|
+
box-shadow: none;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.process-steps-page.consultation-mode .process-steps-card > .ant-card-body {
|
|
24
|
+
padding: 10px;
|
|
6
25
|
}
|
|
7
26
|
|
|
8
27
|
.process-steps-card {
|
|
@@ -49,11 +68,11 @@
|
|
|
49
68
|
flex: 1;
|
|
50
69
|
max-height: 100%;
|
|
51
70
|
overflow: hidden;
|
|
52
|
-
background: #fff;
|
|
53
|
-
border: 1px solid #e5ebf3;
|
|
54
|
-
border-radius: 14px;
|
|
55
|
-
box-shadow: 0 8px 20px rgba(15, 23, 42, 0.08);
|
|
56
|
-
padding: 14px 16px;
|
|
71
|
+
// background: #fff;
|
|
72
|
+
// border: 1px solid #e5ebf3;
|
|
73
|
+
// border-radius: 14px;
|
|
74
|
+
// box-shadow: 0 8px 20px rgba(15, 23, 42, 0.08);
|
|
75
|
+
// padding: 14px 16px;
|
|
57
76
|
position: relative;
|
|
58
77
|
z-index: 2;
|
|
59
78
|
}
|
|
@@ -84,8 +103,18 @@
|
|
|
84
103
|
height: 100%;
|
|
85
104
|
border: none !important;
|
|
86
105
|
border-radius: 12px;
|
|
87
|
-
background: linear-gradient(
|
|
88
|
-
|
|
106
|
+
background: linear-gradient(135deg, color-mix(in srgb, var(--accent-color) 15%, var(--surface-color)), var(--surface-color));
|
|
107
|
+
|
|
108
|
+
border: 1px solid color-mix(in srgb, var(--accent-color) 30%, transparent);
|
|
109
|
+
|
|
110
|
+
box-shadow: 0 8px 20px color-mix(in srgb, var(--accent-color) 20%, transparent);
|
|
111
|
+
|
|
112
|
+
transition: all 0.3s ease;
|
|
113
|
+
// background: linear-gradient(180deg, #f4f7fc 0%, #edf2f9 100%);
|
|
114
|
+
// box-shadow:
|
|
115
|
+
// inset 0 1px 2px rgba(255, 255, 255, 0.8),
|
|
116
|
+
// inset 0 8px 16px rgba(15, 23, 42, 0.08),
|
|
117
|
+
// inset 0 -2px 8px rgba(15, 23, 42, 0.04);
|
|
89
118
|
}
|
|
90
119
|
|
|
91
120
|
.timeline-card .ant-card-body {
|
|
@@ -108,6 +137,35 @@
|
|
|
108
137
|
transition: all 0.3s ease;
|
|
109
138
|
height: 100%;
|
|
110
139
|
overflow-y: auto;
|
|
140
|
+
|
|
141
|
+
scrollbar-width: thin; /* Firefox */
|
|
142
|
+
scrollbar-color: rgba(0, 0, 0, 0.25) transparent;
|
|
143
|
+
|
|
144
|
+
&::-webkit-scrollbar-thumb {
|
|
145
|
+
background: rgba(0, 0, 0, 0);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
&:hover::-webkit-scrollbar-thumb {
|
|
149
|
+
background: rgba(0, 0, 0, 0.25);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
&::-webkit-scrollbar {
|
|
153
|
+
width: 6px;
|
|
154
|
+
height: 6px;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
&::-webkit-scrollbar-track {
|
|
158
|
+
background: transparent;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
&::-webkit-scrollbar-thumb {
|
|
162
|
+
background: rgba(0, 0, 0, 0.25);
|
|
163
|
+
border-radius: 10px;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
&::-webkit-scrollbar-thumb:hover {
|
|
167
|
+
background: rgba(0, 0, 0, 0.4);
|
|
168
|
+
}
|
|
111
169
|
padding-right: 4px;
|
|
112
170
|
}
|
|
113
171
|
|
|
@@ -119,7 +177,10 @@
|
|
|
119
177
|
border-radius: 10px;
|
|
120
178
|
background: #fff;
|
|
121
179
|
padding: 10px 12px;
|
|
122
|
-
transition:
|
|
180
|
+
transition:
|
|
181
|
+
border-color 0.2s ease,
|
|
182
|
+
box-shadow 0.2s ease,
|
|
183
|
+
transform 0.2s ease;
|
|
123
184
|
}
|
|
124
185
|
|
|
125
186
|
.timeline-step:hover {
|
|
@@ -233,7 +294,11 @@
|
|
|
233
294
|
color: #1f3f74;
|
|
234
295
|
box-shadow: 0 6px 14px rgba(15, 23, 42, 0.2);
|
|
235
296
|
z-index: 40;
|
|
236
|
-
transition:
|
|
297
|
+
transition:
|
|
298
|
+
background-color 0.2s ease,
|
|
299
|
+
border-color 0.2s ease,
|
|
300
|
+
box-shadow 0.2s ease,
|
|
301
|
+
transform 0.2s ease;
|
|
237
302
|
|
|
238
303
|
&:hover {
|
|
239
304
|
background: #f0f6ff;
|
|
@@ -265,9 +330,8 @@
|
|
|
265
330
|
MOBILE & TABLET VIEW FIXES
|
|
266
331
|
============================ */
|
|
267
332
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
@media (max-width: 992px) { // iPad & tablets
|
|
333
|
+
@media (max-width: 992px) {
|
|
334
|
+
// iPad & tablets
|
|
271
335
|
.process-steps-page,
|
|
272
336
|
.process-steps-row {
|
|
273
337
|
min-height: auto;
|
|
@@ -275,6 +339,10 @@
|
|
|
275
339
|
overflow: visible;
|
|
276
340
|
}
|
|
277
341
|
|
|
342
|
+
.process-steps-page.consultation-mode {
|
|
343
|
+
padding: 8px;
|
|
344
|
+
}
|
|
345
|
+
|
|
278
346
|
.process-steps-card,
|
|
279
347
|
.process-steps-card .ant-card-body,
|
|
280
348
|
.process-steps-content-col {
|
|
@@ -327,7 +395,8 @@
|
|
|
327
395
|
}
|
|
328
396
|
}
|
|
329
397
|
|
|
330
|
-
@media (max-width: 768px) {
|
|
398
|
+
@media (max-width: 768px) {
|
|
399
|
+
// mobile screens
|
|
331
400
|
.timeline-sidebar {
|
|
332
401
|
gap: 8px;
|
|
333
402
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ui-soxo-bootstrap-core",
|
|
3
|
-
"version": "2.6.
|
|
3
|
+
"version": "2.6.16",
|
|
4
4
|
"description": "All the Core Components for you to start",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"all in one"
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
"i18next": "^22.4.15",
|
|
49
49
|
"i18next-browser-languagedetector": "^7.0.1",
|
|
50
50
|
"jquery": "^3.6.0",
|
|
51
|
+
"libphonenumber-js": "^1.12.39",
|
|
51
52
|
"lint-staged": "^12.3.2",
|
|
52
53
|
"moment-timezone": "^0.5.34",
|
|
53
54
|
"otp-input-react": "^0.3.0",
|