ui-soxo-bootstrap-core 2.4.25-dev.29 → 2.4.25-dev.31
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/menu-tree/menu-tree.js +78 -0
- package/core/models/doctor/components/doctor-add/doctor-add.js +21 -2
- package/core/models/staff/components/staff-add/staff-add.js +29 -1
- package/core/models/users/components/assign-role/assign-role.js +228 -0
- package/core/models/users/components/assign-role/assign-role.scss +90 -0
- package/core/models/users/components/user-add/user-add.js +2 -2
- package/core/models/users/users.js +15 -0
- package/core/modules/index.js +3 -0
- package/core/modules/reporting/components/index.js +2 -1
- package/core/modules/steps/action-buttons.js +29 -41
- package/core/modules/steps/action-buttons.scss +16 -0
- package/core/modules/steps/steps.js +5 -1
- package/core/modules/steps/steps.scss +4 -3
- package/package.json +1 -1
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Checkbox, Collapse } from "antd";
|
|
3
|
+
|
|
4
|
+
const { Panel } = Collapse;
|
|
5
|
+
|
|
6
|
+
export const MenuTree = ({ menus, selectedMenus = [], toggleMenu, parentId = null, showCheckbox = true }) => {
|
|
7
|
+
const renderTree = (menuList, parentId = null) => {
|
|
8
|
+
return menuList.map((menu) => {
|
|
9
|
+
const children = menu.sub_menus || [];
|
|
10
|
+
|
|
11
|
+
const toggleMenuRecursive = (m, checked) => {
|
|
12
|
+
toggleMenu && toggleMenu(m.id, checked);
|
|
13
|
+
if (m.sub_menus && m.sub_menus.length > 0) {
|
|
14
|
+
m.sub_menus.forEach((c) => toggleMenuRecursive(c, checked));
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const onParentChange = (checked) => {
|
|
19
|
+
toggleMenu && toggleMenu(menu.id, checked);
|
|
20
|
+
children.forEach((c) => toggleMenuRecursive(c, checked));
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
if (children.length === 0) {
|
|
24
|
+
return (
|
|
25
|
+
<div
|
|
26
|
+
key={menu.id}
|
|
27
|
+
style={{
|
|
28
|
+
border: "1px solid rgba(198, 195, 195, 0.85)",
|
|
29
|
+
padding: "12px 16px",
|
|
30
|
+
marginBottom: 6,
|
|
31
|
+
background: "#fff",
|
|
32
|
+
display: "flex",
|
|
33
|
+
alignItems: "center",
|
|
34
|
+
gap: 8,
|
|
35
|
+
}}
|
|
36
|
+
>
|
|
37
|
+
{showCheckbox && (
|
|
38
|
+
<Checkbox
|
|
39
|
+
checked={selectedMenus.includes(menu.id)}
|
|
40
|
+
onChange={(e) => onParentChange(e.target.checked)}
|
|
41
|
+
/>
|
|
42
|
+
)}
|
|
43
|
+
<span>{menu.title || menu.caption}</span>
|
|
44
|
+
</div>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<Collapse key={menu.id} style={{ marginBottom: 6 }}>
|
|
50
|
+
<Panel
|
|
51
|
+
key={menu.id}
|
|
52
|
+
header={
|
|
53
|
+
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
|
|
54
|
+
{showCheckbox && (
|
|
55
|
+
<Checkbox
|
|
56
|
+
checked={children.every((c) => selectedMenus.includes(c.id))}
|
|
57
|
+
indeterminate={
|
|
58
|
+
children.some((c) => selectedMenus.includes(c.id)) &&
|
|
59
|
+
!children.every((c) => selectedMenus.includes(c.id))
|
|
60
|
+
}
|
|
61
|
+
onChange={(e) => onParentChange(e.target.checked)}
|
|
62
|
+
/>
|
|
63
|
+
)}
|
|
64
|
+
<span>{menu.title || menu.caption}</span>
|
|
65
|
+
</div>
|
|
66
|
+
}
|
|
67
|
+
>
|
|
68
|
+
<div style={{ paddingLeft: 20 }}>
|
|
69
|
+
{renderTree(children, menu.id)}
|
|
70
|
+
</div>
|
|
71
|
+
</Panel>
|
|
72
|
+
</Collapse>
|
|
73
|
+
);
|
|
74
|
+
});
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
return <>{renderTree(menus, parentId)}</>;
|
|
78
|
+
};
|
|
@@ -105,6 +105,11 @@ const DoctorAdd = ({ visible, onCancel, attributes, doctorId, doctorData, onSucc
|
|
|
105
105
|
return Promise.resolve();
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
if (!/^[a-zA-Z0-9]*$/.test(value)) {
|
|
109
|
+
setCodeStatus({ status: 'error', message: 'Code can only contain letters and numbers.' });
|
|
110
|
+
return Promise.reject(new Error('Code can only contain letters and numbers.'));
|
|
111
|
+
}
|
|
112
|
+
|
|
108
113
|
if (editMode && doctorData?.do_code === value) {
|
|
109
114
|
setCodeStatus({ status: 'success', message: 'Code is unchanged' });
|
|
110
115
|
return Promise.resolve();
|
|
@@ -218,7 +223,17 @@ const DoctorAdd = ({ visible, onCancel, attributes, doctorId, doctorData, onSucc
|
|
|
218
223
|
{/* NAME + CODE */}
|
|
219
224
|
<Row gutter={16}>
|
|
220
225
|
<Col span={12}>
|
|
221
|
-
<Form.Item
|
|
226
|
+
<Form.Item
|
|
227
|
+
label="Name"
|
|
228
|
+
name="name"
|
|
229
|
+
rules={[
|
|
230
|
+
{ required: true },
|
|
231
|
+
{
|
|
232
|
+
pattern: /^[A-Za-z]+$/,
|
|
233
|
+
message: 'Name should contain only alphabets',
|
|
234
|
+
},
|
|
235
|
+
]}
|
|
236
|
+
>
|
|
222
237
|
<Input autoComplete="off" ref={nameInputRef} placeholder="Enter Name" onKeyDown={handleEnterKey} />
|
|
223
238
|
</Form.Item>
|
|
224
239
|
</Col>
|
|
@@ -369,7 +384,11 @@ const DoctorAdd = ({ visible, onCancel, attributes, doctorId, doctorData, onSucc
|
|
|
369
384
|
</Col>
|
|
370
385
|
|
|
371
386
|
<Col span={8}>
|
|
372
|
-
<Form.Item
|
|
387
|
+
<Form.Item
|
|
388
|
+
label="Email ID"
|
|
389
|
+
name="email_id"
|
|
390
|
+
rules={[{ type: 'email', message: 'Please enter a valid email address' }]}
|
|
391
|
+
>
|
|
373
392
|
<Input placeholder="Enter Email ID" onKeyDown={handleEnterKey} />
|
|
374
393
|
</Form.Item>
|
|
375
394
|
</Col>
|
|
@@ -13,6 +13,7 @@ const StaffAdd = ({ visible, onCancel, staffId, staffData, onSuccess }) => {
|
|
|
13
13
|
* ------------------------------- */
|
|
14
14
|
const [loading, setLoading] = useState(false);
|
|
15
15
|
const [designations, setDesignations] = useState([]);
|
|
16
|
+
const [staffList, setStaffList] = useState([]);
|
|
16
17
|
const [isSubmitDisabled, setIsSubmitDisabled] = useState(true);
|
|
17
18
|
const [codeStatus, setCodeStatus] = useState({ status: '', message: '' });
|
|
18
19
|
|
|
@@ -47,6 +48,7 @@ const StaffAdd = ({ visible, onCancel, staffId, staffData, onSuccess }) => {
|
|
|
47
48
|
useEffect(() => {
|
|
48
49
|
if (visible) {
|
|
49
50
|
getDesignations();
|
|
51
|
+
if (!editMode) getStaffList();
|
|
50
52
|
}
|
|
51
53
|
}, [visible]);
|
|
52
54
|
|
|
@@ -95,6 +97,18 @@ const StaffAdd = ({ visible, onCancel, staffId, staffData, onSuccess }) => {
|
|
|
95
97
|
.finally(() => setLoading(false));
|
|
96
98
|
};
|
|
97
99
|
|
|
100
|
+
/** -------------------------------
|
|
101
|
+
* API CALL – Staff List
|
|
102
|
+
* ------------------------------- */
|
|
103
|
+
const getStaffList = () => {
|
|
104
|
+
UsersAPI.getAllStaff()
|
|
105
|
+
.then((res) => {
|
|
106
|
+
if (Array.isArray(res)) {
|
|
107
|
+
setStaffList(res);
|
|
108
|
+
}
|
|
109
|
+
})
|
|
110
|
+
.catch((err) => console.error(err));
|
|
111
|
+
};
|
|
98
112
|
|
|
99
113
|
/** -------------------------------
|
|
100
114
|
* VALIDATION – Input
|
|
@@ -149,6 +163,16 @@ const StaffAdd = ({ visible, onCancel, staffId, staffData, onSuccess }) => {
|
|
|
149
163
|
return Promise.resolve();
|
|
150
164
|
};
|
|
151
165
|
|
|
166
|
+
const validateSerial = (_, value) => {
|
|
167
|
+
if (!value) {
|
|
168
|
+
return Promise.resolve();
|
|
169
|
+
}
|
|
170
|
+
const exists = staffList.some((staff) => String(staff.slNo) === value);
|
|
171
|
+
if (exists) {
|
|
172
|
+
return Promise.reject('Serial Number already exists');
|
|
173
|
+
}
|
|
174
|
+
return Promise.resolve();
|
|
175
|
+
};
|
|
152
176
|
|
|
153
177
|
/** -------------------------------
|
|
154
178
|
* UTIL – Enter Key Navigation
|
|
@@ -350,7 +374,11 @@ const StaffAdd = ({ visible, onCancel, staffId, staffData, onSuccess }) => {
|
|
|
350
374
|
</Col>
|
|
351
375
|
|
|
352
376
|
<Col span={8}>
|
|
353
|
-
<Form.Item
|
|
377
|
+
<Form.Item
|
|
378
|
+
label="Serial Number"
|
|
379
|
+
name="slno"
|
|
380
|
+
rules={[{ message: 'only numbers are allowed', pattern: /^\d*$/ }, { validator: validateSerial }]}
|
|
381
|
+
>
|
|
354
382
|
<Input autoComplete="off" maxLength={5} placeholder="Enter Serial Number" onKeyDown={handleEnterKey} disabled={editMode} />
|
|
355
383
|
</Form.Item>
|
|
356
384
|
</Col>
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import React, { useMemo, useState, useEffect } from 'react';
|
|
2
|
+
import { Checkbox, Input, Typography, Empty, Avatar, List, Card, message, Skeleton } from 'antd';
|
|
3
|
+
|
|
4
|
+
import { UsersAPI, RolesAPI, MenusAPI } from '../../..';
|
|
5
|
+
import { UserRolesAPI } from '../../..';
|
|
6
|
+
import { useParams } from 'react-router-dom';
|
|
7
|
+
import { Button } from '../../../../lib';
|
|
8
|
+
import { MenuTree } from '../../../../lib/elements/basic/menu-tree/menu-tree';
|
|
9
|
+
import './assign-role.scss';
|
|
10
|
+
|
|
11
|
+
const { Text } = Typography;
|
|
12
|
+
|
|
13
|
+
const { Search } = Input;
|
|
14
|
+
|
|
15
|
+
export default function AssignRole() {
|
|
16
|
+
const { id } = useParams();
|
|
17
|
+
|
|
18
|
+
const [user, setUser] = useState(null);
|
|
19
|
+
const [roles, setRoles] = useState([]);
|
|
20
|
+
const [activeRole, setActiveRole] = useState(null);
|
|
21
|
+
const [modules, setModules] = useState([]);
|
|
22
|
+
const [selectedRoles, setSelectedRoles] = useState([]);
|
|
23
|
+
// const [loadingUser, setLoadingUser] = useState(false);
|
|
24
|
+
// const [loadingRoles, setLoadingRoles] = useState(false);
|
|
25
|
+
const [loadingMenus, setLoadingMenus] = useState(false);
|
|
26
|
+
const [selectedMenus, setSelectedMenus] = useState([]);
|
|
27
|
+
const [search, setSearch] = useState('');
|
|
28
|
+
|
|
29
|
+
/** Load user by id */
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (id) loadUser();
|
|
32
|
+
}, [id]);
|
|
33
|
+
|
|
34
|
+
const loadUser = async () => {
|
|
35
|
+
setLoadingUser(true);
|
|
36
|
+
try {
|
|
37
|
+
const res = await UsersAPI.getUserRole({ id });
|
|
38
|
+
const list = Array.isArray(res?.result) ? res.result : [];
|
|
39
|
+
|
|
40
|
+
if (!list.length) {
|
|
41
|
+
setUser(null);
|
|
42
|
+
setSelectedRoles([]);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// 1️⃣ Extract user from first record
|
|
47
|
+
const userInfo = list[0].user;
|
|
48
|
+
setUser({
|
|
49
|
+
id: userInfo.id,
|
|
50
|
+
name: userInfo.name,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// 2️⃣ Extract VALID role IDs (ignore nulls & duplicates)
|
|
54
|
+
const roleIds = [
|
|
55
|
+
...new Set(
|
|
56
|
+
list
|
|
57
|
+
.filter((item) => item.role_id) // ignore null role_id
|
|
58
|
+
.map((item) => Number(item.role_id))
|
|
59
|
+
),
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
setSelectedRoles(roleIds);
|
|
63
|
+
} catch (e) {
|
|
64
|
+
console.error(e);
|
|
65
|
+
message.error('Unable to load user details');
|
|
66
|
+
} finally {
|
|
67
|
+
setLoadingUser(false);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/** Load roles */
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
getRoles();
|
|
74
|
+
}, []);
|
|
75
|
+
|
|
76
|
+
const getRoles = async () => {
|
|
77
|
+
setLoadingRoles(true);
|
|
78
|
+
try {
|
|
79
|
+
const res = await RolesAPI.getRole();
|
|
80
|
+
const activeRoles = Array.isArray(res?.result) ? res.result.filter((r) => r.active === 'Y') : [];
|
|
81
|
+
setRoles(activeRoles);
|
|
82
|
+
} catch (e) {
|
|
83
|
+
console.error(e);
|
|
84
|
+
setRoles([]);
|
|
85
|
+
} finally {
|
|
86
|
+
setLoadingRoles(false);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const filterAndSortMenus = (menus, allowedIds) => {
|
|
91
|
+
return menus
|
|
92
|
+
.filter((m) => allowedIds.includes(m.id))
|
|
93
|
+
.map((m) => ({
|
|
94
|
+
...m,
|
|
95
|
+
sub_menus: filterAndSortMenus(m.sub_menus || [], allowedIds),
|
|
96
|
+
}));
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
/** Load menus for a role */
|
|
100
|
+
const loadRoleMenus = async (role) => {
|
|
101
|
+
setActiveRole(role);
|
|
102
|
+
setSelectedMenus(role.menu_ids || []);
|
|
103
|
+
setLoadingMenus(true);
|
|
104
|
+
try {
|
|
105
|
+
const res = await MenusAPI.getCoreMenuLists();
|
|
106
|
+
const allMenus = res.result || [];
|
|
107
|
+
const filteredMenus = filterAndSortMenus(allMenus, role.menu_ids);
|
|
108
|
+
setModules(filteredMenus);
|
|
109
|
+
} catch (e) {
|
|
110
|
+
console.error(e);
|
|
111
|
+
setModules([]);
|
|
112
|
+
} finally {
|
|
113
|
+
setLoadingMenus(false);
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
/** Toggle menu selection */
|
|
118
|
+
const toggleMenu = (menuId, checked) => {
|
|
119
|
+
setSelectedMenus((prev) => (checked ? [...prev, menuId] : prev.filter((id) => id !== menuId)));
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
/** Filtered roles */
|
|
123
|
+
const filteredRoles = useMemo(() => {
|
|
124
|
+
if (!search) return roles;
|
|
125
|
+
return roles.filter((r) => r.name.toLowerCase().includes(search.toLowerCase()));
|
|
126
|
+
}, [roles, search]);
|
|
127
|
+
|
|
128
|
+
/** Toggle role selection */
|
|
129
|
+
const toggleRole = (roleId, checked) => {
|
|
130
|
+
const next = checked ? [...selectedRoles, roleId] : selectedRoles.filter((v) => v !== roleId);
|
|
131
|
+
setSelectedRoles(next);
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const handleSaveUserRole = async () => {
|
|
135
|
+
if (!id || !selectedRoles.length) {
|
|
136
|
+
message.warning('User or roles missing');
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
message.loading({ key: 'save', content: 'Saving user roles...' });
|
|
142
|
+
|
|
143
|
+
await Promise.all(
|
|
144
|
+
selectedRoles.map((roleId) =>
|
|
145
|
+
UserRolesAPI.addUserRole({
|
|
146
|
+
values: {
|
|
147
|
+
user_id: id,
|
|
148
|
+
role_id: roleId,
|
|
149
|
+
},
|
|
150
|
+
})
|
|
151
|
+
)
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
message.success({ key: 'save', content: 'User roles saved successfully' });
|
|
155
|
+
} catch (err) {
|
|
156
|
+
console.error(err);
|
|
157
|
+
message.error({ key: 'save', content: 'Failed to save user roles' });
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
return (
|
|
162
|
+
<section className="assign-role">
|
|
163
|
+
{/* LEFT PANEL */}
|
|
164
|
+
<Card className="left-panel" bodyStyle={{ padding: 12 }}>
|
|
165
|
+
<div size="small" className="user-card">
|
|
166
|
+
<Avatar size={40}>{user?.name[0]}</Avatar>
|
|
167
|
+
|
|
168
|
+
<div className="user-info">
|
|
169
|
+
<div>{user?.name || '--'}</div>
|
|
170
|
+
<Text className="user-id">ID : {user?.id || '--'}</Text>
|
|
171
|
+
</div>
|
|
172
|
+
</div>
|
|
173
|
+
|
|
174
|
+
<div className="role-list-header">
|
|
175
|
+
<Text strong>Role List ({roles.length})</Text>
|
|
176
|
+
</div>
|
|
177
|
+
|
|
178
|
+
<Search placeholder="Enter Search Value" allowClear style={{ width: 300, marginBottom: '0px' }} onChange={(e) => setSearch(e.target.value)} />
|
|
179
|
+
{/* <Input className="role-search" placeholder="Search Here" allowClear value={search} onChange={(e) => setSearch(e.target.value)} /> */}
|
|
180
|
+
|
|
181
|
+
<List
|
|
182
|
+
itemLayout="horizontal"
|
|
183
|
+
dataSource={filteredRoles}
|
|
184
|
+
renderItem={(role) => (
|
|
185
|
+
<List.Item
|
|
186
|
+
key={role.id}
|
|
187
|
+
onClick={() => loadRoleMenus(role)}
|
|
188
|
+
className={`role-item ${activeRole?.id === role.id ? 'active' : ''}`}
|
|
189
|
+
actions={[
|
|
190
|
+
<Checkbox
|
|
191
|
+
checked={selectedRoles.includes(role.id)}
|
|
192
|
+
onChange={(e) => toggleRole(role.id, e.target.checked)}
|
|
193
|
+
onClick={(e) => e.stopPropagation()}
|
|
194
|
+
/>,
|
|
195
|
+
]}
|
|
196
|
+
>
|
|
197
|
+
<List.Item.Meta avatar={<Avatar size={28}>{role.name[0]}</Avatar>} title={role.name} description={role.description} />
|
|
198
|
+
</List.Item>
|
|
199
|
+
)}
|
|
200
|
+
/>
|
|
201
|
+
</Card>
|
|
202
|
+
|
|
203
|
+
{/* RIGHT PANEL */}
|
|
204
|
+
<Card className="right-panel">
|
|
205
|
+
<div className="menus-header">
|
|
206
|
+
<Text>Menus {activeRole ? `– ${activeRole.name}` : ''}</Text>
|
|
207
|
+
<div className="sub-text">It's not editable. To edit it, go to the role editing page.</div>
|
|
208
|
+
</div>
|
|
209
|
+
|
|
210
|
+
<div className="menus-content">
|
|
211
|
+
{loadingMenus ? (
|
|
212
|
+
<Skeleton active paragraph={{ rows: 6 }} />
|
|
213
|
+
) : modules.length === 0 ? (
|
|
214
|
+
<Empty description="Click a role to view menus" />
|
|
215
|
+
) : (
|
|
216
|
+
<MenuTree menus={modules} selectedMenus={selectedMenus} toggleMenu={toggleMenu} showCheckbox={false} />
|
|
217
|
+
)}
|
|
218
|
+
</div>
|
|
219
|
+
|
|
220
|
+
<div className="footer-actions">
|
|
221
|
+
<Button type="primary" onClick={handleSaveUserRole}>
|
|
222
|
+
Save
|
|
223
|
+
</Button>
|
|
224
|
+
</div>
|
|
225
|
+
</Card>
|
|
226
|
+
</section>
|
|
227
|
+
);
|
|
228
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
.assign-role {
|
|
2
|
+
display: flex;
|
|
3
|
+
gap: 6px;
|
|
4
|
+
|
|
5
|
+
.left-panel {
|
|
6
|
+
width: 340px;
|
|
7
|
+
|
|
8
|
+
.user-card {
|
|
9
|
+
margin-bottom: 12px;
|
|
10
|
+
background: #fafafa;
|
|
11
|
+
border: none;
|
|
12
|
+
display: flex;
|
|
13
|
+
align-items: center;
|
|
14
|
+
gap: 12px;
|
|
15
|
+
padding: 8px;
|
|
16
|
+
|
|
17
|
+
.user-info {
|
|
18
|
+
font-weight: 500;
|
|
19
|
+
|
|
20
|
+
.user-id {
|
|
21
|
+
font-size: 12px;
|
|
22
|
+
color: rgba(0, 0, 0, 0.45);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.role-list-header {
|
|
28
|
+
display: flex;
|
|
29
|
+
justify-content: space-between;
|
|
30
|
+
margin-bottom: 12px;
|
|
31
|
+
strong {
|
|
32
|
+
font-weight: 600;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.role-search {
|
|
37
|
+
margin: 12px 0;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.role-item {
|
|
41
|
+
cursor: pointer;
|
|
42
|
+
border-radius: 6px;
|
|
43
|
+
padding: 8px 12px;
|
|
44
|
+
|
|
45
|
+
&.active {
|
|
46
|
+
background: #e6f7ff;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.ant-list-item-meta-title {
|
|
50
|
+
font-weight: 500;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.ant-list-item-meta-description {
|
|
54
|
+
font-size: 12px;
|
|
55
|
+
color: rgba(0, 0, 0, 0.45);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.right-panel {
|
|
61
|
+
flex: 1;
|
|
62
|
+
display: flex;
|
|
63
|
+
flex-direction: column;
|
|
64
|
+
justify-content: space-between;
|
|
65
|
+
padding: 16px;
|
|
66
|
+
|
|
67
|
+
.menus-header {
|
|
68
|
+
font-weight: 500;
|
|
69
|
+
margin-bottom: 4px;
|
|
70
|
+
color: #000;
|
|
71
|
+
|
|
72
|
+
.sub-text {
|
|
73
|
+
font-size: 12px;
|
|
74
|
+
color: #888;
|
|
75
|
+
margin-top: 4px;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.menus-content {
|
|
80
|
+
margin-top: 16px;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.footer-actions {
|
|
84
|
+
margin-top: 16px;
|
|
85
|
+
display: flex;
|
|
86
|
+
justify-content: flex-end;
|
|
87
|
+
gap: 8px;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -728,7 +728,7 @@ const UserAdd = ({ model, callback, edit, history, formContent, match, additiona
|
|
|
728
728
|
<Input placeholder="Enter User Group" />
|
|
729
729
|
</Form.Item>
|
|
730
730
|
</Col>
|
|
731
|
-
<Col span={8}>
|
|
731
|
+
{/* <Col span={8}>
|
|
732
732
|
<Form.Item name="role_id" label="Role" rules={[{ required: true, message: 'Please select a Role' }]}>
|
|
733
733
|
<Select placeholder="Select Role">
|
|
734
734
|
{roles.map((role) => (
|
|
@@ -738,7 +738,7 @@ const UserAdd = ({ model, callback, edit, history, formContent, match, additiona
|
|
|
738
738
|
))}
|
|
739
739
|
</Select>
|
|
740
740
|
</Form.Item>
|
|
741
|
-
</Col>
|
|
741
|
+
</Col> */}
|
|
742
742
|
|
|
743
743
|
{/* <Form.Item name="mobile" label="Mobile" required={mobileRequired}>
|
|
744
744
|
<Input placeholder="Enter Mobile" />
|
|
@@ -273,8 +273,23 @@ class Users extends Base {
|
|
|
273
273
|
updateUser = ({ id, formBody }) => {
|
|
274
274
|
return ApiUtils.put({ url: `users/update-user/${id}`, formBody });
|
|
275
275
|
};
|
|
276
|
+
|
|
277
|
+
/* The `getUser` method is a function that takes an object with an `id` property as a parameter. It
|
|
278
|
+
then uses the `ApiUtils.get` function to make a GET request to the endpoint `users/` to fetch
|
|
279
|
+
user data based on the provided `id`. This method is used to retrieve a specific user's
|
|
280
|
+
information from the API. */
|
|
281
|
+
|
|
276
282
|
getUser = ({ id }) => {
|
|
277
283
|
return ApiUtils.get({ url: `users/${id}` });
|
|
284
|
+
};
|
|
285
|
+
/* The `getUserRole` method is a function that takes an object with an `id` property as a parameter.
|
|
286
|
+
It then uses the `ApiUtils.get` function to make a GET request to the endpoint `core-user-roles`
|
|
287
|
+
with the `user_id` parameter set to the provided `id`. This method is used to fetch the role or
|
|
288
|
+
roles associated with a specific user from the API. */
|
|
289
|
+
|
|
290
|
+
getUserRole = ({ id }) => {
|
|
291
|
+
return ApiUtils.get({ url: `core-user-roles?user_id=${id}` });
|
|
292
|
+
|
|
278
293
|
};
|
|
279
294
|
|
|
280
295
|
/**
|
package/core/modules/index.js
CHANGED
|
@@ -8,6 +8,8 @@ import GenericDetail from './generic/components/generic-detail/generic-detail';
|
|
|
8
8
|
|
|
9
9
|
import ModuleRoutes from './module-routes/module-routes';
|
|
10
10
|
|
|
11
|
+
import { AssignRole } from './reporting/components';
|
|
12
|
+
|
|
11
13
|
// All Dashboard Components
|
|
12
14
|
|
|
13
15
|
import DashboardCard from './dashboard/components/dashboard-card/dashboard-card';
|
|
@@ -30,6 +32,7 @@ export {
|
|
|
30
32
|
GenericDetail,
|
|
31
33
|
ModuleRoutes,
|
|
32
34
|
DashboardCard,
|
|
35
|
+
AssignRole,
|
|
33
36
|
PopQueryDashboard,
|
|
34
37
|
HomePageAPI,
|
|
35
38
|
ReportingDashboard,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import UserEdit from '../../../models/users/components/user-add/user-edit';
|
|
2
2
|
|
|
3
3
|
import UserAdd from '../../../models/users/components/user-add/user-add';
|
|
4
|
+
import AssignRole from '../../../models/users/components/assign-role/assign-role';
|
|
4
5
|
|
|
5
|
-
export { UserEdit, UserAdd };
|
|
6
|
+
export { UserEdit, UserAdd, AssignRole };
|
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
* Handles navigation and action controls for a multi-step process,
|
|
4
4
|
* including dynamic content rendering and process completion actions.
|
|
5
5
|
*/
|
|
6
|
-
import React from 'react';
|
|
6
|
+
import React, { useState } from 'react';
|
|
7
7
|
import { Skeleton } from 'antd';
|
|
8
8
|
import { Button } from '../../lib';
|
|
9
|
+
import './action-buttons.scss';
|
|
9
10
|
|
|
10
11
|
export default function ActionButtons({
|
|
11
12
|
loading,
|
|
@@ -21,63 +22,50 @@ export default function ActionButtons({
|
|
|
21
22
|
nextProcessId,
|
|
22
23
|
timelineCollapsed,
|
|
23
24
|
}) {
|
|
25
|
+
const [showNextProcess, setShowNextProcess] = useState(false);
|
|
26
|
+
|
|
24
27
|
return (
|
|
25
28
|
<>
|
|
26
29
|
<div style={{ minHeight: 300 }}>{loading ? <Skeleton active /> : renderDynamicComponent()}</div>
|
|
27
30
|
<>
|
|
28
|
-
<div
|
|
31
|
+
<div className="action-buttons-container">
|
|
29
32
|
{/* Back button */}
|
|
30
|
-
<Button disabled={activeStep === 0} onClick={handlePrevious}
|
|
33
|
+
<Button disabled={activeStep === 0} onClick={handlePrevious}>
|
|
31
34
|
Back
|
|
32
35
|
</Button>
|
|
33
36
|
|
|
34
37
|
{/* Skip button */}
|
|
35
38
|
{steps.length > 0 && steps[activeStep]?.allow_skip === 'Y' && (
|
|
36
|
-
<Button
|
|
37
|
-
type="default"
|
|
38
|
-
onClick={handleSkip}
|
|
39
|
-
style={{
|
|
40
|
-
borderRadius: 4,
|
|
41
|
-
}}
|
|
42
|
-
disabled={activeStep === steps.length - 1}
|
|
43
|
-
>
|
|
39
|
+
<Button type="default" onClick={handleSkip} disabled={activeStep === steps.length - 1}>
|
|
44
40
|
Skip
|
|
45
41
|
</Button>
|
|
46
42
|
)}
|
|
47
43
|
|
|
48
44
|
{/* Next / Finish / Start Next */}
|
|
49
45
|
{steps[activeStep]?.order_seqtype === 'E' ? (
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
onClick={
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
46
|
+
<>
|
|
47
|
+
{!showNextProcess && (
|
|
48
|
+
<Button
|
|
49
|
+
type="primary"
|
|
50
|
+
onClick={async () => {
|
|
51
|
+
const success = await handleFinish();
|
|
52
|
+
if (success && nextProcessId?.next_process_id) {
|
|
53
|
+
setShowNextProcess(true);
|
|
54
|
+
}
|
|
55
|
+
}}
|
|
56
|
+
>
|
|
57
|
+
Finish
|
|
58
|
+
</Button>
|
|
59
|
+
)}
|
|
60
|
+
|
|
61
|
+
{showNextProcess && nextProcessId?.next_process_id && (
|
|
62
|
+
<Button type="primary" onClick={handleStartNextProcess}>
|
|
63
|
+
Start {nextProcessId.next_process_name}
|
|
64
|
+
</Button>
|
|
65
|
+
)}
|
|
66
|
+
</>
|
|
71
67
|
) : (
|
|
72
|
-
<Button
|
|
73
|
-
type="primary"
|
|
74
|
-
// shape="round"
|
|
75
|
-
style={{
|
|
76
|
-
borderRadius: 4,
|
|
77
|
-
}}
|
|
78
|
-
disabled={activeStep === steps.length - 1 || !isStepCompleted}
|
|
79
|
-
onClick={handleNext}
|
|
80
|
-
>
|
|
68
|
+
<Button type="primary" disabled={activeStep === steps.length - 1 || !isStepCompleted} onClick={handleNext}>
|
|
81
69
|
Next →
|
|
82
70
|
</Button>
|
|
83
71
|
)}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
.action-buttons-container {
|
|
2
|
+
margin-top: 38px;
|
|
3
|
+
display: flex;
|
|
4
|
+
justify-content: flex-start;
|
|
5
|
+
gap: 10px;
|
|
6
|
+
position: sticky;
|
|
7
|
+
bottom: 0;
|
|
8
|
+
z-index: 1000;
|
|
9
|
+
background: #fff;
|
|
10
|
+
padding: 10px 0;
|
|
11
|
+
border-top: 1px solid #f0f0f0;
|
|
12
|
+
|
|
13
|
+
.ant-btn {
|
|
14
|
+
border-radius: 4px;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -204,7 +204,9 @@ export default function ProcessStepsPage({ processId, match, CustomComponents =
|
|
|
204
204
|
*/
|
|
205
205
|
const handleFinish = async () => {
|
|
206
206
|
const final = recordStepTime();
|
|
207
|
-
|
|
207
|
+
const success = await handleProcessSubmit(final);
|
|
208
|
+
if (success && !nextProcessId) props.history?.goBack();
|
|
209
|
+
return success;
|
|
208
210
|
};
|
|
209
211
|
/**
|
|
210
212
|
* Start Next Process
|
|
@@ -268,6 +270,7 @@ export default function ProcessStepsPage({ processId, match, CustomComponents =
|
|
|
268
270
|
* and external window view.
|
|
269
271
|
*/
|
|
270
272
|
const renderContent = () => (
|
|
273
|
+
<div>
|
|
271
274
|
<Card>
|
|
272
275
|
<Row gutter={20}>
|
|
273
276
|
<Col xs={24} sm={24} lg={timelineCollapsed ? 2 : 6}>
|
|
@@ -303,6 +306,7 @@ export default function ProcessStepsPage({ processId, match, CustomComponents =
|
|
|
303
306
|
</Col>
|
|
304
307
|
</Row>
|
|
305
308
|
</Card>
|
|
309
|
+
</div>
|
|
306
310
|
);
|
|
307
311
|
/**
|
|
308
312
|
* Renders content in both the main window and an external window
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
.timeline-card .ant-card-body {
|
|
2
|
-
padding: 20px;
|
|
2
|
+
// padding: 20px;
|
|
3
|
+
border: none;
|
|
3
4
|
min-height: 400px;
|
|
4
|
-
position: fixed; /* For positioning the arrow */
|
|
5
|
+
// position: fixed; /* For positioning the arrow */
|
|
5
6
|
}
|
|
6
7
|
|
|
7
8
|
.timeline-sidebar {
|
|
@@ -46,7 +47,7 @@
|
|
|
46
47
|
|
|
47
48
|
.vertical-line {
|
|
48
49
|
width: 2px;
|
|
49
|
-
height:
|
|
50
|
+
height: 20px;
|
|
50
51
|
background: #d9d9d9;
|
|
51
52
|
}
|
|
52
53
|
|