ui-soxo-bootstrap-core 2.4.25-dev.22 → 2.4.25-dev.23

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.
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import { render, screen, act } from '@testing-library/react';
3
- import { ExternalWindow } from './index';
3
+ import { ExternalWindow } from './external-window';
4
4
 
5
5
  // Mock window.open
6
6
  global.open = jest.fn(() => {
@@ -21,8 +21,8 @@ global.open = jest.fn(() => {
21
21
  removeEventListener: jest.fn(),
22
22
  close: jest.fn(),
23
23
  screen: {
24
- availWidth: 1920,
25
- availHeight: 1080,
24
+ availWidth: 1920,
25
+ availHeight: 1080,
26
26
  },
27
27
  getComputedStyle: jest.fn().mockReturnValue({}),
28
28
  };
@@ -31,16 +31,16 @@ global.open = jest.fn(() => {
31
31
 
32
32
  // Mock getComputedStyle
33
33
  global.getComputedStyle = jest.fn(() => ({
34
- backgroundColor: '#ffffff',
35
- color: '#000000'
34
+ backgroundColor: '#ffffff',
35
+ color: '#000000'
36
36
  }));
37
37
 
38
38
  // Mock message.error
39
39
  jest.mock('antd', () => ({
40
- ...jest.requireActual('antd'),
41
- message: {
42
- error: jest.fn(),
43
- },
40
+ ...jest.requireActual('antd'),
41
+ message: {
42
+ error: jest.fn(),
43
+ },
44
44
  }));
45
45
 
46
46
  describe('ExternalWindow', () => {
@@ -61,11 +61,11 @@ describe('ExternalWindow', () => {
61
61
  it('calls window.close on unmount', () => {
62
62
  const handleClose = jest.fn();
63
63
  const { unmount } = render(<ExternalWindow onClose={handleClose} />);
64
-
64
+
65
65
  const newWindow = global.open.mock.results[0].value;
66
-
66
+
67
67
  unmount(); // cleanup
68
-
68
+
69
69
  expect(newWindow.close).toHaveBeenCalledTimes(1);
70
70
  });
71
71
 
@@ -10,7 +10,7 @@ import RootApplicationAPI from './root-application-api/root-application-api';
10
10
 
11
11
  import { HomePageAPI } from '../modules';
12
12
 
13
- import { ExternalWindow } from './external-window';
13
+ import { ExternalWindow } from './external-window/external-window';
14
14
 
15
15
  export {
16
16
  LandingAPI,
@@ -158,29 +158,32 @@ export default function SideMenu({ loading, modules = [], callback, appSettings,
158
158
 
159
159
  // callback();
160
160
  // };
161
+
161
162
 
162
163
  const onMenuClick = (menu, index) => {
163
164
  const key = menu.path || `menu-${menu.id || index}`;
164
- const parentKey = menu.parentKey;
165
165
 
166
- // Keep submenu highlighted
167
166
  setSelectedKeys([key]);
168
167
 
169
- // Keep parent open
170
-
171
- if (parentKey) {
172
- setOpenKeys((prev) =>
173
- prev.includes(parentKey) ? prev : [...prev, parentKey]
174
- );
175
- }
168
+ if (menu.path) {
169
+ history.push(menu.path);
170
+ }
176
171
 
177
- history.push(menu.path);
178
172
  setMenu(menu);
179
-
180
173
  if (callback) callback();
181
174
  };
175
+
182
176
 
183
177
  const onOpenChange = (keys) => {
178
+ const latestOpenKey = keys.find((k) => !openKeys.includes(k));
179
+
180
+ // If opening a ROOT menu → close other roots
181
+ if (rootSubmenuKeys.includes(latestOpenKey)) {
182
+ setOpenKeys([latestOpenKey]);
183
+ return;
184
+ }
185
+
186
+ // Otherwise (2nd / 3rd level) → keep ALL parents open
184
187
  setOpenKeys(keys);
185
188
  };
186
189
 
@@ -207,6 +210,8 @@ export default function SideMenu({ loading, modules = [], callback, appSettings,
207
210
  // document.documentElement.style.setProperty('--custom-text-color', state.theme.colors.colorText);
208
211
  }, [state.theme]);
209
212
 
213
+ const rootSubmenuKeys = Menus.screenMenus(modules, 'order').map((m) => m.path || m.caption);
214
+
210
215
  return (
211
216
  <div className="sidemenu">
212
217
  {loading ? (
@@ -337,7 +342,7 @@ export default function SideMenu({ loading, modules = [], callback, appSettings,
337
342
  let sub_menus = menu && menu.sub_menus ? Menus.screenMenus(menu.sub_menus) : [];
338
343
 
339
344
  if (menu && sub_menus && sub_menus.length) {
340
- let randomIndex = parseInt(Math.random() * 10000000000);
345
+ // let randomIndex = parseInt(Math.random() * 10000000000);
341
346
 
342
347
  return (
343
348
  <SubMenu
@@ -358,7 +363,7 @@ export default function SideMenu({ loading, modules = [], callback, appSettings,
358
363
  }
359
364
  >
360
365
  {sub_menus.map((submenu, innerIndex) => {
361
- let randomIndex = parseInt(Math.random() * 10000000000);
366
+ // let randomIndex = parseInt(Math.random() * 10000000000);
362
367
 
363
368
  let third_menus = submenu && submenu.sub_menus ? Menus.screenMenus(submenu.sub_menus) : [];
364
369
 
@@ -381,7 +386,7 @@ export default function SideMenu({ loading, modules = [], callback, appSettings,
381
386
  }
382
387
  >
383
388
  {third_menus.map((menu) => {
384
- let randomIndex = parseInt(Math.random() * 10000000000);
389
+ // let randomIndex = parseInt(Math.random() * 10000000000);
385
390
 
386
391
  return (
387
392
  <Menu.Item
@@ -407,7 +412,7 @@ export default function SideMenu({ loading, modules = [], callback, appSettings,
407
412
  </SubMenu>
408
413
  );
409
414
  } else {
410
- let randomIndex = parseInt(Math.random() * 10000000000);
415
+ // let randomIndex = parseInt(Math.random() * 10000000000);
411
416
 
412
417
  return (
413
418
  <Menu.Item
@@ -433,7 +438,7 @@ export default function SideMenu({ loading, modules = [], callback, appSettings,
433
438
  </SubMenu>
434
439
  );
435
440
  } else {
436
- let randomIndex = parseInt(Math.random() * 10000000000);
441
+ // let randomIndex = parseInt(Math.random() * 10000000000);
437
442
 
438
443
  return (
439
444
  <Menu.Item
@@ -43,9 +43,11 @@ const StaffAdd = ({ visible, onCancel, staffId, staffData, onSuccess }) => {
43
43
  setIsSubmitDisabled(!(watchedName && watchedCode && watchedDesc));
44
44
  }, [watchedName, watchedCode, watchedDesc]);
45
45
 
46
- // Load designations when modal opens
46
+ // Load designations and staff list when modal opens
47
47
  useEffect(() => {
48
- if (visible) getDesignations();
48
+ if (visible) {
49
+ getDesignations();
50
+ }
49
51
  }, [visible]);
50
52
 
51
53
  // Reset or fill edit form
@@ -93,61 +95,61 @@ const StaffAdd = ({ visible, onCancel, staffId, staffData, onSuccess }) => {
93
95
  .finally(() => setLoading(false));
94
96
  };
95
97
 
98
+
96
99
  /** -------------------------------
97
- * VALIDATION – Code
100
+ * VALIDATION – Input
98
101
  * ------------------------------- */
99
- const validateDoctorCode = async (_, value) => {
100
- if (editMode) return Promise.resolve(); // Skip validation if editing
101
-
102
- if (!value) {
103
- setCodeStatus({ status: '', message: '' });
104
- return Promise.resolve();
105
- }
106
-
107
- // Starting space not allowed
108
- if (value.startsWith(' ')) {
109
- setCodeStatus({
110
- status: 'error',
111
- message: 'Code cannot start with a space',
112
- });
113
- return Promise.reject(new Error('Code cannot start with a space'));
114
- }
115
-
116
- // Any space not allowed
117
- if (/\s/.test(value)) {
118
- setCodeStatus({
119
- status: 'error',
120
- message: 'Spaces are not allowed in the code',
121
- });
122
- return Promise.reject(new Error('Spaces are not allowed in the code'));
102
+ const validateInput = async (rule, value) => {
103
+ // Common check: Starting space
104
+ if (value && value.startsWith(' ')) {
105
+ const msg = rule.message || 'Cannot start with a space';
106
+ if (rule.field === 'id') {
107
+ setCodeStatus({ status: 'error', message: msg });
108
+ }
109
+ return Promise.reject(new Error(msg));
123
110
  }
124
111
 
125
- try {
126
- const res = await UsersAPI.getStaffCode(value);
127
- console.log(res.message, ' inside validateDoctorCode');
112
+ // Specific check: Code (id)
113
+ if (rule.field === 'id') {
114
+ if (editMode) return Promise.resolve();
128
115
 
129
- if (res?.status === 409 || res?.success === false) {
130
- setCodeStatus({ status: 'error', message: res.message || 'Code already exists' });
131
- console.log('here');
132
- return Promise.reject(new Error(res.message || 'Code already exists'));
116
+ if (!value) {
117
+ setCodeStatus({ status: '', message: '' });
118
+ return Promise.resolve();
133
119
  }
134
120
 
135
- if (res?.status === 200 && res?.success === true) {
136
- setCodeStatus({
137
- status: 'success',
138
- message: 'The entered code is valid for assigning a staff.',
139
- });
140
- return Promise.resolve();
121
+ // Any space check
122
+ if (/\s/.test(value)) {
123
+ setCodeStatus({ status: 'error', message: 'Spaces are not allowed in the code' });
124
+ return Promise.reject(new Error('Spaces are not allowed in the code'));
141
125
  }
142
126
 
143
- setCodeStatus({ status: 'error', message: 'Please enter a valid code' });
144
- return Promise.reject(new Error('Please enter a valid code'));
145
- } catch {
146
- setCodeStatus({ status: 'error', message: 'Validation failed' });
147
- return Promise.reject(new Error('Validation failed'));
127
+ // API Check
128
+ try {
129
+ const res = await UsersAPI.getStaffCode(value);
130
+
131
+ if (res?.status === 409 || res?.success === false) {
132
+ setCodeStatus({ status: 'error', message: res.message || 'Code already exists' });
133
+ return Promise.reject(new Error(res.message || 'Code already exists'));
134
+ }
135
+
136
+ if (res?.status === 200 && res?.success === true) {
137
+ setCodeStatus({ status: 'success', message: 'The entered code is valid for assigning a staff.' });
138
+ return Promise.resolve();
139
+ }
140
+
141
+ setCodeStatus({ status: 'error', message: 'Please enter a valid code' });
142
+ return Promise.reject(new Error('Please enter a valid code'));
143
+ } catch {
144
+ setCodeStatus({ status: 'error', message: 'Validation failed' });
145
+ return Promise.reject(new Error('Validation failed'));
146
+ }
148
147
  }
148
+
149
+ return Promise.resolve();
149
150
  };
150
151
 
152
+
151
153
  /** -------------------------------
152
154
  * UTIL – Enter Key Navigation
153
155
  * ------------------------------- */
@@ -171,8 +173,8 @@ const StaffAdd = ({ visible, onCancel, staffId, staffData, onSuccess }) => {
171
173
  shortName: data.shortName,
172
174
  description: data.description,
173
175
  designation: data.designationPtr,
174
- phone1: data.phone,
175
- phone2: data.mobile || data.alternateMobile,
176
+ phone1: data.phone || data.mobile,
177
+ phone2: data.alternateMobile,
176
178
  email1: data.email,
177
179
  email2: data.alternateEmail,
178
180
  slno: data.slNo,
@@ -249,19 +251,38 @@ const StaffAdd = ({ visible, onCancel, staffId, staffData, onSuccess }) => {
249
251
  name="id"
250
252
  validateTrigger="onChange"
251
253
  hasFeedback={!editMode}
252
- rules={[{ required: true, message: 'Code is required' }, { validator: validateDoctorCode }]}
254
+ rules={[
255
+ { required: true, message: 'Code is required' },
256
+ { validator: validateInput },
257
+ ]}
253
258
  >
254
259
  <Input placeholder="Enter Code" autoComplete="off" maxLength={10} ref={nameInputRef} onKeyDown={handleEnterKey} disabled={editMode} />
255
260
  </Form.Item>
256
261
  </Col>
257
262
 
258
263
  <Col span={12}>
259
- <Form.Item label="Name" name="description" rules={[{ required: true }]}>
264
+ <Form.Item
265
+ label="Name"
266
+ name="description"
267
+ rules={[
268
+ { required: true, message: 'Name is required' },
269
+ { pattern: /^[A-Za-z\s]+$/, message: 'Name should contain only alphabets' },
270
+ { validator: validateInput, message: 'Name cannot start with a space' },
271
+ ]}
272
+ >
260
273
  <Input placeholder="Enter Description" autoComplete="off" onKeyDown={handleEnterKey} />
261
274
  </Form.Item>
262
275
  </Col>
263
276
  <Col span={6}>
264
- <Form.Item label="Short Name" name="shortName" rules={[{ required: true }]}>
277
+ <Form.Item
278
+ label="Short Name"
279
+ name="shortName"
280
+ rules={[
281
+ { required: true, message: 'Short Name is required' },
282
+ { pattern: /^[A-Za-z\s]+$/, message: 'Name should contain only alphabets' },
283
+ { validator: validateInput, message: 'Short Name cannot start with a space' },
284
+ ]}
285
+ >
265
286
  <Input placeholder="Enter Short Name" autoComplete="off" onKeyDown={handleEnterKey} maxLength={10} />
266
287
  </Form.Item>
267
288
  </Col>
@@ -309,13 +330,13 @@ const StaffAdd = ({ visible, onCancel, staffId, staffData, onSuccess }) => {
309
330
  </Col>
310
331
 
311
332
  <Col span={6}>
312
- <Form.Item label="Email ID" name="email1">
333
+ <Form.Item label="Email ID" name="email1" rules={[{ type: 'email', message: 'Please enter a valid email' }]}>
313
334
  <Input autoComplete="off" placeholder="Enter Email" onKeyDown={handleEnterKey} />
314
335
  </Form.Item>
315
336
  </Col>
316
337
 
317
338
  <Col span={6}>
318
- <Form.Item label="Alternate Email" name="email2">
339
+ <Form.Item label="Alternate Email" name="email2" rules={[{ type: 'email', message: 'Please enter a valid email' }]}>
319
340
  <Input autoComplete="off" placeholder="Enter Email" onKeyDown={handleEnterKey} />
320
341
  </Form.Item>
321
342
  </Col>
@@ -330,7 +351,7 @@ const StaffAdd = ({ visible, onCancel, staffId, staffData, onSuccess }) => {
330
351
 
331
352
  <Col span={8}>
332
353
  <Form.Item label="Serial Number" name="slno" rules={[{ message: 'only numbers are allowed', pattern: /^\d*$/ }]}>
333
- <Input autoComplete="off" maxLength={5} placeholder="Enter Serial Number" onKeyDown={handleEnterKey} />
354
+ <Input autoComplete="off" maxLength={5} placeholder="Enter Serial Number" onKeyDown={handleEnterKey} disabled={editMode} />
334
355
  </Form.Item>
335
356
  </Col>
336
357
 
@@ -352,7 +373,14 @@ const StaffAdd = ({ visible, onCancel, staffId, staffData, onSuccess }) => {
352
373
 
353
374
  <Row>
354
375
  <Col span={24}>
355
- <Form.Item label="Remarks" name="remarks">
376
+ <Form.Item
377
+ label="Remarks"
378
+ name="remarks"
379
+ rules={[
380
+ { max: 50, message: 'Remarks must be less than 50 characters' },
381
+ { validator: validateInput, message: 'Remarks cannot start with a space' },
382
+ ]}
383
+ >
356
384
  <Input autoComplete="off" placeholder="Enter Remarks" onKeyDown={handleEnterKey} />
357
385
  </Form.Item>
358
386
  </Col>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ui-soxo-bootstrap-core",
3
- "version": "2.4.25-dev.22",
3
+ "version": "2.4.25-dev.23",
4
4
  "description": "All the Core Components for you to start",
5
5
  "keywords": [
6
6
  "all in one"