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

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
@@ -111,7 +111,7 @@ const MenuAdd = ({ model, callback, edit, history, formContent, match, additiona
111
111
 
112
112
  let id = formContent.id;
113
113
 
114
- //ONLY set step if it's NOT already provided
114
+ // ONLY set step if it's NOT already provided
115
115
  if (!values.step) {
116
116
  values.step = formContent.step || step;
117
117
  }
@@ -134,7 +134,17 @@ const MenuAdd = ({ model, callback, edit, history, formContent, match, additiona
134
134
  values[field] = value;
135
135
  });
136
136
 
137
- model.add({ values }).then(() => {
137
+ MenusAPI.createMenu({ formBody: values }).then((res) => {
138
+ const apiMessage = res?.result?.message;
139
+
140
+ // (duplicate)
141
+ if (apiMessage?.toLowerCase().includes('already exists')) {
142
+ message.warning(apiMessage);
143
+ setLoading(false);
144
+ return; // stop further execution
145
+ }
146
+
147
+ // Real success
138
148
  message.success('Menu Added');
139
149
  setLoading(false);
140
150
  callback();
@@ -219,9 +229,9 @@ const MenuAdd = ({ model, callback, edit, history, formContent, match, additiona
219
229
  {/* Pages Ends */}
220
230
 
221
231
  {/* Path */}
222
- {/* <Form.Item name="path" label="Path" required>
223
- <Input placeholder="Enter path" />
224
- </Form.Item> */}
232
+ <Form.Item name="path" label="Path" rules={[{ required: true, message: 'Path is required' }]}>
233
+ <Input placeholder="Enter path" />
234
+ </Form.Item>
225
235
  {/* Path Ends */}
226
236
 
227
237
  {/* Route */}
@@ -231,7 +241,7 @@ const MenuAdd = ({ model, callback, edit, history, formContent, match, additiona
231
241
  {/* Route Ends */}
232
242
 
233
243
  {/* Switch */}
234
- <Form.Item name="is_visible" label="Visible" valuePropName="checked" required>
244
+ <Form.Item name="is_visible" label="Visible" valuePropName="checked" required>
235
245
  <Switch />
236
246
  </Form.Item>
237
247
 
@@ -250,7 +260,7 @@ const MenuAdd = ({ model, callback, edit, history, formContent, match, additiona
250
260
  {/* Icon Name Ends */}
251
261
 
252
262
  {/* Step */}
253
- <Form.Item name={'step'} label="Step" rules={[{ required: true, message: 'Step is required' }]}>
263
+ <Form.Item name={'step'} label="Step" required>
254
264
  <InputNumber placeholder="Enter step" />
255
265
  </Form.Item>
256
266
  {/* Step Ends */}
@@ -474,7 +474,7 @@ function panelActions(item, model, setSelectedRecord, setDrawerTitle, setDrawerV
474
474
  </Button>
475
475
  )}
476
476
 
477
- <Button
477
+ {/* <Button
478
478
  size="small"
479
479
  type="default"
480
480
  onClick={() => {
@@ -484,7 +484,7 @@ function panelActions(item, model, setSelectedRecord, setDrawerTitle, setDrawerV
484
484
  }}
485
485
  >
486
486
  <CopyOutlined />
487
- </Button>
487
+ </Button> */}
488
488
 
489
489
  <Popconfirm title="Are you sure?" onConfirm={() => deleteRecord(item)}>
490
490
  <Button danger size="small" type="default">
@@ -160,6 +160,17 @@ class MenusAPI extends Base {
160
160
  });
161
161
  };
162
162
 
163
+ /**
164
+ * create menu
165
+ */
166
+
167
+ createMenu = ({ formBody }) => {
168
+ return ApiUtils.post({
169
+ url: `core-menus/create-menu`,
170
+ formBody,
171
+ });
172
+ };
173
+
163
174
  /**
164
175
  * load the list of menu
165
176
  *
@@ -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.24",
4
4
  "description": "All the Core Components for you to start",
5
5
  "keywords": [
6
6
  "all in one"