ui-soxo-bootstrap-core 2.4.24 → 2.4.25-dev.6
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/.github/workflows/npm-publish.yml +37 -15
- package/README.md +260 -0
- package/core/components/extra-info/extra-info-details.js +109 -126
- package/core/components/landing-api/landing-api.js +22 -30
- package/core/lib/Store.js +20 -18
- package/core/lib/components/index.js +4 -1
- package/core/lib/components/sidemenu/sidemenu.js +153 -256
- package/core/lib/components/sidemenu/sidemenu.scss +39 -26
- package/core/lib/hooks/index.js +2 -12
- package/core/lib/hooks/use-otp-timer.js +99 -0
- package/core/lib/pages/login/login.js +255 -139
- package/core/lib/pages/login/login.scss +140 -32
- package/core/models/dashboard/dashboard.js +14 -0
- package/core/models/doctor/components/doctor-add/doctor-add.js +403 -0
- package/core/models/doctor/components/doctor-add/doctor-add.scss +32 -0
- package/core/models/menus/components/menu-add/menu-add.js +230 -268
- package/core/models/menus/components/menu-lists/menu-lists.js +126 -89
- package/core/models/menus/components/menu-lists/menu-lists.scss +9 -0
- package/core/models/menus/menus.js +247 -267
- package/core/models/roles/components/role-add/role-add.js +269 -227
- package/core/models/roles/components/role-list/role-list.js +8 -6
- package/core/models/roles/roles.js +182 -174
- package/core/models/users/components/user-add/user-add.js +619 -365
- package/core/models/users/components/user-add/user-edit.js +90 -0
- package/core/models/users/users.js +261 -165
- package/core/modules/index.js +5 -8
- package/core/modules/reporting/components/index.js +5 -0
- package/core/modules/reporting/components/reporting-dashboard/reporting-dashboard.js +65 -2
- package/core/modules/steps/action-buttons.js +79 -0
- package/core/modules/steps/steps.js +553 -0
- package/core/modules/steps/steps.scss +158 -0
- package/core/modules/steps/timeline.js +49 -0
- 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
|
-
|
|
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:
|
|
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:
|
|
159
|
+
border-radius: 16px;
|
|
148
160
|
background-color: white;
|
|
149
161
|
overflow: hidden;
|
|
150
162
|
border: 1px solid #e8e8e8;
|
|
151
163
|
|
|
152
|
-
width:
|
|
164
|
+
width: 42%;
|
|
153
165
|
|
|
154
166
|
@media only screen and (max-width: 768px) {
|
|
155
|
-
width:
|
|
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
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
align
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
389
|
+
flex-direction: column;
|
|
314
390
|
gap: 7px;
|
|
315
391
|
}
|
|
316
392
|
|
|
317
|
-
.otp-container input[type=
|
|
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:
|
|
399
|
+
background-color: 'cover'; // Ensures full coverage
|
|
324
400
|
background-position: 'bottom center'; // Aligns image at the bottom
|
|
325
|
-
background-size: '100% auto'
|
|
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
|
|
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 'soxo-bootstrap-core';
|
|
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;
|