ui-soxo-bootstrap-core 2.4.25-dev.7 β 2.4.25-dev.9
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/core/lib/elements/basic/rangepicker/rangepicker.js +118 -29
- package/core/models/staff/components/staff-add/staff-add.js +352 -0
- package/core/models/staff/components/staff-add/staff-add.scss +0 -0
- package/core/models/users/components/user-add/user-add.js +69 -1
- package/core/models/users/users.js +57 -0
- package/core/modules/steps/steps.js +1 -1
- package/package.json +1 -1
- package/README.md +0 -260
|
@@ -1,46 +1,135 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @file rangepicker.js
|
|
3
|
+
* @description A reusable, enhanced Ant Design RangePicker component.
|
|
4
|
+
*
|
|
5
|
+
* Features:
|
|
6
|
+
* - Manual selections require "Apply" button confirmation
|
|
7
|
+
* - Preset ranges apply immediately without confirmation
|
|
8
|
+
* - Optimized state management and event handling
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { useCallback, useRef, useState } from 'react';
|
|
12
|
+
import { DatePicker, Space } from 'antd';
|
|
2
13
|
import moment from 'moment-timezone';
|
|
3
14
|
import PropTypes from 'prop-types';
|
|
15
|
+
import { useTranslation } from 'react-i18next';
|
|
16
|
+
import Button from '../button/button';
|
|
4
17
|
import './rangepicker.scss';
|
|
5
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Enhanced RangePicker with Apply/Cancel controls for manual selections.
|
|
21
|
+
* @param {object} props
|
|
22
|
+
* @param {moment.Moment[]} props.value - Current applied date range
|
|
23
|
+
* @param {function(moment.Moment[]): void} props.onChange - Callback when range is applied
|
|
24
|
+
* @param {string} [props.format='DD/MM/YYYY'] - Date display format
|
|
25
|
+
* @param {object} [props.ranges] - Preset date ranges
|
|
26
|
+
* @param {boolean} [props.allowClear=false] - Whether to show clear button
|
|
27
|
+
* @param {boolean} [props.inputReadOnly=true] - Whether input is read-only
|
|
28
|
+
*/
|
|
29
|
+
|
|
6
30
|
const { RangePicker } = DatePicker;
|
|
7
31
|
|
|
8
|
-
export default function RangePickerComponent({
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
32
|
+
export default function RangePickerComponent({
|
|
33
|
+
value,
|
|
34
|
+
onChange,
|
|
35
|
+
format = 'DD/MM/YYYY',
|
|
36
|
+
ranges,
|
|
37
|
+
allowClear = false,
|
|
38
|
+
inputReadOnly = true,
|
|
39
|
+
...restProps
|
|
40
|
+
}) {
|
|
41
|
+
const { t } = useTranslation();
|
|
42
|
+
|
|
43
|
+
// Temporary range during selection (before Apply is clicked)
|
|
44
|
+
const [tempRange, setTempRange] = useState(null);
|
|
45
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
46
|
+
|
|
47
|
+
// Ref to track if a selection just happened, to prevent the picker from closing.
|
|
48
|
+
const selectionJustHappened = useRef(false);
|
|
12
49
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
50
|
+
/**
|
|
51
|
+
* Handles any completed date selection (both manual and preset).
|
|
52
|
+
* It updates the temporary range and sets a flag to keep the picker open.
|
|
53
|
+
*/
|
|
54
|
+
const handleChange = useCallback((dates) => {
|
|
55
|
+
setTempRange(dates || []);
|
|
56
|
+
selectionJustHappened.current = true;
|
|
57
|
+
}, []);
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Handles picker open/close events.
|
|
61
|
+
* Prevents closing after a selection, requiring user to click Apply/Cancel.
|
|
62
|
+
*/
|
|
63
|
+
const handleOpenChange = useCallback((open) => {
|
|
64
|
+
// If the picker is trying to close, check if it was due to a selection.
|
|
65
|
+
if (!open && selectionJustHappened.current) {
|
|
66
|
+
// It was a selection, so ignore the close request and reset the flag.
|
|
67
|
+
selectionJustHappened.current = false;
|
|
16
68
|
return;
|
|
17
69
|
}
|
|
18
70
|
|
|
19
|
-
//
|
|
20
|
-
|
|
71
|
+
// For all other cases (opening, or closing by clicking outside), proceed as normal.
|
|
72
|
+
if (open) {
|
|
73
|
+
// Reset temp range when opening for a fresh selection.
|
|
74
|
+
setTempRange(null);
|
|
75
|
+
}
|
|
76
|
+
setIsOpen(open);
|
|
77
|
+
}, []);
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Applies the temporary range and closes picker
|
|
81
|
+
*/
|
|
82
|
+
const handleApply = useCallback(() => {
|
|
83
|
+
if (tempRange?.length === 2 && onChange) {
|
|
84
|
+
onChange(tempRange);
|
|
85
|
+
}
|
|
86
|
+
selectionJustHappened.current = false; // Clear the flag
|
|
87
|
+
setIsOpen(false);
|
|
88
|
+
}, [tempRange, onChange]);
|
|
21
89
|
|
|
22
|
-
|
|
23
|
-
|
|
90
|
+
/**
|
|
91
|
+
* Cancels selection and closes picker
|
|
92
|
+
*/
|
|
93
|
+
const handleCancel = useCallback(() => {
|
|
94
|
+
selectionJustHappened.current = false; // Clear the flag
|
|
95
|
+
setTempRange(null);
|
|
96
|
+
setIsOpen(false);
|
|
97
|
+
}, []);
|
|
24
98
|
|
|
25
|
-
//
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
99
|
+
// Memoized preset ranges - use provided ranges or default set
|
|
100
|
+
const defaultRanges = useRef({
|
|
101
|
+
Today: [moment(), moment()],
|
|
102
|
+
Yesterday: [moment().subtract(1, 'days').startOf('day'), moment().subtract(1, 'days').endOf('day')],
|
|
103
|
+
'This Week': [moment().startOf('week'), moment().endOf('week')],
|
|
104
|
+
'Last Week': [moment().subtract(1, 'week').startOf('week'), moment().subtract(1, 'week').endOf('week')],
|
|
105
|
+
'This Month': [moment().startOf('month'), moment().endOf('month')],
|
|
106
|
+
'Last Month': [moment().subtract(1, 'month').startOf('month'), moment().subtract(1, 'month').endOf('month')],
|
|
107
|
+
}).current;
|
|
30
108
|
|
|
31
109
|
return (
|
|
32
|
-
<
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
110
|
+
<RangePicker
|
|
111
|
+
allowClear={allowClear}
|
|
112
|
+
inputReadOnly={inputReadOnly}
|
|
113
|
+
format={format}
|
|
114
|
+
value={tempRange || value}
|
|
115
|
+
open={isOpen}
|
|
116
|
+
onOpenChange={handleOpenChange}
|
|
117
|
+
onChange={handleChange}
|
|
118
|
+
ranges={ranges !== undefined ? ranges : defaultRanges}
|
|
119
|
+
{...restProps}
|
|
120
|
+
renderExtraFooter={() => (
|
|
121
|
+
<div style={{ width: '100%', display: 'flex', justifyContent: 'flex-end', order: 1 }}>
|
|
122
|
+
<Space>
|
|
123
|
+
<Button size="small" onClick={handleCancel}>
|
|
124
|
+
{t('Cancel')}
|
|
125
|
+
</Button>
|
|
126
|
+
<Button type="primary" size="small" onClick={handleApply}>
|
|
127
|
+
{t('Apply')}
|
|
128
|
+
</Button>
|
|
129
|
+
</Space>
|
|
130
|
+
</div>
|
|
131
|
+
)}
|
|
132
|
+
/>
|
|
44
133
|
);
|
|
45
134
|
}
|
|
46
135
|
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
import React, { useState, useEffect, useRef } from 'react';
|
|
2
|
+
import './staff-add.scss';
|
|
3
|
+
import { Modal, Tabs, Input, Form, Row, Col, message, Checkbox, Select } from 'antd';
|
|
4
|
+
import { useTranslation, Button } from './../../../../lib/';
|
|
5
|
+
import { UsersAPI } from '../../..';
|
|
6
|
+
|
|
7
|
+
const StaffAdd = ({ visible, onCancel, staffId, staffData, onSuccess }) => {
|
|
8
|
+
const [form] = Form.useForm();
|
|
9
|
+
const { t } = useTranslation();
|
|
10
|
+
|
|
11
|
+
/** -------------------------------
|
|
12
|
+
* STATE
|
|
13
|
+
* ------------------------------- */
|
|
14
|
+
const [loading, setLoading] = useState(false);
|
|
15
|
+
const [designations, setDesignations] = useState([]);
|
|
16
|
+
const [isSubmitDisabled, setIsSubmitDisabled] = useState(true);
|
|
17
|
+
const [codeStatus, setCodeStatus] = useState({ status: '', message: '' });
|
|
18
|
+
|
|
19
|
+
const nameInputRef = useRef(null);
|
|
20
|
+
|
|
21
|
+
const editMode = Boolean(staffId);
|
|
22
|
+
|
|
23
|
+
/** -------------------------------
|
|
24
|
+
* FORM WATCHERS
|
|
25
|
+
* ------------------------------- */
|
|
26
|
+
const watchedName = Form.useWatch('shortName', form);
|
|
27
|
+
const watchedCode = Form.useWatch('id', form);
|
|
28
|
+
const watchedDesc = Form.useWatch('description', form);
|
|
29
|
+
|
|
30
|
+
/** -------------------------------
|
|
31
|
+
* EFFECTS
|
|
32
|
+
* ------------------------------- */
|
|
33
|
+
|
|
34
|
+
// Auto-focus on open
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
if (visible) {
|
|
37
|
+
setTimeout(() => nameInputRef.current?.focus(), 200);
|
|
38
|
+
}
|
|
39
|
+
}, [visible]);
|
|
40
|
+
|
|
41
|
+
// Control submit button
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
setIsSubmitDisabled(!(watchedName && watchedCode && watchedDesc));
|
|
44
|
+
}, [watchedName, watchedCode, watchedDesc]);
|
|
45
|
+
|
|
46
|
+
// Load designations when modal opens
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
if (visible) getDesignations();
|
|
49
|
+
}, [visible]);
|
|
50
|
+
|
|
51
|
+
// Reset or fill edit form
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
if (visible) {
|
|
54
|
+
if (editMode && staffData) fillEditForm(staffData);
|
|
55
|
+
} else {
|
|
56
|
+
form.resetFields();
|
|
57
|
+
}
|
|
58
|
+
}, [visible]);
|
|
59
|
+
|
|
60
|
+
/** -------------------------------
|
|
61
|
+
* API CALL β Designations
|
|
62
|
+
* ------------------------------- */
|
|
63
|
+
const getDesignations = () => {
|
|
64
|
+
setLoading(true);
|
|
65
|
+
UsersAPI.getDesignations()
|
|
66
|
+
.then((res) => {
|
|
67
|
+
if (res?.success && Array.isArray(res.result)) {
|
|
68
|
+
const formatted = res.result.map((ele) => ({
|
|
69
|
+
label: ele.dg_desc?.trim() || '',
|
|
70
|
+
value: ele.dg_code,
|
|
71
|
+
}));
|
|
72
|
+
setDesignations(formatted);
|
|
73
|
+
} else {
|
|
74
|
+
setDesignations([]);
|
|
75
|
+
}
|
|
76
|
+
})
|
|
77
|
+
.catch((err) => {
|
|
78
|
+
console.error('Error fetching designations:', err);
|
|
79
|
+
setDesignations([]);
|
|
80
|
+
})
|
|
81
|
+
.finally(() => setLoading(false));
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/** -------------------------------
|
|
85
|
+
* VALIDATION β Code
|
|
86
|
+
* ------------------------------- */
|
|
87
|
+
const validateDoctorCode = async (_, value) => {
|
|
88
|
+
if (editMode) return Promise.resolve(); // Skip validation if editing
|
|
89
|
+
|
|
90
|
+
if (!value) {
|
|
91
|
+
setCodeStatus({ status: '', message: '' });
|
|
92
|
+
return Promise.resolve();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
const res = await UsersAPI.getStaffCode(value);
|
|
97
|
+
|
|
98
|
+
if (res?.status === 409 || res?.success === false) {
|
|
99
|
+
setCodeStatus({ status: 'error', message: res.message || 'Code already exists' });
|
|
100
|
+
return Promise.reject(new Error(res.message || 'Code already exists'));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (res?.status === 200 && res?.success === true) {
|
|
104
|
+
setCodeStatus({
|
|
105
|
+
status: 'success',
|
|
106
|
+
message: 'The entered code is valid for assigning a staff.',
|
|
107
|
+
});
|
|
108
|
+
return Promise.resolve();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
setCodeStatus({ status: 'error', message: 'Unexpected response' });
|
|
112
|
+
return Promise.reject(new Error('Unexpected response'));
|
|
113
|
+
} catch {
|
|
114
|
+
setCodeStatus({ status: 'error', message: 'Validation failed' });
|
|
115
|
+
return Promise.reject(new Error('Validation failed'));
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
/** -------------------------------
|
|
120
|
+
* UTIL β Enter Key Navigation
|
|
121
|
+
* ------------------------------- */
|
|
122
|
+
const handleEnterKey = (e) => {
|
|
123
|
+
if (e.key === 'Enter') {
|
|
124
|
+
e.preventDefault();
|
|
125
|
+
const fields = Array.from(document.querySelectorAll('.ant-input'));
|
|
126
|
+
const index = fields.indexOf(e.target);
|
|
127
|
+
if (index > -1 && index < fields.length - 1) {
|
|
128
|
+
fields[index + 1].focus();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
/** -------------------------------
|
|
134
|
+
* FORM FILL β Edit Mode
|
|
135
|
+
* ------------------------------- */
|
|
136
|
+
const fillEditForm = (data) => {
|
|
137
|
+
form.setFieldsValue({
|
|
138
|
+
id: data.id,
|
|
139
|
+
shortName: data.shortName,
|
|
140
|
+
description: data.description,
|
|
141
|
+
designation: data.designationPtr,
|
|
142
|
+
phone1: data.phone,
|
|
143
|
+
phone2: data.mobile || data.alternateMobile,
|
|
144
|
+
email1: data.email,
|
|
145
|
+
email2: data.alternateEmail,
|
|
146
|
+
slno: data.slNo,
|
|
147
|
+
active: data.active === 'Y' ? 'Y' : 'N',
|
|
148
|
+
remarks: data.remarks,
|
|
149
|
+
address1: data.address1,
|
|
150
|
+
address2: data.address2,
|
|
151
|
+
place: data.place,
|
|
152
|
+
zip: data.zip,
|
|
153
|
+
});
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
/** -------------------------------
|
|
157
|
+
* SUBMIT HANDLER
|
|
158
|
+
* ------------------------------- */
|
|
159
|
+
const handleFinish = async (values) => {
|
|
160
|
+
setLoading(true);
|
|
161
|
+
|
|
162
|
+
const payload = {
|
|
163
|
+
id: values.id,
|
|
164
|
+
shortName: values.shortName,
|
|
165
|
+
description: values.description,
|
|
166
|
+
designationPtr: values.designation,
|
|
167
|
+
phone: values.phone1,
|
|
168
|
+
mobile: values.phone2,
|
|
169
|
+
alternateMobile: values.phone2,
|
|
170
|
+
email: values.email1,
|
|
171
|
+
alternateEmail: values.email2,
|
|
172
|
+
remarks: values.remarks,
|
|
173
|
+
slNo: values.slno ? Number(values.slno) : null,
|
|
174
|
+
active: values.active === 'Y' ? 'Y' : 'N',
|
|
175
|
+
address1: values.address1,
|
|
176
|
+
address2: values.address2,
|
|
177
|
+
place: values.place,
|
|
178
|
+
zip: values.zip,
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
try {
|
|
182
|
+
const res = editMode ? await UsersAPI.updateStaff(payload, staffData.id) : await UsersAPI.createStaff(payload);
|
|
183
|
+
|
|
184
|
+
if (res?.success) {
|
|
185
|
+
message.success(editMode ? 'Staff updated successfully' : 'Staff created successfully');
|
|
186
|
+
onSuccess?.();
|
|
187
|
+
onCancel();
|
|
188
|
+
} else {
|
|
189
|
+
message.error(res?.message || 'Operation failed');
|
|
190
|
+
}
|
|
191
|
+
} catch {
|
|
192
|
+
message.error('Something went wrong');
|
|
193
|
+
} finally {
|
|
194
|
+
setLoading(false);
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
/** -------------------------------
|
|
199
|
+
* RENDER
|
|
200
|
+
* ------------------------------- */
|
|
201
|
+
return (
|
|
202
|
+
<Modal
|
|
203
|
+
title={editMode ? 'Edit Staff' : 'Add Staff'}
|
|
204
|
+
open={visible}
|
|
205
|
+
onCancel={onCancel}
|
|
206
|
+
width={950}
|
|
207
|
+
footer={null}
|
|
208
|
+
destroyOnClose
|
|
209
|
+
style={{ top: 10 }}
|
|
210
|
+
>
|
|
211
|
+
<Form form={form} layout="vertical" onFinish={handleFinish}>
|
|
212
|
+
{/* TOP SECTION */}
|
|
213
|
+
<Row gutter={16}>
|
|
214
|
+
<Col span={6}>
|
|
215
|
+
<Form.Item
|
|
216
|
+
label="Code"
|
|
217
|
+
name="id"
|
|
218
|
+
validateTrigger="onChange"
|
|
219
|
+
hasFeedback={!editMode}
|
|
220
|
+
rules={[{ required: true }, { validator: validateDoctorCode }]}
|
|
221
|
+
>
|
|
222
|
+
<Input placeholder="Enter Code" ref={nameInputRef} onKeyDown={handleEnterKey} disabled={editMode} />
|
|
223
|
+
</Form.Item>
|
|
224
|
+
</Col>
|
|
225
|
+
|
|
226
|
+
<Col span={6}>
|
|
227
|
+
<Form.Item label="Name" name="shortName" rules={[{ required: true }]}>
|
|
228
|
+
<Input placeholder="Enter Name" onKeyDown={handleEnterKey} />
|
|
229
|
+
</Form.Item>
|
|
230
|
+
</Col>
|
|
231
|
+
|
|
232
|
+
<Col span={12}>
|
|
233
|
+
<Form.Item label="Description" name="description" rules={[{ required: true }]}>
|
|
234
|
+
<Input placeholder="Enter Description" onKeyDown={handleEnterKey} />
|
|
235
|
+
</Form.Item>
|
|
236
|
+
</Col>
|
|
237
|
+
</Row>
|
|
238
|
+
|
|
239
|
+
<Tabs defaultActiveKey="1">
|
|
240
|
+
{/* GENERAL TAB */}
|
|
241
|
+
<Tabs.TabPane tab="GENERAL" key="1">
|
|
242
|
+
<Row gutter={16}>
|
|
243
|
+
<Col span={6}>
|
|
244
|
+
<Form.Item label="Phone Number 1" name="phone1">
|
|
245
|
+
<Input maxLength={10} placeholder="Enter Phone Number" onKeyDown={handleEnterKey} />
|
|
246
|
+
</Form.Item>
|
|
247
|
+
</Col>
|
|
248
|
+
|
|
249
|
+
<Col span={6}>
|
|
250
|
+
<Form.Item label="Phone Number 2" name="phone2">
|
|
251
|
+
<Input maxLength={10} placeholder="Enter Phone Number" onKeyDown={handleEnterKey} />
|
|
252
|
+
</Form.Item>
|
|
253
|
+
</Col>
|
|
254
|
+
|
|
255
|
+
<Col span={6}>
|
|
256
|
+
<Form.Item label="Email ID 1" name="email1">
|
|
257
|
+
<Input placeholder="Enter Email" onKeyDown={handleEnterKey} />
|
|
258
|
+
</Form.Item>
|
|
259
|
+
</Col>
|
|
260
|
+
|
|
261
|
+
<Col span={6}>
|
|
262
|
+
<Form.Item label="Email ID 2" name="email2">
|
|
263
|
+
<Input placeholder="Enter Email" onKeyDown={handleEnterKey} />
|
|
264
|
+
</Form.Item>
|
|
265
|
+
</Col>
|
|
266
|
+
</Row>
|
|
267
|
+
|
|
268
|
+
<Row gutter={16}>
|
|
269
|
+
<Col span={8}>
|
|
270
|
+
<Form.Item name="designation" label="Designation">
|
|
271
|
+
<Select placeholder="Select Designation" options={designations} allowClear showSearch optionFilterProp="label" />
|
|
272
|
+
</Form.Item>
|
|
273
|
+
</Col>
|
|
274
|
+
|
|
275
|
+
<Col span={8}>
|
|
276
|
+
<Form.Item label="Serial Number" name="slno">
|
|
277
|
+
<Input placeholder="Enter Serial Number" onKeyDown={handleEnterKey} />
|
|
278
|
+
</Form.Item>
|
|
279
|
+
</Col>
|
|
280
|
+
|
|
281
|
+
<Col span={8}>
|
|
282
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 10, marginTop: 30 }}>
|
|
283
|
+
<label>Active</label>
|
|
284
|
+
<Form.Item
|
|
285
|
+
name="active"
|
|
286
|
+
valuePropName="checked"
|
|
287
|
+
getValueFromEvent={(e) => (e.target.checked ? 'Y' : 'N')}
|
|
288
|
+
getValueProps={(value) => ({ checked: value === 'Y' })}
|
|
289
|
+
style={{ marginBottom: 0 }}
|
|
290
|
+
>
|
|
291
|
+
<Checkbox />
|
|
292
|
+
</Form.Item>
|
|
293
|
+
</div>
|
|
294
|
+
</Col>
|
|
295
|
+
</Row>
|
|
296
|
+
|
|
297
|
+
<Row>
|
|
298
|
+
<Col span={24}>
|
|
299
|
+
<Form.Item label="Remarks" name="remarks">
|
|
300
|
+
<Input placeholder="Enter Remarks" onKeyDown={handleEnterKey} />
|
|
301
|
+
</Form.Item>
|
|
302
|
+
</Col>
|
|
303
|
+
</Row>
|
|
304
|
+
</Tabs.TabPane>
|
|
305
|
+
|
|
306
|
+
{/* ADDRESS TAB */}
|
|
307
|
+
<Tabs.TabPane tab="ADDRESS" key="2">
|
|
308
|
+
<Row gutter={16}>
|
|
309
|
+
<Col span={12}>
|
|
310
|
+
<Form.Item label="Address Line 1" name="address1">
|
|
311
|
+
<Input placeholder="Enter Address" onKeyDown={handleEnterKey} />
|
|
312
|
+
</Form.Item>
|
|
313
|
+
</Col>
|
|
314
|
+
|
|
315
|
+
<Col span={12}>
|
|
316
|
+
<Form.Item label="Address Line 2" name="address2">
|
|
317
|
+
<Input placeholder="Enter Address" onKeyDown={handleEnterKey} />
|
|
318
|
+
</Form.Item>
|
|
319
|
+
</Col>
|
|
320
|
+
</Row>
|
|
321
|
+
|
|
322
|
+
<Row gutter={16}>
|
|
323
|
+
<Col span={12}>
|
|
324
|
+
<Form.Item label="Place" name="place">
|
|
325
|
+
<Input placeholder="Enter Place" onKeyDown={handleEnterKey} />
|
|
326
|
+
</Form.Item>
|
|
327
|
+
</Col>
|
|
328
|
+
|
|
329
|
+
<Col span={12}>
|
|
330
|
+
<Form.Item label="Zip Code" name="zip">
|
|
331
|
+
<Input placeholder="Enter Zip Code" maxLength={7} onKeyDown={handleEnterKey} />
|
|
332
|
+
</Form.Item>
|
|
333
|
+
</Col>
|
|
334
|
+
</Row>
|
|
335
|
+
</Tabs.TabPane>
|
|
336
|
+
</Tabs>
|
|
337
|
+
|
|
338
|
+
{/* FOOTER BUTTONS */}
|
|
339
|
+
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: 10 }}>
|
|
340
|
+
<Button onClick={onCancel} type="secondary">
|
|
341
|
+
Cancel
|
|
342
|
+
</Button>
|
|
343
|
+
<Button type="primary" htmlType="submit" loading={loading} disabled={isSubmitDisabled}>
|
|
344
|
+
{editMode ? 'Update' : 'Save'}
|
|
345
|
+
</Button>
|
|
346
|
+
</div>
|
|
347
|
+
</Form>
|
|
348
|
+
</Modal>
|
|
349
|
+
);
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
export default StaffAdd;
|
|
File without changes
|
|
@@ -12,6 +12,7 @@ import { ModelsAPI, PagesAPI } from '../../..';
|
|
|
12
12
|
|
|
13
13
|
import { UsersAPI } from '../../..';
|
|
14
14
|
import DoctorAdd from '../../../doctor/components/doctor-add/doctor-add';
|
|
15
|
+
import StaffAdd from '../../../staff/components/staff-add/staff-add';
|
|
15
16
|
|
|
16
17
|
const { Title } = Typography;
|
|
17
18
|
|
|
@@ -37,6 +38,9 @@ const UserAdd = ({ model, callback, edit, history, formContent, match, additiona
|
|
|
37
38
|
const [users, setUsers] = useState([]);
|
|
38
39
|
const [doctorID, setDoctorID] = useState(false);
|
|
39
40
|
const [selectedDoctor, setSelectedDoctor] = useState(null);
|
|
41
|
+
const [staffID, setStaffID] = useState(false);
|
|
42
|
+
const [selectedStaff, setSelectedStaff] = useState(null);
|
|
43
|
+
const [staffList, setStaffList] = useState([]);
|
|
40
44
|
/**Converting to JSON */
|
|
41
45
|
let firmDetails = JSON.parse(user.firm.f_otherdetails1);
|
|
42
46
|
|
|
@@ -160,6 +164,7 @@ const UserAdd = ({ model, callback, edit, history, formContent, match, additiona
|
|
|
160
164
|
{ label: 'General', value: 'GEN' },
|
|
161
165
|
{ label: 'Doctor', value: 'RAD' },
|
|
162
166
|
{ label: 'Radiographer', value: 'TECH' },
|
|
167
|
+
{ label: 'Staff', value: 'STAFF' },
|
|
163
168
|
];
|
|
164
169
|
|
|
165
170
|
/**
|
|
@@ -237,6 +242,25 @@ const UserAdd = ({ model, callback, edit, history, formContent, match, additiona
|
|
|
237
242
|
})
|
|
238
243
|
.catch((err) => console.error('Doctor API Error:', err));
|
|
239
244
|
};
|
|
245
|
+
/** Get staff List */
|
|
246
|
+
const getStaff = () => {
|
|
247
|
+
UsersAPI.getAllStaff()
|
|
248
|
+
.then((res) => {
|
|
249
|
+
console.log('Staff List Response:', res);
|
|
250
|
+
|
|
251
|
+
if (Array.isArray(res)) {
|
|
252
|
+
const list = res.map((staff) => ({
|
|
253
|
+
label: `${staff.shortName || 'No Name'} (${staff.id})`,
|
|
254
|
+
value: staff.id,
|
|
255
|
+
}));
|
|
256
|
+
|
|
257
|
+
setStaffList(list);
|
|
258
|
+
} else {
|
|
259
|
+
console.error('API did not return an array!');
|
|
260
|
+
}
|
|
261
|
+
})
|
|
262
|
+
.catch((err) => console.error('staff API Error:', err));
|
|
263
|
+
};
|
|
240
264
|
|
|
241
265
|
// Function to handle user type change
|
|
242
266
|
const handleUserTypeChange = (value) => {
|
|
@@ -247,6 +271,11 @@ const UserAdd = ({ model, callback, edit, history, formContent, match, additiona
|
|
|
247
271
|
} else {
|
|
248
272
|
form.setFieldsValue({ default_code: undefined });
|
|
249
273
|
}
|
|
274
|
+
if (value === 'STAFF') {
|
|
275
|
+
getStaff(); // load staff list
|
|
276
|
+
} else {
|
|
277
|
+
form.setFieldsValue({ staff_code: undefined });
|
|
278
|
+
}
|
|
250
279
|
};
|
|
251
280
|
|
|
252
281
|
useEffect(() => {
|
|
@@ -254,6 +283,7 @@ const UserAdd = ({ model, callback, edit, history, formContent, match, additiona
|
|
|
254
283
|
getDesignations();
|
|
255
284
|
getDepartments();
|
|
256
285
|
getDoctors(); // load doctor list
|
|
286
|
+
getStaff();
|
|
257
287
|
}, []);
|
|
258
288
|
|
|
259
289
|
useEffect(() => {
|
|
@@ -264,6 +294,9 @@ const UserAdd = ({ model, callback, edit, history, formContent, match, additiona
|
|
|
264
294
|
if (formContent.user_type === 'RAD' && formContent.doctor_code) {
|
|
265
295
|
form.setFieldsValue({ default_code: formContent.doctor_code });
|
|
266
296
|
}
|
|
297
|
+
if (formContent.user_type === 'STAFF' && formContent.staff_code) {
|
|
298
|
+
form.setFieldsValue({ staff_code: formContent.staff_code });
|
|
299
|
+
}
|
|
267
300
|
}
|
|
268
301
|
if (formContent?.id && formContent?.organization_details) {
|
|
269
302
|
try {
|
|
@@ -294,7 +327,6 @@ const UserAdd = ({ model, callback, edit, history, formContent, match, additiona
|
|
|
294
327
|
* Submit values
|
|
295
328
|
*/
|
|
296
329
|
const onSubmit = (values) => {
|
|
297
|
-
console.log('values____________', values);
|
|
298
330
|
/**If PanelOpen is open and edit mode then password will be existing password else new password*/
|
|
299
331
|
if (!isPasswordVisible && mode === 'Edit') {
|
|
300
332
|
values.password = body.password;
|
|
@@ -306,6 +338,11 @@ const UserAdd = ({ model, callback, edit, history, formContent, match, additiona
|
|
|
306
338
|
} else {
|
|
307
339
|
values.doctor_code = null;
|
|
308
340
|
}
|
|
341
|
+
if (values.user_type === 'STAFF') {
|
|
342
|
+
values.staff_code = values.staff_code;
|
|
343
|
+
} else {
|
|
344
|
+
values.staff_code = null;
|
|
345
|
+
}
|
|
309
346
|
values = {
|
|
310
347
|
...values,
|
|
311
348
|
auth_type: 'LDAP',
|
|
@@ -481,6 +518,37 @@ const UserAdd = ({ model, callback, edit, history, formContent, match, additiona
|
|
|
481
518
|
/>
|
|
482
519
|
</Form.Item>
|
|
483
520
|
)}
|
|
521
|
+
{userType === 'STAFF' && (
|
|
522
|
+
<Form.Item name="staff_code" label="Staff Code" rules={[{ required: true, message: 'Please select a staff code' }]}>
|
|
523
|
+
<Select
|
|
524
|
+
placeholder="Select Code"
|
|
525
|
+
options={staffList}
|
|
526
|
+
showSearch
|
|
527
|
+
optionFilterProp="label"
|
|
528
|
+
dropdownRender={(menu) => (
|
|
529
|
+
<>
|
|
530
|
+
{menu}
|
|
531
|
+
<div
|
|
532
|
+
style={{
|
|
533
|
+
padding: '8px',
|
|
534
|
+
cursor: 'pointer',
|
|
535
|
+
borderTop: '1px solid #f0f0f0',
|
|
536
|
+
color: '#1890ff',
|
|
537
|
+
}}
|
|
538
|
+
onClick={() => setVisible(true)}
|
|
539
|
+
>
|
|
540
|
+
+ Add New Staff
|
|
541
|
+
</div>
|
|
542
|
+
</>
|
|
543
|
+
)}
|
|
544
|
+
/>
|
|
545
|
+
{/* Render DoctorAdd OUTSIDE the Select */}
|
|
546
|
+
|
|
547
|
+
<StaffAdd visible={visible} onCancel={() => setVisible(false)} staffData={selectedStaff} staffId={staffID} onSuccess={getStaff} />
|
|
548
|
+
|
|
549
|
+
<></>
|
|
550
|
+
</Form.Item>
|
|
551
|
+
)}
|
|
484
552
|
</Col>
|
|
485
553
|
</Row>
|
|
486
554
|
<Row gutter={16}>
|
|
@@ -276,6 +276,63 @@ class Users extends Base {
|
|
|
276
276
|
getUser = ({ id }) => {
|
|
277
277
|
return ApiUtils.get({ url: `users/${id}` });
|
|
278
278
|
};
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
*Cutsom Api for creating staff
|
|
282
|
+
* @returns
|
|
283
|
+
*/
|
|
284
|
+
|
|
285
|
+
createStaff = (formBody) => {
|
|
286
|
+
return ApiUtils.post({
|
|
287
|
+
url: `staff/create-staff`,
|
|
288
|
+
formBody,
|
|
289
|
+
});
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Update a staff by ID
|
|
294
|
+
* @param {Object} formBody - Data to update
|
|
295
|
+
* @param {string|number} id - Staff ID
|
|
296
|
+
* @returns {Promise} API response
|
|
297
|
+
*/
|
|
298
|
+
updateStaff = (formBody, id) => {
|
|
299
|
+
return ApiUtils.put({
|
|
300
|
+
url: `staff/update-staff/${id}`,
|
|
301
|
+
formBody,
|
|
302
|
+
});
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* edit staff with id
|
|
307
|
+
* @returns
|
|
308
|
+
*/
|
|
309
|
+
|
|
310
|
+
getStaff = (id) => {
|
|
311
|
+
return ApiUtils.get({
|
|
312
|
+
url: `staff/get-staff/${id}`,
|
|
313
|
+
});
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* staff code validation
|
|
318
|
+
*
|
|
319
|
+
*/
|
|
320
|
+
getStaffCode = (code) => {
|
|
321
|
+
return ApiUtils.get({
|
|
322
|
+
url: `staff/check-staff-exists/${code}`,
|
|
323
|
+
});
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* get all staff
|
|
328
|
+
* @returns
|
|
329
|
+
*/
|
|
330
|
+
|
|
331
|
+
getAllStaff = () => {
|
|
332
|
+
return ApiUtils.get({
|
|
333
|
+
url: 'staff/get-all-staff',
|
|
334
|
+
});
|
|
335
|
+
};
|
|
279
336
|
}
|
|
280
337
|
|
|
281
338
|
export default Users;
|
|
@@ -512,7 +512,7 @@ export default function ProcessStepsPage({ processId = 1, match, CustomComponent
|
|
|
512
512
|
return (
|
|
513
513
|
<Card>
|
|
514
514
|
<Row gutter={20}>
|
|
515
|
-
<Col xs={24} sm={24} lg={timelineCollapsed ?
|
|
515
|
+
<Col xs={24} sm={24} lg={timelineCollapsed ? 2 : 6}>
|
|
516
516
|
|
|
517
517
|
|
|
518
518
|
|
package/package.json
CHANGED
package/README.md
DELETED
|
@@ -1,260 +0,0 @@
|
|
|
1
|
-
# π¦ NPM Release Workflow β Task-Based Development β Develop β Master
|
|
2
|
-
|
|
3
|
-
This guide describes the complete workflow for publishing **task-based development builds** and **stable releases** using GitHub Actions, Git tags, and npm versioning.
|
|
4
|
-
|
|
5
|
-
Incorrect versioning or incorrect tags will break the publish pipeline β follow the process exactly.
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
# π Table of Contents
|
|
10
|
-
- Overview
|
|
11
|
-
- Branch Strategy
|
|
12
|
-
- Task Branch Workflow
|
|
13
|
-
- Versioning Rules
|
|
14
|
-
- Development Release Workflow (`develop`)
|
|
15
|
-
- Promotion Workflow (Dev β Master)
|
|
16
|
-
- Publishing via GitHub Release UI
|
|
17
|
-
- How GitHub Action Detects Release Type
|
|
18
|
-
- Summary Table
|
|
19
|
-
- Common Mistakes & Fixes
|
|
20
|
-
|
|
21
|
-
---
|
|
22
|
-
|
|
23
|
-
# π§© Overview
|
|
24
|
-
|
|
25
|
-
We maintain two release channels:
|
|
26
|
-
|
|
27
|
-
| Channel | Branch | Tag | Purpose |
|
|
28
|
-
|---------------------|----------|------------------|-----------------------|
|
|
29
|
-
| Stable (`latest`) | master | `vX.Y.Z` | Production release |
|
|
30
|
-
| Development (`dev`) | develop | `vX.Y.Z-dev.N` | QA/internal testing |
|
|
31
|
-
|
|
32
|
-
Releases are triggered using Git tags:
|
|
33
|
-
|
|
34
|
-
```
|
|
35
|
-
npm version
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
---
|
|
39
|
-
|
|
40
|
-
# πΏ Branch Strategy
|
|
41
|
-
|
|
42
|
-
## `master`
|
|
43
|
-
- Production-ready code
|
|
44
|
-
- Publishes **stable** builds
|
|
45
|
-
- Versions never include a `dev` suffix
|
|
46
|
-
|
|
47
|
-
## `develop`
|
|
48
|
-
- Used for QA/internal testing
|
|
49
|
-
- Publishes **development** builds
|
|
50
|
-
- Versions always include `-dev.N`
|
|
51
|
-
|
|
52
|
-
## Task Branches
|
|
53
|
-
- Create from `master`
|
|
54
|
-
- Naming: `task/<JIRA-ID>-<description>`
|
|
55
|
-
- After completing work, merge into `develop`
|
|
56
|
-
|
|
57
|
-
---
|
|
58
|
-
|
|
59
|
-
# ποΈ Task Branch Workflow
|
|
60
|
-
|
|
61
|
-
### 1. Create a task branch
|
|
62
|
-
```bash
|
|
63
|
-
git checkout master
|
|
64
|
-
git pull origin master
|
|
65
|
-
git checkout -b task/<JIRA-ID>-<description>
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
### 2. Work on the task
|
|
69
|
-
```bash
|
|
70
|
-
git add .
|
|
71
|
-
git commit -m "TASK-123: Implement XYZ"
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
### 3. Merge into develop
|
|
75
|
-
```bash
|
|
76
|
-
git checkout develop
|
|
77
|
-
git pull origin develop
|
|
78
|
-
git merge task/<JIRA-ID>-<description>
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
---
|
|
82
|
-
|
|
83
|
-
# π§ Versioning Rules
|
|
84
|
-
|
|
85
|
-
## Development Versions
|
|
86
|
-
```
|
|
87
|
-
X.Y.Z-dev.N
|
|
88
|
-
```
|
|
89
|
-
Example:
|
|
90
|
-
```
|
|
91
|
-
2.4.30-dev.0
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
## Stable Versions
|
|
95
|
-
```
|
|
96
|
-
X.Y.Z
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
## Rules
|
|
100
|
-
- Each version must be unique
|
|
101
|
-
- Dev versions do **not** convert into stable versions
|
|
102
|
-
- Git tag must exactly match `package.json` version
|
|
103
|
-
|
|
104
|
-
---
|
|
105
|
-
|
|
106
|
-
# π¦ Development Release Workflow (`develop`)
|
|
107
|
-
|
|
108
|
-
### 1. Switch to develop
|
|
109
|
-
```bash
|
|
110
|
-
git checkout develop
|
|
111
|
-
git pull origin develop
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
### 2. Create a new development version
|
|
115
|
-
```bash
|
|
116
|
-
npm version prerelease --preid=dev
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
### 3. Push commit + tag
|
|
120
|
-
```bash
|
|
121
|
-
git push --follow-tags
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
### 4. GitHub Action publishes dev build
|
|
125
|
-
```
|
|
126
|
-
npm publish --tag dev
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
### 5. Install the dev build
|
|
130
|
-
```bash
|
|
131
|
-
npm install soxo-bootstrap-core@dev
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
---
|
|
135
|
-
|
|
136
|
-
# π© Promotion Workflow (Dev β Master)
|
|
137
|
-
|
|
138
|
-
### 1. Switch to master
|
|
139
|
-
```bash
|
|
140
|
-
git checkout master
|
|
141
|
-
git pull origin master
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
### 2. Merge develop into master
|
|
145
|
-
```bash
|
|
146
|
-
git merge develop
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
### 3. Bump the stable version
|
|
150
|
-
```bash
|
|
151
|
-
npm version patch
|
|
152
|
-
# or
|
|
153
|
-
npm version minor
|
|
154
|
-
# or
|
|
155
|
-
npm version major
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
### 4. Push with tags
|
|
159
|
-
```bash
|
|
160
|
-
git push --follow-tags
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
### 5. GitHub Action publishes stable build
|
|
164
|
-
```
|
|
165
|
-
npm publish
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
### 6. Install stable version
|
|
169
|
-
```bash
|
|
170
|
-
npm install soxo-bootstrap-core
|
|
171
|
-
# or
|
|
172
|
-
npm install soxo-bootstrap-core@<version>
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
---
|
|
176
|
-
|
|
177
|
-
# π§ Publishing via GitHub Release UI
|
|
178
|
-
|
|
179
|
-
### Important
|
|
180
|
-
**The tag must match the `package.json` version exactly**, including the `v` prefix.
|
|
181
|
-
|
|
182
|
-
Example:
|
|
183
|
-
```
|
|
184
|
-
package.json: "version": "2.4.31-dev.0"
|
|
185
|
-
GitHub Tag: v2.4.31-dev.0
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
---
|
|
189
|
-
|
|
190
|
-
## Steps
|
|
191
|
-
|
|
192
|
-
### 1. Ensure `develop` branch is up to date
|
|
193
|
-
Push your changes.
|
|
194
|
-
|
|
195
|
-
### 2. Ensure version contains a dev suffix
|
|
196
|
-
If not:
|
|
197
|
-
```bash
|
|
198
|
-
npm version prerelease --preid=dev
|
|
199
|
-
git push --follow-tags
|
|
200
|
-
```
|
|
201
|
-
|
|
202
|
-
### 3. Open GitHub β Releases β Draft a new release
|
|
203
|
-
|
|
204
|
-
### 4. Create tag matching the version
|
|
205
|
-
Example:
|
|
206
|
-
```
|
|
207
|
-
v2.4.31-dev.0
|
|
208
|
-
```
|
|
209
|
-
|
|
210
|
-
### 5. Select target branch = `develop`
|
|
211
|
-
|
|
212
|
-
### 6. Title = tag version
|
|
213
|
-
Description is optional.
|
|
214
|
-
|
|
215
|
-
### 7. Publish the release
|
|
216
|
-
Triggers:
|
|
217
|
-
```
|
|
218
|
-
npm publish --tag dev
|
|
219
|
-
```
|
|
220
|
-
|
|
221
|
-
---
|
|
222
|
-
|
|
223
|
-
# βοΈ How GitHub Action Detects Release Type
|
|
224
|
-
|
|
225
|
-
If version contains `dev`:
|
|
226
|
-
```
|
|
227
|
-
npm publish --tag dev
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
Otherwise:
|
|
231
|
-
```
|
|
232
|
-
npm publish
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
---
|
|
236
|
-
|
|
237
|
-
# π Summary Table
|
|
238
|
-
|
|
239
|
-
| Step | Branch | Command | Tag | Publish Type |
|
|
240
|
-
|--------------------------|----------|------------------------------------------|------------------|--------------|
|
|
241
|
-
| Create task branch | master | `git checkout -b task/<ID>` | β | β |
|
|
242
|
-
| Merge task β develop | develop | `git merge task/<ID>` | β | β |
|
|
243
|
-
| Dev version | develop | `npm version prerelease --preid=dev` | `vX.Y.Z-dev.N` | dev |
|
|
244
|
-
| Publish dev build | develop | `git push --follow-tags` | β | dev |
|
|
245
|
-
| Merge develop β master | master | `git merge develop` | β | β |
|
|
246
|
-
| Stable version | master | `npm version patch/minor/major` | `vX.Y.Z` | latest |
|
|
247
|
-
| Publish stable build | master | `git push --follow-tags` | β | latest |
|
|
248
|
-
|
|
249
|
-
---
|
|
250
|
-
|
|
251
|
-
# β οΈ Common Mistakes & Fixes
|
|
252
|
-
|
|
253
|
-
| Mistake | Issue | Fix |
|
|
254
|
-
|-------------------------------|----------------------|---------------------------------|
|
|
255
|
-
| Tag doesn't match version | Publish fails | Always use `npm version` |
|
|
256
|
-
| Manual tag creation | Version mismatch | Avoid manual tagging |
|
|
257
|
-
| Dev publish from master | Wrong channel | Verify branch before tagging |
|
|
258
|
-
| Not pushing tags | Workflow wonβt run | Use `git push --follow-tags` |
|
|
259
|
-
| Merging incomplete tasks | Breaks dev builds | Test before merging |
|
|
260
|
-
|