ui-soxo-bootstrap-core 2.4.24 → 2.4.25-dev.7

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.
Files changed (33) hide show
  1. package/.github/workflows/npm-publish.yml +37 -15
  2. package/README.md +260 -0
  3. package/core/components/extra-info/extra-info-details.js +109 -126
  4. package/core/components/landing-api/landing-api.js +22 -30
  5. package/core/lib/Store.js +20 -18
  6. package/core/lib/components/index.js +4 -1
  7. package/core/lib/components/sidemenu/sidemenu.js +153 -256
  8. package/core/lib/components/sidemenu/sidemenu.scss +39 -26
  9. package/core/lib/hooks/index.js +2 -12
  10. package/core/lib/hooks/use-otp-timer.js +99 -0
  11. package/core/lib/pages/login/login.js +255 -139
  12. package/core/lib/pages/login/login.scss +140 -32
  13. package/core/models/dashboard/dashboard.js +14 -0
  14. package/core/models/doctor/components/doctor-add/doctor-add.js +403 -0
  15. package/core/models/doctor/components/doctor-add/doctor-add.scss +32 -0
  16. package/core/models/menus/components/menu-add/menu-add.js +230 -268
  17. package/core/models/menus/components/menu-lists/menu-lists.js +126 -89
  18. package/core/models/menus/components/menu-lists/menu-lists.scss +9 -0
  19. package/core/models/menus/menus.js +247 -267
  20. package/core/models/roles/components/role-add/role-add.js +269 -227
  21. package/core/models/roles/components/role-list/role-list.js +8 -6
  22. package/core/models/roles/roles.js +182 -174
  23. package/core/models/users/components/user-add/user-add.js +619 -365
  24. package/core/models/users/components/user-add/user-edit.js +90 -0
  25. package/core/models/users/users.js +261 -165
  26. package/core/modules/index.js +5 -8
  27. package/core/modules/reporting/components/index.js +5 -0
  28. package/core/modules/reporting/components/reporting-dashboard/reporting-dashboard.js +65 -2
  29. package/core/modules/steps/action-buttons.js +79 -0
  30. package/core/modules/steps/steps.js +553 -0
  31. package/core/modules/steps/steps.scss +158 -0
  32. package/core/modules/steps/timeline.js +49 -0
  33. package/package.json +2 -2
@@ -5,17 +5,29 @@ body {
5
5
  .user-authentication-section {
6
6
  min-height: 90vh;
7
7
  display: flex;
8
- margin: 0px 15%;
8
+ // margin: 0px 15%;
9
9
  justify-content: center;
10
+ padding: 0px 15%;
10
11
  // align-items: center;
11
12
 
13
+ @media (max-width: 1200px) {
14
+ margin: 0 5%;
15
+ }
16
+
17
+ @media (max-width: 768px) {
18
+ margin: 0 10px;
19
+ padding-top: 20px;
20
+ flex-direction: column;
21
+ align-items: center;
22
+ }
23
+
12
24
  .page-background {
13
- position: absolute;
25
+ position: absolute;
14
26
  left: 0px;
15
27
  top: 0px;
16
- height:40vh;
28
+ height: 40vh;
17
29
  width: 100%;
18
-
30
+
19
31
  // background-image:url("./../../../assets/images/vector.png"); /* Set Image */ ;
20
32
  // background: url("./../../../assets/images/vector.png"),
21
33
  // linear-gradient(to right, #D5F1FB, #24AEB8);
@@ -121,7 +133,7 @@ body {
121
133
  border: 1px solid #b7b7b7;
122
134
  text-align: center;
123
135
  border-radius: 2px;
124
- margin: 16px 0px;
136
+ margin: 22px 0px;
125
137
  padding: 4px;
126
138
 
127
139
  img {
@@ -144,21 +156,22 @@ body {
144
156
  // box-shadow: 0 2px 4px rgba(104, 97, 97, 0.5);
145
157
  // display: flex;
146
158
  // align-items: center;
147
- border-radius: 12px;
159
+ border-radius: 16px;
148
160
  background-color: white;
149
161
  overflow: hidden;
150
162
  border: 1px solid #e8e8e8;
151
163
 
152
- width: 40%;
164
+ width: 42%;
153
165
 
154
166
  @media only screen and (max-width: 768px) {
155
- width: 95%;
167
+ width: 100%;
168
+ padding: 0 16px;
156
169
 
157
- display: flex;
158
- flex-direction: column;
159
- justify-content: center;
160
- text-align: center;
161
- align-items: center;
170
+ // display: flex;
171
+ // flex-direction: column;
172
+ // justify-content: center;
173
+ // text-align: center;
174
+ // align-items: center;
162
175
  }
163
176
 
164
177
  .login-form-container {
@@ -166,20 +179,24 @@ body {
166
179
  flex-basis: 50%;
167
180
  // border: none !important;
168
181
  // box-shadow: none !important;
169
- padding: 20px 30px;
182
+ // padding: 20px 30px;
183
+ padding: 20px;
184
+ padding-top: 2px;
170
185
 
171
186
  @media only screen and (max-width: 768px) {
172
- display: flex;
173
- flex-direction: column;
174
- justify-content: center;
175
- text-align: center;
176
- align-items: center;
187
+ padding: 16px;
188
+ // display: flex;
189
+ // flex-direction: column;
190
+ // justify-content: center;
191
+ // text-align: center;
192
+ // align-items: center;
177
193
  }
178
194
 
179
195
  .branch-switcher {
180
196
  .branches {
181
197
  .ant-select {
182
- min-width: 150px;
198
+ min-width: auto !important;
199
+ width: 100%;
183
200
  }
184
201
  }
185
202
  }
@@ -217,7 +234,7 @@ body {
217
234
  .ant-form {
218
235
  // height: 100%;
219
236
  .ant-form-item {
220
- margin: 30px 0px;
237
+ margin: 20px 0px;
221
238
  .ant-form-item-label {
222
239
  margin: 0 0 8px 0;
223
240
  line-height: 0;
@@ -249,9 +266,13 @@ body {
249
266
  width: 100%;
250
267
  }
251
268
  }
269
+ .otp-container {
270
+ display: flex;
271
+ }
252
272
 
253
273
  .otp-input-container p {
254
- margin: 20px 0 -10px 0;
274
+ margin: 12px 0 0 0;
275
+ font-size: 12px;
255
276
  }
256
277
 
257
278
  .otp-input-container .otp-title {
@@ -259,13 +280,17 @@ body {
259
280
  text-align: left;
260
281
  }
261
282
  }
283
+ .ant-divider-horizontal {
284
+ margin-bottom: 16px;
285
+ margin-top: 2px;
286
+ }
262
287
 
263
288
  .otp-input-container .resend-otp-link {
264
289
  display: flex;
265
290
  justify-content: center;
266
291
  align-items: center;
267
292
  text-align: center;
268
- margin-top: -10px;
293
+ // margin-top: -10px;
269
294
  color: #0c66e4;
270
295
  margin-bottom: 10px;
271
296
  }
@@ -292,7 +317,58 @@ body {
292
317
  .otp-input-container {
293
318
  display: flex;
294
319
  flex-direction: column;
295
- gap: 20px;
320
+ gap: 2px;
321
+ }
322
+ .otp-actions {
323
+ background-color: #f5f5f5; // light gray
324
+ padding: 12px 16px;
325
+ display: flex;
326
+ justify-content: space-between;
327
+ gap: 6px;
328
+ // margin-top: 6px;
329
+ }
330
+ .resend-action {
331
+ margin-bottom: 8px;
332
+ display: flex;
333
+ gap: 6px;
334
+ }
335
+ .otp-method-section {
336
+ // margin-top: 10px;
337
+
338
+ display: flex;
339
+ flex-direction: column;
340
+ gap: 6px;
341
+
342
+ .otp-method-title {
343
+ font-size: 16px;
344
+ font-weight: 600;
345
+ margin-bottom: 8px;
346
+ }
347
+
348
+ .otp-method-group {
349
+ display: flex;
350
+ align-items: center;
351
+ margin-bottom: 10px;
352
+ gap: 30px;
353
+ font-size: 12px;
354
+ }
355
+
356
+ .ant-radio-wrapper {
357
+ display: flex;
358
+ align-items: center;
359
+ gap: 6px;
360
+
361
+ svg {
362
+ font-size: 18px;
363
+ opacity: 0.8;
364
+ }
365
+ }
366
+
367
+ .otp-mode-error {
368
+ margin: 5px;
369
+ font-size: 13px;
370
+ color: red;
371
+ }
296
372
  }
297
373
 
298
374
  .otp-mode-text {
@@ -308,25 +384,25 @@ input {
308
384
  input:focus {
309
385
  border-color: #fff !important;
310
386
  }
311
- .otp-container{
387
+ .otp-container {
312
388
  display: flex;
313
- flex-direction:row;
389
+ flex-direction: column;
314
390
  gap: 7px;
315
391
  }
316
392
 
317
- .otp-container input[type="text"]:focus {
393
+ .otp-container input[type='text']:focus {
318
394
  border-color: #fff !important;
319
395
  }
320
396
 
321
397
  .full-page {
322
398
  height: 100vh;
323
- background-color: "cover"; // Ensures full coverage
399
+ background-color: 'cover'; // Ensures full coverage
324
400
  background-position: 'bottom center'; // Aligns image at the bottom
325
- background-size: '100% auto';// image spans full width
326
- background-repeat:no-repeat ;
327
-
401
+ background-size: '100% auto'; // image spans full width
402
+ background-repeat: no-repeat;
403
+
328
404
  min-height: 100vh; // Ensures full height
329
- width: 100%, // Covers the full width
405
+ width: 100%; // Covers the full width
330
406
  }
331
407
  .ant-input {
332
408
  background-color: transparent !important;
@@ -344,3 +420,35 @@ input:focus {
344
420
  // background-color: var(--custom-input-bg-color);
345
421
  // color: var(--custom-input-color);
346
422
  // }
423
+ /* Mobile: stack OTP radio options in separate rows */
424
+ /* Mobile phones (iPhone) – stack in one column */
425
+ @media only screen and (max-width: 600px) {
426
+ .otp-method-group {
427
+ flex-direction: column !important;
428
+ align-items: flex-start !important;
429
+ gap: 12px !important;
430
+ }
431
+ }
432
+
433
+ /* iPad Mini + Tablets (600px to 1024px) – show in row */
434
+ @media only screen and (min-width: 601px) and (max-width: 1024px) {
435
+ .otp-method-group {
436
+ flex-direction: row !important;
437
+ align-items: center !important;
438
+ justify-content: space-between !important;
439
+ gap: 20px !important;
440
+ width: 100%;
441
+ }
442
+
443
+ .otp-method-group .ant-radio-wrapper {
444
+ flex: 1; /* evenly spaced columns */
445
+ }
446
+ }
447
+
448
+ /* Desktop – normal layout */
449
+ @media only screen and (min-width: 1025px) {
450
+ .otp-method-group {
451
+ flex-direction: row;
452
+ gap: 30px;
453
+ }
454
+ }
@@ -81,6 +81,20 @@ class Dashboard extends Base {
81
81
  ];
82
82
  }
83
83
 
84
+ loadProcess(id) {
85
+ return ApiUtils.get({
86
+ url: `process/${id}`,
87
+ });
88
+ }
89
+
90
+ processLog(formBody) {
91
+ return ApiUtils.post({
92
+ url: `process/process-log`,
93
+ formBody,
94
+ });
95
+ }
96
+
97
+
84
98
  /**
85
99
  * Function to load dashboards based on user information
86
100
  * @param {*} user
@@ -0,0 +1,403 @@
1
+ import React, { useState, useEffect, useRef } from 'react';
2
+ import './doctor-add.scss';
3
+ import { Modal, Select, Tabs, Input, Form, Row, Col, message, Checkbox } from 'antd';
4
+ import { useTranslation, Button } from './../../../../lib/';
5
+
6
+ import { UsersAPI } from '../../..';
7
+ import SignatureCanvasComponent from '../../../../lib/components/consent/signature-pad';
8
+
9
+ const DoctorAdd = ({ visible, onCancel, attributes, doctorId, doctorData, onSuccess }) => {
10
+ const [form] = Form.useForm();
11
+ const { t } = useTranslation();
12
+
13
+ // Signature states
14
+ const [fileUrl, setFileUrl] = useState(null);
15
+ const [editingSignature, setEditingSignature] = useState(false);
16
+ const [previousSignature, setPreviousSignature] = useState(null);
17
+
18
+ // submit state
19
+ const [isSubmitDisabled, setIsSubmitDisabled] = useState(true);
20
+ const [loading, setLoading] = useState(false);
21
+ const [doctorID, setDoctorID] = useState(false);
22
+ const [selectedDoctor, setSelectedDoctor] = useState(null);
23
+
24
+ const propValues = attributes || {};
25
+ const doctorType = propValues?.type;
26
+
27
+ const editMode = Boolean(doctorId);
28
+
29
+ const nameInputRef = useRef(null);
30
+
31
+ const watchedName = Form.useWatch('name', form);
32
+ const watchedCode = Form.useWatch('code', form);
33
+ const watchedSignature = Form.useWatch('signature', form);
34
+
35
+ const [codeStatus, setCodeStatus] = useState({
36
+ status: '',
37
+ message: '',
38
+ });
39
+
40
+ /** Autofocus on modal open */
41
+ useEffect(() => {
42
+ if (visible) {
43
+ setTimeout(() => {
44
+ nameInputRef.current?.focus();
45
+ }, 200);
46
+ }
47
+ }, [visible]);
48
+
49
+ /** Enable/disable submit */
50
+ useEffect(() => {
51
+ if (watchedName && watchedCode && watchedSignature) {
52
+ setIsSubmitDisabled(false);
53
+ } else {
54
+ setIsSubmitDisabled(true);
55
+ }
56
+ }, [watchedName, watchedCode, watchedSignature]);
57
+
58
+ /** Enter key jump */
59
+ const handleEnterKey = (e) => {
60
+ if (e.key === 'Enter') {
61
+ e.preventDefault();
62
+ const fields = Array.from(document.querySelectorAll('.ant-input, .ant-select-selector'));
63
+ const index = fields.indexOf(e.target);
64
+ if (index > -1 && index < fields.length - 1) {
65
+ const nextField = fields[index + 1];
66
+ nextField.focus();
67
+ if (nextField.classList.contains('ant-select-selector')) nextField.click();
68
+ }
69
+ }
70
+ };
71
+
72
+ /** Fill form when editing */
73
+ const fillEditForm = (data) => {
74
+ if (!data) return;
75
+
76
+ form.setFieldsValue({
77
+ name: data.name,
78
+ code: data.code,
79
+ qualification: data.qualification,
80
+ designation: data.designation,
81
+ type: data.type,
82
+ regno1: data.regno1,
83
+ remarks: data.remarks,
84
+ active: data.active === 'Y' ? 'Y' : 'N',
85
+ address: data.address1,
86
+ places: data.place,
87
+ pincode: data.zip,
88
+ phone_number: data.phone,
89
+ alternate_phone: data.mobile,
90
+ email_id: data.email,
91
+ fax: data.fax,
92
+ });
93
+
94
+ if (data.signature) {
95
+ setFileUrl(data.signature);
96
+ form.setFieldValue('signature', data.signature);
97
+ }
98
+ };
99
+
100
+ /** Code validation */
101
+ const validateDoctorCode = async (_, value) => {
102
+ if (editMode) return Promise.resolve();
103
+ if (!value) {
104
+ setCodeStatus({ status: '', message: '' });
105
+ return Promise.resolve();
106
+ }
107
+
108
+ if (editMode && doctorData?.do_code === value) {
109
+ setCodeStatus({ status: 'success', message: 'Code is unchanged' });
110
+ return Promise.resolve();
111
+ }
112
+
113
+ try {
114
+ const res = await UsersAPI.getDoctorCode(value);
115
+
116
+ if (res?.statusCode === 409 || res?.success === false) {
117
+ setCodeStatus({ status: 'error', message: res.message || 'Code already exists' });
118
+ return Promise.reject(new Error(res.message || 'Code already exists'));
119
+ }
120
+
121
+ if (res?.statusCode === 200 && res?.success === true) {
122
+ setCodeStatus({
123
+ status: 'success',
124
+ message: 'The entered code is used for assigning a doctor.',
125
+ });
126
+ return Promise.resolve();
127
+ }
128
+
129
+ setCodeStatus({ status: 'error', message: 'Unexpected response' });
130
+ return Promise.reject(new Error('Unexpected response'));
131
+ } catch {
132
+ setCodeStatus({ status: 'error', message: 'Validation failed' });
133
+ return Promise.reject(new Error('Validation failed'));
134
+ }
135
+ };
136
+
137
+ /** Reset when closing */
138
+ useEffect(() => {
139
+ if (visible) {
140
+ if (editMode) fillEditForm(doctorData);
141
+ } else {
142
+ form.resetFields();
143
+ setFileUrl(null);
144
+ setPreviousSignature(null);
145
+ setEditingSignature(false);
146
+ }
147
+ }, [visible, editMode, doctorData]);
148
+
149
+ /** Submit handler */
150
+ const handleFinish = async (values) => {
151
+ setLoading(true);
152
+
153
+ const payload = {
154
+ dbkey: values.dbkey ? Number(values.dbkey) : null,
155
+ code: values.code || '',
156
+ name: values.name || '',
157
+ regno1: values.regno1 || '',
158
+ regno2: '',
159
+ qualification: values.qualification || '',
160
+ desg: values.designation || '',
161
+ add1: values.address || '',
162
+ add2: '',
163
+ place: values.places || '',
164
+ phone: values.phone_number || '',
165
+ fax: values.fax || '',
166
+ mobile: values.alternate_phone || '',
167
+ email: values.email_id || '',
168
+ remarks: values.remarks || '',
169
+ deptptr: '',
170
+ regfee: values.regfee ? Number(values.regfee) : null,
171
+ consfee: values.consfee ? Number(values.consfee) : null,
172
+ gpfee: values.gpfee ? Number(values.gpfee) : null,
173
+ consdays: values.consdays ? Number(values.consdays) : null,
174
+ slno: values.slno ? Number(values.slno) : null,
175
+ active: values.active === 'Y' ? 'Y' : 'N',
176
+ timeperpatient: values.timeperpatient ? Number(values.timeperpatient) : null,
177
+ username: '',
178
+ type: values.type || '',
179
+ signature: fileUrl || '',
180
+ zip: values.pincode || '',
181
+ };
182
+
183
+ try {
184
+ let res;
185
+ if (editMode) {
186
+ res = await UsersAPI.updateDoctors(payload, doctorData.code);
187
+ } else {
188
+ res = await UsersAPI.createDoctors(payload);
189
+ }
190
+
191
+ if (res?.success) {
192
+ message.success(editMode ? 'Doctor updated successfully' : 'Doctor created successfully');
193
+ onSuccess?.();
194
+
195
+ onCancel();
196
+ } else {
197
+ message.error(res?.message || 'Operation failed');
198
+ }
199
+ } catch {
200
+ message.error('Something went wrong');
201
+ } finally {
202
+ setLoading(false);
203
+ }
204
+ };
205
+
206
+ return (
207
+ <>
208
+ <Modal
209
+ title={doctorId ? 'Edit Doctor' : 'Add Doctor'}
210
+ open={visible}
211
+ onCancel={onCancel}
212
+ width={950}
213
+ footer={null}
214
+ destroyOnClose
215
+ style={{ top: 10 }}
216
+ >
217
+ <Form form={form} layout="vertical" onFinish={handleFinish}>
218
+ {/* NAME + CODE */}
219
+ <Row gutter={16}>
220
+ <Col span={12}>
221
+ <Form.Item label="Name" name="name" rules={[{ required: true }]}>
222
+ <Input autoComplete="off" ref={nameInputRef} placeholder="Enter Name" onKeyDown={handleEnterKey} />
223
+ </Form.Item>
224
+ </Col>
225
+
226
+ <Col span={12}>
227
+ <Form.Item
228
+ label="Code"
229
+ name="code"
230
+ validateTrigger="onChange"
231
+ hasFeedback={!editMode}
232
+ rules={[{ required: true }, { validator: validateDoctorCode }]}
233
+ >
234
+ <Input autoComplete="off" placeholder="Enter Code" onKeyDown={handleEnterKey} disabled={editMode} />
235
+ </Form.Item>
236
+ </Col>
237
+ </Row>
238
+
239
+ <Tabs defaultActiveKey="1">
240
+ {/* GENERAL */}
241
+ <Tabs.TabPane tab="GENERAL" key="1">
242
+ <Row gutter={16}>
243
+ <Col span={8}>
244
+ <Form.Item label="Type" name="type">
245
+ <Select placeholder="Select Type" options={doctorType || []} showSearch />
246
+ </Form.Item>
247
+ </Col>
248
+
249
+ <Col span={8}>
250
+ <Form.Item label="Qualification" name="qualification">
251
+ <Input placeholder="Enter Qualification" onKeyDown={handleEnterKey} />
252
+ </Form.Item>
253
+ </Col>
254
+
255
+ <Col span={8}>
256
+ <Form.Item label="Designation" name="designation">
257
+ <Input placeholder="Enter Designation" onKeyDown={handleEnterKey} />
258
+ </Form.Item>
259
+ </Col>
260
+ </Row>
261
+
262
+ <Row gutter={16}>
263
+ <Col span={6}>
264
+ <Form.Item label="Reg. No." name="regno1">
265
+ <Input placeholder="Enter Reg No" onKeyDown={handleEnterKey} />
266
+ </Form.Item>
267
+ </Col>
268
+
269
+ <Col span={8}>
270
+ <Form.Item label="Remarks" name="remarks">
271
+ <Input placeholder="Enter Remarks" onKeyDown={handleEnterKey} />
272
+ </Form.Item>
273
+ </Col>
274
+
275
+ <Col span={8}>
276
+ <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginTop: 30 }}>
277
+ <Form.Item
278
+ name="active"
279
+ valuePropName="checked"
280
+ getValueFromEvent={(e) => (e.target.checked ? 'Y' : 'N')}
281
+ getValueProps={(value) => ({ checked: value === 'Y' })}
282
+ style={{ marginBottom: 0 }}
283
+ >
284
+ <Checkbox>Active</Checkbox>
285
+ </Form.Item>
286
+ </div>
287
+ </Col>
288
+ </Row>
289
+
290
+ {/* SIGNATURE (DRAW ONLY) */}
291
+ <Form.Item name="signature" label="Signature">
292
+ {/* Preview */}
293
+ {fileUrl && !editingSignature && (
294
+ <div className="signature-preview">
295
+ <img src={fileUrl} alt="signature" className="signature-image" />
296
+
297
+ <div style={{ marginTop: 10 }}>
298
+ <Button
299
+ type="primary"
300
+ onClick={() => {
301
+ setEditingSignature(true);
302
+ setPreviousSignature(fileUrl);
303
+ setFileUrl(null);
304
+ form.setFieldValue('signature', null);
305
+ }}
306
+ >
307
+ Redraw Signature
308
+ </Button>
309
+ </div>
310
+ </div>
311
+ )}
312
+
313
+ {/* Drawing Canvas */}
314
+ {(editingSignature || !fileUrl) && (
315
+ <SignatureCanvasComponent
316
+ onSaveAndAddSignature={(sig) => {
317
+ setFileUrl(sig);
318
+ setEditingSignature(false);
319
+ setPreviousSignature(null);
320
+ form.setFieldValue('signature', sig);
321
+ }}
322
+ onClear={() => {
323
+ if (previousSignature) {
324
+ setFileUrl(previousSignature);
325
+ } else {
326
+ setFileUrl(null);
327
+ form.setFieldValue('signature', null);
328
+ }
329
+ setEditingSignature(false);
330
+ }}
331
+ />
332
+ )}
333
+ </Form.Item>
334
+ </Tabs.TabPane>
335
+
336
+ {/* ADDRESS */}
337
+ <Tabs.TabPane tab="ADDRESS" key="2">
338
+ <Row gutter={16}>
339
+ <Col span={8}>
340
+ <Form.Item label="Address" name="address">
341
+ <Input placeholder="Enter Address" onKeyDown={handleEnterKey} />
342
+ </Form.Item>
343
+ </Col>
344
+
345
+ <Col span={8}>
346
+ <Form.Item label="Places" name="places">
347
+ <Input placeholder="Enter Place" onKeyDown={handleEnterKey} />
348
+ </Form.Item>
349
+ </Col>
350
+
351
+ <Col span={8}>
352
+ <Form.Item label="Pincode" name="pincode">
353
+ <Input placeholder="Enter Pincode" onKeyDown={handleEnterKey} maxLength={7} />
354
+ </Form.Item>
355
+ </Col>
356
+ </Row>
357
+
358
+ <Row gutter={16}>
359
+ <Col span={8}>
360
+ <Form.Item label="Phone Number" name="phone_number">
361
+ <Input placeholder="Enter Phone Number" onKeyDown={handleEnterKey} maxLength={10} />
362
+ </Form.Item>
363
+ </Col>
364
+
365
+ <Col span={8}>
366
+ <Form.Item label="Alternate Phone" name="alternate_phone">
367
+ <Input placeholder="Enter Alternate Number" onKeyDown={handleEnterKey} maxLength={10} />
368
+ </Form.Item>
369
+ </Col>
370
+
371
+ <Col span={8}>
372
+ <Form.Item label="Email ID" name="email_id">
373
+ <Input placeholder="Enter Email ID" onKeyDown={handleEnterKey} />
374
+ </Form.Item>
375
+ </Col>
376
+ </Row>
377
+
378
+ <Row gutter={16}>
379
+ <Col span={8}>
380
+ <Form.Item label="Fax" name="fax">
381
+ <Input placeholder="Enter Fax Number" onKeyDown={handleEnterKey} maxLength={25} />
382
+ </Form.Item>
383
+ </Col>
384
+ </Row>
385
+ </Tabs.TabPane>
386
+ </Tabs>
387
+
388
+ {/* FOOTER BUTTONS */}
389
+ <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 10 }}>
390
+ <Button onClick={onCancel} type="secondary">
391
+ Cancel
392
+ </Button>
393
+ <Button type="primary" htmlType="submit" loading={loading} disabled={isSubmitDisabled}>
394
+ {doctorId ? 'Update' : 'Save'}
395
+ </Button>
396
+ </div>
397
+ </Form>
398
+ </Modal>
399
+ </>
400
+ );
401
+ };
402
+
403
+ export default DoctorAdd;