ui-soxo-bootstrap-core 2.6.13 → 2.6.15

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.
@@ -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.name}</span>
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.title || menu.caption}</span>
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.title || menu.caption}</span>
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>
@@ -31,10 +31,9 @@ function CommunicationModeSelection({ communicationMode, setCommunicationMode, m
31
31
  </Radio>
32
32
 
33
33
  {/* SMS Option */}
34
- {/* Commented for future use. */}
35
- {/* <Radio checked={communicationMode === 'mobile'} onChange={() => setCommunicationMode('mobile')}>
34
+ <Radio checked={communicationMode === 'mobile'} disabled onChange={() => setCommunicationMode('mobile')}>
36
35
  SMS <MessageOutlined className="otp-icon" style={{ marginLeft: 6 }} />
37
- </Radio> */}
36
+ </Radio>
38
37
  </div>
39
38
 
40
39
  {/* Validation Error */}
@@ -624,28 +624,28 @@ function LoginPhone({ history, appSettings }) {
624
624
  return user.username;
625
625
  };
626
626
 
627
- const { globalCustomerHeader = () => {} } = appSettings;
627
+ const { globalCustomerHeader = () => { } } = appSettings;
628
628
 
629
629
  const themeName = process.env.REACT_APP_THEME; // e.g., 'purple'
630
630
  const isPurple = themeName === 'purple';
631
631
 
632
632
  const sectionStyle = isPurple
633
633
  ? {
634
- width: '100%',
635
- height: '100vh',
636
- backgroundImage: `${state.theme.colors.loginPageBackground}`,
637
- backgroundPosition: 'center bottom, center',
638
- backgroundRepeat: 'no-repeat, no-repeat',
639
- backgroundSize: 'cover, cover',
640
- }
634
+ width: '100%',
635
+ height: '100vh',
636
+ backgroundImage: `${state.theme.colors.loginPageBackground}`,
637
+ backgroundPosition: 'center bottom, center',
638
+ backgroundRepeat: 'no-repeat, no-repeat',
639
+ backgroundSize: 'cover, cover',
640
+ }
641
641
  : {
642
- width: '100%',
643
- height: '100vh',
644
- background: 'linear-gradient(to bottom, #F7F6E3 0%, #EEF1DE 20%, #D5E4DA 45%, #9DBFC8 75%, #4F89A6 100%)',
645
- backgroundPosition: 'center bottom, center',
646
- backgroundRepeat: 'no-repeat, no-repeat',
647
- backgroundSize: 'cover, cover',
648
- };
642
+ width: '100%',
643
+ height: '100vh',
644
+ background: 'linear-gradient(to bottom, #F7F6E3 0%, #EEF1DE 20%, #D5E4DA 45%, #9DBFC8 75%, #4F89A6 100%)',
645
+ backgroundPosition: 'center bottom, center',
646
+ backgroundRepeat: 'no-repeat, no-repeat',
647
+ backgroundSize: 'cover, cover',
648
+ };
649
649
 
650
650
  return (
651
651
  <section className="full-page" style={sectionStyle}>
@@ -835,7 +835,7 @@ function LoginPhone({ history, appSettings }) {
835
835
  // ? 'Your password has expired. Select a preferred communication method to receive the One-Time Password (OTP) to continue.'
836
836
  // : 'Enter your username and Select a preferred communication method to receive the One-Time Password (OTP) to continue..'
837
837
  // }
838
- buttonText={expiredPassword ? 'Send Reset Link' : 'Reset Password'}
838
+ buttonText={expiredPassword ? 'Send Reset Link' : 'Send Reset Link'}
839
839
  />
840
840
  )}
841
841
  </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
+ };
@@ -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.title || menu.caption}</span>
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.title || menu.caption}</span>
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: values.phone1 ?? existing.phone ?? null,
232
- mobile: values.phone1 ?? existing.mobile ?? null,
233
- alternateMobile: values.phone2 ?? existing.alternateMobile ?? null,
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: phoneValidator }]}>
335
- <Input
336
- maxLength={10}
337
- autoComplete="off"
338
- placeholder="Enter Phone Number"
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: phoneValidator }]}>
348
- <Input
349
- maxLength={10}
350
- placeholder="Enter Phone Number"
351
- autoComplete="off"
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
- rules={[
601
- { required: true, message: 'Please enter your mobile number' },
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
- <Input
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: apiData.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,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ui-soxo-bootstrap-core",
3
- "version": "2.6.13",
3
+ "version": "2.6.15",
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",