ui-soxo-bootstrap-core 2.5.6 → 2.5.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.
- package/core/lib/elements/basic/dragabble-wrapper/draggable-wrapper.js +116 -41
- package/core/lib/elements/basic/menu-tree/menu-tree.js +114 -0
- package/core/lib/elements/basic/switch/switch.js +1 -1
- package/core/models/menus/components/menu-add/menu-add.js +29 -28
- package/core/models/menus/components/menu-lists/menu-lists.js +369 -213
- package/core/models/menus/components/menu-lists/menu-lists.scss +8 -3
- package/core/models/menus/menus.js +267 -267
- package/core/models/roles/components/role-add/role-add.js +292 -228
- package/core/models/roles/components/role-add/role-add.scss +4 -0
- package/core/models/roles/components/role-list/role-list.js +326 -348
- package/core/models/roles/roles.js +182 -174
- package/core/models/user-roles/user-roles.js +14 -0
- package/core/models/users/components/assign-role/assign-role.js +318 -0
- package/core/models/users/components/assign-role/assign-role.scss +117 -0
- package/core/models/users/components/user-add/user-edit.js +4 -5
- package/core/models/users/components/user-list/user-list.js +1 -1
- 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/package.json +1 -1
|
@@ -1,248 +1,312 @@
|
|
|
1
1
|
import React, { useState, useEffect, useContext } from 'react';
|
|
2
|
-
import { useLocation,useParams } from 'react-router-dom';
|
|
3
|
-
import { Skeleton, Typography, message, Form, Input,
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
import { ModelsAPI, PagesAPI, RolesAPI,MenusAPI } from '../../..';
|
|
2
|
+
import { useLocation, useParams } from 'react-router-dom';
|
|
3
|
+
import { Skeleton, Typography, message, Form, Input, Collapse, Checkbox } from 'antd';
|
|
4
|
+
import { GlobalContext } from './../../../../lib';
|
|
5
|
+
import { Button } from './../../../../lib';
|
|
6
|
+
import { ModelsAPI, PagesAPI, RolesAPI, MenusAPI } from '../../..';
|
|
7
|
+
import './role-add.scss';
|
|
7
8
|
|
|
8
9
|
const { Title } = Typography;
|
|
9
|
-
const { Option } = Select;
|
|
10
10
|
const { Panel } = Collapse;
|
|
11
11
|
|
|
12
|
-
const RoleAdd = ({ model, callback, edit,
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
if (formContent.attributes) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
formContent.attributes = {};
|
|
24
|
-
}
|
|
25
|
-
} else {
|
|
12
|
+
const RoleAdd = ({ model, callback, edit, formContent = {}, match, additional_queries = [] }) => {
|
|
13
|
+
let mode = 'Add';
|
|
14
|
+
if (formContent.id) mode = 'Edit';
|
|
15
|
+
else if (formContent.copy) mode = 'copy';
|
|
16
|
+
|
|
17
|
+
// Parse attributes safely
|
|
18
|
+
if (formContent.attributes) {
|
|
19
|
+
if (typeof formContent.attributes === 'string' && formContent.attributes !== '') {
|
|
20
|
+
try {
|
|
21
|
+
formContent.attributes = JSON.parse(formContent.attributes);
|
|
22
|
+
} catch {
|
|
26
23
|
formContent.attributes = {};
|
|
24
|
+
}
|
|
25
|
+
} else {
|
|
26
|
+
formContent.attributes = {};
|
|
27
|
+
}
|
|
28
|
+
} else {
|
|
29
|
+
formContent.attributes = {};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const [loading, setLoading] = useState(true);
|
|
33
|
+
const [form] = Form.useForm();
|
|
34
|
+
const [pages, setPages] = useState([]);
|
|
35
|
+
const [models, setModels] = useState([]);
|
|
36
|
+
const [menuList, setMenuList] = useState([]);
|
|
37
|
+
const [showMenus, setShowMenus] = useState(false);
|
|
38
|
+
// for deselected menu for editing
|
|
39
|
+
const [originalMenus, setOriginalMenus] = useState([]);
|
|
40
|
+
|
|
41
|
+
// Selected menus (array of IDs)
|
|
42
|
+
const [selectedMenus, setSelectedMenus] = useState([]);
|
|
43
|
+
|
|
44
|
+
const location = useLocation();
|
|
45
|
+
const query = new URLSearchParams(location.search);
|
|
46
|
+
let step = parseInt(query.get('step')) || 1;
|
|
47
|
+
|
|
48
|
+
const { user = {} } = useContext(GlobalContext);
|
|
49
|
+
|
|
50
|
+
// On component mount
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
getPages();
|
|
53
|
+
getModels();
|
|
54
|
+
loadMenus();
|
|
55
|
+
setShowMenus(true);
|
|
56
|
+
|
|
57
|
+
// Preselect menus when editing
|
|
58
|
+
if (formContent && formContent.menu_ids) {
|
|
59
|
+
let menus = formContent.menu_ids;
|
|
60
|
+
|
|
61
|
+
setSelectedMenus(menus);
|
|
62
|
+
// keep original copy for deselect comparison
|
|
63
|
+
setOriginalMenus(menus);
|
|
27
64
|
}
|
|
28
65
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
66
|
+
setLoading(false);
|
|
67
|
+
}, [formContent]);
|
|
68
|
+
|
|
69
|
+
// Load pages
|
|
70
|
+
const getPages = () => {
|
|
71
|
+
PagesAPI.getPages().then((res) => setPages(res.result || []));
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// Load models
|
|
75
|
+
const getModels = () => {
|
|
76
|
+
ModelsAPI.get().then((res) => setModels(res.result || []));
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// Load top-level menus
|
|
80
|
+
const loadMenus = () => {
|
|
81
|
+
MenusAPI.getCoreMenuLists()
|
|
82
|
+
.then((res) => setMenuList(res.result || []))
|
|
83
|
+
.catch(console.error);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// Toggle menu selection
|
|
87
|
+
const toggleMenu = (id, checked) => {
|
|
88
|
+
setSelectedMenus((prev) => (checked ? [...prev, id] : prev.filter((m) => m !== id)));
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
// Submit handler
|
|
92
|
+
// const onSubmit = (values) => {
|
|
93
|
+
// setLoading(true);
|
|
94
|
+
|
|
95
|
+
// const payload = {
|
|
96
|
+
// ...values,
|
|
97
|
+
// menu_ids
|
|
98
|
+
// : selectedMenus,
|
|
99
|
+
// attributes: JSON.stringify(values.attributes || {}),
|
|
100
|
+
// };
|
|
101
|
+
|
|
102
|
+
// if (formContent.id) {
|
|
103
|
+
// RolesAPI.updateRole({ id: formContent.id, formBody: payload })
|
|
104
|
+
// .then(() => {
|
|
105
|
+
// message.success('Role Updated');
|
|
106
|
+
// setLoading(false);
|
|
107
|
+
// callback();
|
|
108
|
+
// })
|
|
109
|
+
// .catch(() => setLoading(false));
|
|
110
|
+
// } else {
|
|
111
|
+
// additional_queries.forEach(({ field, value }) => {
|
|
112
|
+
// payload[field] = value;
|
|
113
|
+
// });
|
|
114
|
+
// RolesAPI.createRole(payload)
|
|
115
|
+
// .then(() => {
|
|
116
|
+
// message.success('Role Added');
|
|
117
|
+
// setLoading(false);
|
|
118
|
+
// callback();
|
|
119
|
+
// })
|
|
120
|
+
// .catch(() => setLoading(false));
|
|
121
|
+
// }
|
|
122
|
+
// };
|
|
123
|
+
const onSubmit = async (values) => {
|
|
124
|
+
setLoading(true);
|
|
125
|
+
|
|
126
|
+
// Find menus that were originally selected but now deselected
|
|
127
|
+
const deselectedMenus = originalMenus.filter((id) => !selectedMenus.includes(id));
|
|
128
|
+
|
|
129
|
+
const payload = {
|
|
130
|
+
...values,
|
|
131
|
+
menu_ids: selectedMenus,
|
|
132
|
+
deselect_array: deselectedMenus, // add deselected menus when edit time
|
|
133
|
+
attributes: JSON.stringify(values.attributes || {}),
|
|
77
134
|
};
|
|
78
135
|
|
|
79
|
-
//
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
{ field: 'step', value: 1 },
|
|
84
|
-
{ field: 'header_id', value: null }
|
|
85
|
-
]
|
|
86
|
-
})
|
|
87
|
-
.then(res => setMenuList(res.result || []))
|
|
88
|
-
.catch(console.error);
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
// Submit
|
|
94
|
-
const onSubmit = (values) => {
|
|
95
|
-
setLoading(true);
|
|
96
|
-
let id = formContent.id;
|
|
97
|
-
|
|
98
|
-
if (values.attributes && typeof values === 'object') {
|
|
99
|
-
values = {
|
|
100
|
-
...values,
|
|
101
|
-
attributes: JSON.stringify(values.attributes)
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
if (id) {
|
|
106
|
-
RolesAPI.updateRole({ id, formBody: values }).then(() => {
|
|
107
|
-
message.success('Role Updated');
|
|
108
|
-
setLoading(false);
|
|
109
|
-
callback();
|
|
110
|
-
});
|
|
111
|
-
} else {
|
|
112
|
-
additional_queries.forEach(({ field, value }) => {
|
|
113
|
-
values[field] = value;
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
RolesAPI.createRole(values).then(() => {
|
|
117
|
-
message.success('Role Added');
|
|
118
|
-
setLoading(false);
|
|
119
|
-
callback();
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
};
|
|
136
|
+
// include id ONLY for edit
|
|
137
|
+
if (formContent?.id) {
|
|
138
|
+
payload.id = formContent.id;
|
|
139
|
+
}
|
|
123
140
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
{/* Submit Button */}
|
|
185
|
-
<Form.Item>
|
|
186
|
-
<Button loading={loading} htmlType="submit" type="primary">
|
|
187
|
-
Save
|
|
188
|
-
</Button>
|
|
189
|
-
</Form.Item>
|
|
190
|
-
|
|
191
|
-
</Form>
|
|
192
|
-
)}
|
|
193
|
-
|
|
194
|
-
</section>
|
|
195
|
-
);
|
|
141
|
+
// include additional queries for both cases
|
|
142
|
+
additional_queries.forEach(({ field, value }) => {
|
|
143
|
+
payload[field] = value;
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
await RolesAPI.createRole(payload); // single API
|
|
148
|
+
message.success(formContent?.id ? 'Role Updated' : 'Role Added');
|
|
149
|
+
callback();
|
|
150
|
+
} catch (err) {
|
|
151
|
+
// message.error('Something went wrong');
|
|
152
|
+
} finally {
|
|
153
|
+
setLoading(false);
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
return (
|
|
158
|
+
<section className="collection-add">
|
|
159
|
+
{loading ? (
|
|
160
|
+
<Skeleton />
|
|
161
|
+
) : (
|
|
162
|
+
<Form initialValues={{ ...formContent }} form={form} layout="vertical" onFinish={onSubmit}>
|
|
163
|
+
{/* Role Name */}
|
|
164
|
+
<Form.Item name="name" label="Enter Role Name" rules={[{ required: true, message: 'Role name is required' }]}>
|
|
165
|
+
<Input placeholder="Enter name" />
|
|
166
|
+
</Form.Item>
|
|
167
|
+
|
|
168
|
+
{/* Description */}
|
|
169
|
+
<Form.Item
|
|
170
|
+
name="description"
|
|
171
|
+
label="Enter Description"
|
|
172
|
+
rules={[
|
|
173
|
+
{ required: true, message: 'Description is required' },
|
|
174
|
+
{ max: 250, message: 'Description cannot exceed 255 characters' },
|
|
175
|
+
]}
|
|
176
|
+
>
|
|
177
|
+
<Input placeholder="Enter description" />
|
|
178
|
+
</Form.Item>
|
|
179
|
+
|
|
180
|
+
{/* MENU TREE */}
|
|
181
|
+
{showMenus && menuList.length > 0 && (
|
|
182
|
+
<div style={{ marginTop: 30 }}>
|
|
183
|
+
<Title level={5}>Menu List</Title>
|
|
184
|
+
<p style={{ color: '#999' }}>Choose menus and set permissions</p>
|
|
185
|
+
|
|
186
|
+
<MenuTree menus={menuList} selectedMenus={selectedMenus} toggleMenu={toggleMenu} />
|
|
187
|
+
</div>
|
|
188
|
+
)}
|
|
189
|
+
|
|
190
|
+
{/* Submit Button */}
|
|
191
|
+
<Form.Item style={{ marginTop: 20 }}>
|
|
192
|
+
<Button loading={loading} htmlType="submit" type="primary">
|
|
193
|
+
Save
|
|
194
|
+
</Button>
|
|
195
|
+
</Form.Item>
|
|
196
|
+
</Form>
|
|
197
|
+
)}
|
|
198
|
+
</section>
|
|
199
|
+
);
|
|
196
200
|
};
|
|
197
201
|
|
|
198
202
|
export default RoleAdd;
|
|
199
203
|
|
|
200
|
-
|
|
201
204
|
// ------------------------
|
|
202
205
|
// Recursive Nested Menu Component
|
|
203
206
|
// ------------------------
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
207
|
+
// ------------------------
|
|
208
|
+
// Recursive Nested Menu Component
|
|
209
|
+
// ------------------------
|
|
210
|
+
const MenuTree = ({ menus, selectedMenus, toggleMenu, parentId = null }) => {
|
|
211
|
+
// Helper: check if parent should be checked
|
|
212
|
+
const isParentChecked = (menu) => {
|
|
213
|
+
if (!menu.sub_menus || menu.sub_menus.length === 0) {
|
|
214
|
+
return selectedMenus.includes(menu.id);
|
|
215
|
+
}
|
|
216
|
+
const allChildIds = menu.sub_menus.map((c) => c.id);
|
|
217
|
+
return allChildIds.every((id) => selectedMenus.includes(id));
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
// Helper: check if parent is indeterminate
|
|
221
|
+
const isParentIndeterminate = (menu) => {
|
|
222
|
+
if (!menu.sub_menus || menu.sub_menus.length === 0) return false;
|
|
223
|
+
const allChildIds = menu.sub_menus.map((c) => c.id);
|
|
224
|
+
const checkedCount = allChildIds.filter((id) => selectedMenus.includes(id)).length;
|
|
225
|
+
return checkedCount > 0 && checkedCount < allChildIds.length;
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
return (
|
|
229
|
+
<>
|
|
230
|
+
{menus.map((menu) => {
|
|
231
|
+
const children = menu.sub_menus || [];
|
|
232
|
+
const parentChecked = isParentChecked(menu);
|
|
233
|
+
const parentIndeterminate = isParentIndeterminate(menu);
|
|
234
|
+
|
|
235
|
+
const onParentChange = (checked) => {
|
|
236
|
+
toggleMenu(menu.id, checked);
|
|
237
|
+
// toggle children recursively
|
|
238
|
+
children.forEach((c) => toggleMenuRecursive(c, checked));
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
const toggleMenuRecursive = (menu, checked) => {
|
|
242
|
+
toggleMenu(menu.id, checked);
|
|
243
|
+
if (menu.sub_menus && menu.sub_menus.length > 0) {
|
|
244
|
+
menu.sub_menus.forEach((c) => toggleMenuRecursive(c, checked));
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
if (children.length === 0) {
|
|
249
|
+
return (
|
|
250
|
+
<div
|
|
251
|
+
key={menu.id}
|
|
252
|
+
style={{
|
|
253
|
+
border: '1px solid rgba(198, 195, 195, 0.85)',
|
|
254
|
+
// borderRadius: 6,
|
|
255
|
+
padding: '12px 16px',
|
|
256
|
+
marginBottom: 6,
|
|
257
|
+
background: '#fff',
|
|
258
|
+
display: 'flex',
|
|
259
|
+
alignItems: 'center',
|
|
260
|
+
gap: 8,
|
|
261
|
+
}}
|
|
262
|
+
>
|
|
263
|
+
<Checkbox
|
|
264
|
+
checked={selectedMenus.includes(menu.id)}
|
|
265
|
+
onChange={(e) => {
|
|
266
|
+
const checked = e.target.checked;
|
|
267
|
+
|
|
268
|
+
toggleMenu(menu.id, checked);
|
|
269
|
+
|
|
270
|
+
// ✅ FORCE parent selection
|
|
271
|
+
if (checked && parentId) {
|
|
272
|
+
toggleMenu(parentId, true);
|
|
273
|
+
}
|
|
274
|
+
}}
|
|
275
|
+
/>
|
|
276
|
+
<span>{menu.title || menu.caption}</span>
|
|
277
|
+
</div>
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return (
|
|
282
|
+
<Collapse
|
|
283
|
+
key={menu.id}
|
|
284
|
+
style={{ marginBottom: 6 }}
|
|
285
|
+
// defaultActiveKey={[menu.id]}
|
|
286
|
+
>
|
|
287
|
+
<Panel
|
|
288
|
+
key={menu.id}
|
|
289
|
+
header={
|
|
290
|
+
<div
|
|
291
|
+
style={{
|
|
292
|
+
display: 'flex',
|
|
293
|
+
alignItems: 'center',
|
|
294
|
+
gap: 8,
|
|
295
|
+
}}
|
|
296
|
+
onClick={(e) => e.stopPropagation()}
|
|
237
297
|
>
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
298
|
+
<Checkbox checked={parentChecked} indeterminate={parentIndeterminate} onChange={(e) => onParentChange(e.target.checked)} />
|
|
299
|
+
<span>{menu.title || menu.caption}</span>
|
|
300
|
+
</div>
|
|
301
|
+
}
|
|
302
|
+
>
|
|
303
|
+
<div style={{ paddingLeft: 20 }}>
|
|
304
|
+
<MenuTree menus={children} selectedMenus={selectedMenus} toggleMenu={toggleMenu} parentId={menu.id} />
|
|
305
|
+
</div>
|
|
306
|
+
</Panel>
|
|
307
|
+
</Collapse>
|
|
308
|
+
);
|
|
309
|
+
})}
|
|
310
|
+
</>
|
|
311
|
+
);
|
|
248
312
|
};
|