ui-soxo-bootstrap-core 2.4.25-dev.10 → 2.4.25-dev.11
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 +119 -42
- package/core/lib/elements/basic/switch/switch.js +1 -1
- package/core/models/menus/components/menu-add/menu-add.js +22 -31
- package/core/models/menus/components/menu-lists/menu-lists.js +336 -218
- package/core/models/menus/components/menu-lists/menu-lists.scss +4 -9
- package/core/models/menus/menus.js +9 -0
- package/core/models/roles/components/role-add/role-add.js +123 -128
- package/core/models/roles/components/role-list/role-list.js +325 -349
- package/core/models/roles/roles.js +9 -0
- package/core/models/users/components/user-add/user-add.js +35 -1
- package/package.json +1 -1
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
}
|
|
5
5
|
|
|
6
6
|
.ant-collapse {
|
|
7
|
-
|
|
7
|
+
background-color: #fafafa !important;
|
|
8
8
|
border: none !important;
|
|
9
9
|
}
|
|
10
10
|
.ant-collapse > .ant-collapse-item {
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
.nested-collapse {
|
|
28
|
-
margin-left:
|
|
28
|
+
margin-left: 2px; // indent child collapses
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
.nested-collapse .ant-collapse-item {
|
|
@@ -38,13 +38,8 @@
|
|
|
38
38
|
padding: 10px 14px;
|
|
39
39
|
box-shadow: 0 1px 4px rgba(0,0,0,0.04);
|
|
40
40
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
background-color: #bfbfbf !important; /* gray */
|
|
41
|
+
.ant-collapse > .ant-collapse-item > .ant-collapse-heade{
|
|
42
|
+
align-items: center;
|
|
44
43
|
}
|
|
45
44
|
|
|
46
|
-
/* ON state */
|
|
47
|
-
.custom-switch.ant-switch-checked {
|
|
48
|
-
background-color: #446AA9 !important; /* blue */
|
|
49
|
-
}
|
|
50
45
|
|
|
@@ -179,6 +179,15 @@ class MenusAPI extends Base {
|
|
|
179
179
|
}).then((result) => result);
|
|
180
180
|
};
|
|
181
181
|
|
|
182
|
+
// get core-menu list with submenu
|
|
183
|
+
getCoreMenuLists = () => {
|
|
184
|
+
const url = 'core-menus/core-menus?step=1&header_id=null';
|
|
185
|
+
|
|
186
|
+
return this.get({
|
|
187
|
+
url,
|
|
188
|
+
}).then((result) => result);
|
|
189
|
+
};
|
|
190
|
+
|
|
182
191
|
getCoreMenus = () => {
|
|
183
192
|
return [
|
|
184
193
|
// {
|
|
@@ -2,7 +2,7 @@ import React, { useState, useEffect, useContext } from 'react';
|
|
|
2
2
|
import { useLocation, useParams } from 'react-router-dom';
|
|
3
3
|
import { Skeleton, Typography, message, Form, Input, Collapse, Checkbox } from 'antd';
|
|
4
4
|
import { GlobalContext } from './../../../../lib';
|
|
5
|
-
import {Button} from './../../../../lib';
|
|
5
|
+
import { Button } from './../../../../lib';
|
|
6
6
|
import { ModelsAPI, PagesAPI, RolesAPI, MenusAPI } from '../../..';
|
|
7
7
|
|
|
8
8
|
const { Title } = Typography;
|
|
@@ -34,8 +34,7 @@ const RoleAdd = ({ model, callback, edit, formContent = {}, match, additional_qu
|
|
|
34
34
|
const [models, setModels] = useState([]);
|
|
35
35
|
const [menuList, setMenuList] = useState([]);
|
|
36
36
|
const [showMenus, setShowMenus] = useState(false);
|
|
37
|
-
const [originalMenus, setOriginalMenus] = useState([])
|
|
38
|
-
|
|
37
|
+
const [originalMenus, setOriginalMenus] = useState([]); // for deselected menu for editing
|
|
39
38
|
|
|
40
39
|
// Selected menus (array of IDs)
|
|
41
40
|
const [selectedMenus, setSelectedMenus] = useState([]);
|
|
@@ -54,18 +53,16 @@ const RoleAdd = ({ model, callback, edit, formContent = {}, match, additional_qu
|
|
|
54
53
|
setShowMenus(true);
|
|
55
54
|
|
|
56
55
|
// Preselect menus when editing
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
56
|
+
if (formContent && formContent.menu_ids) {
|
|
57
|
+
let menus = formContent.menu_ids;
|
|
58
|
+
|
|
59
|
+
setSelectedMenus(menus);
|
|
60
|
+
setOriginalMenus(menus); // keep original copy for deselect comparison
|
|
61
|
+
}
|
|
63
62
|
|
|
64
63
|
setLoading(false);
|
|
65
64
|
}, [formContent]);
|
|
66
65
|
|
|
67
|
-
|
|
68
|
-
|
|
69
66
|
// Load pages
|
|
70
67
|
const getPages = () => {
|
|
71
68
|
PagesAPI.getPages().then((res) => setPages(res.result || []));
|
|
@@ -78,112 +75,102 @@ const RoleAdd = ({ model, callback, edit, formContent = {}, match, additional_qu
|
|
|
78
75
|
|
|
79
76
|
// Load top-level menus
|
|
80
77
|
const loadMenus = () => {
|
|
81
|
-
MenusAPI.
|
|
82
|
-
queries: [
|
|
83
|
-
{ field: 'step', value: 1 },
|
|
84
|
-
{ field: 'header_id', value: null },
|
|
85
|
-
],
|
|
86
|
-
})
|
|
78
|
+
MenusAPI.getCoreMenuLists()
|
|
87
79
|
.then((res) => setMenuList(res.result || []))
|
|
88
80
|
.catch(console.error);
|
|
89
81
|
};
|
|
90
82
|
|
|
91
83
|
// Toggle menu selection
|
|
92
84
|
const toggleMenu = (id, checked) => {
|
|
93
|
-
setSelectedMenus((prev) =>
|
|
94
|
-
checked ? [...prev, id] : prev.filter((m) => m !== id)
|
|
95
|
-
);
|
|
85
|
+
setSelectedMenus((prev) => (checked ? [...prev, id] : prev.filter((m) => m !== id)));
|
|
96
86
|
};
|
|
97
87
|
|
|
98
88
|
// Submit handler
|
|
99
|
-
// const onSubmit = (values) => {
|
|
100
|
-
// setLoading(true);
|
|
101
|
-
|
|
102
|
-
// const payload = {
|
|
103
|
-
// ...values,
|
|
104
|
-
// menu_ids
|
|
105
|
-
// : selectedMenus,
|
|
106
|
-
// attributes: JSON.stringify(values.attributes || {}),
|
|
107
|
-
// };
|
|
108
|
-
|
|
109
|
-
// if (formContent.id) {
|
|
110
|
-
// RolesAPI.updateRole({ id: formContent.id, formBody: payload })
|
|
111
|
-
// .then(() => {
|
|
112
|
-
// message.success('Role Updated');
|
|
113
|
-
// setLoading(false);
|
|
114
|
-
// callback();
|
|
115
|
-
// })
|
|
116
|
-
// .catch(() => setLoading(false));
|
|
117
|
-
// } else {
|
|
118
|
-
// additional_queries.forEach(({ field, value }) => {
|
|
119
|
-
// payload[field] = value;
|
|
120
|
-
// });
|
|
121
|
-
// RolesAPI.createRole(payload)
|
|
122
|
-
// .then(() => {
|
|
123
|
-
// message.success('Role Added');
|
|
124
|
-
// setLoading(false);
|
|
125
|
-
// callback();
|
|
126
|
-
// })
|
|
127
|
-
// .catch(() => setLoading(false));
|
|
128
|
-
// }
|
|
129
|
-
// };
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
89
|
+
// const onSubmit = (values) => {
|
|
90
|
+
// setLoading(true);
|
|
91
|
+
|
|
92
|
+
// const payload = {
|
|
93
|
+
// ...values,
|
|
94
|
+
// menu_ids
|
|
95
|
+
// : selectedMenus,
|
|
96
|
+
// attributes: JSON.stringify(values.attributes || {}),
|
|
97
|
+
// };
|
|
98
|
+
|
|
99
|
+
// if (formContent.id) {
|
|
100
|
+
// RolesAPI.updateRole({ id: formContent.id, formBody: payload })
|
|
101
|
+
// .then(() => {
|
|
102
|
+
// message.success('Role Updated');
|
|
103
|
+
// setLoading(false);
|
|
104
|
+
// callback();
|
|
105
|
+
// })
|
|
106
|
+
// .catch(() => setLoading(false));
|
|
107
|
+
// } else {
|
|
108
|
+
// additional_queries.forEach(({ field, value }) => {
|
|
109
|
+
// payload[field] = value;
|
|
110
|
+
// });
|
|
111
|
+
// RolesAPI.createRole(payload)
|
|
112
|
+
// .then(() => {
|
|
113
|
+
// message.success('Role Added');
|
|
114
|
+
// setLoading(false);
|
|
115
|
+
// callback();
|
|
116
|
+
// })
|
|
117
|
+
// .catch(() => setLoading(false));
|
|
118
|
+
// }
|
|
119
|
+
// };
|
|
120
|
+
const onSubmit = async (values) => {
|
|
121
|
+
setLoading(true);
|
|
122
|
+
|
|
123
|
+
// Find menus that were originally selected but now deselected
|
|
124
|
+
const deselectedMenus = originalMenus.filter((id) => !selectedMenus.includes(id));
|
|
125
|
+
|
|
126
|
+
const payload = {
|
|
127
|
+
...values,
|
|
128
|
+
menu_ids: selectedMenus,
|
|
129
|
+
deselect_array: deselectedMenus, // add deselected menus when edit time
|
|
130
|
+
attributes: JSON.stringify(values.attributes || {}),
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
// include id ONLY for edit
|
|
134
|
+
if (formContent?.id) {
|
|
135
|
+
payload.id = formContent.id;
|
|
136
|
+
}
|
|
138
137
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
138
|
+
// include additional queries for both cases
|
|
139
|
+
additional_queries.forEach(({ field, value }) => {
|
|
140
|
+
payload[field] = value;
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
await RolesAPI.createRole(payload); // single API
|
|
145
|
+
message.success(formContent?.id ? 'Role Updated' : 'Role Added');
|
|
146
|
+
callback();
|
|
147
|
+
} catch (err) {
|
|
148
|
+
// message.error('Something went wrong');
|
|
149
|
+
} finally {
|
|
150
|
+
setLoading(false);
|
|
151
|
+
}
|
|
144
152
|
};
|
|
145
153
|
|
|
146
|
-
// include id ONLY for edit
|
|
147
|
-
if (formContent?.id) {
|
|
148
|
-
payload.id = formContent.id;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// include additional queries for both cases
|
|
152
|
-
additional_queries.forEach(({ field, value }) => {
|
|
153
|
-
payload[field] = value;
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
try {
|
|
157
|
-
await RolesAPI.createRole(payload); // single API
|
|
158
|
-
message.success(formContent?.id ? 'Role Updated' : 'Role Added');
|
|
159
|
-
callback();
|
|
160
|
-
} catch (err) {
|
|
161
|
-
// message.error('Something went wrong');
|
|
162
|
-
} finally {
|
|
163
|
-
setLoading(false);
|
|
164
|
-
}
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
154
|
return (
|
|
170
155
|
<section className="collection-add">
|
|
171
156
|
{loading ? (
|
|
172
157
|
<Skeleton />
|
|
173
158
|
) : (
|
|
174
|
-
<Form
|
|
175
|
-
initialValues={{ ...formContent }}
|
|
176
|
-
form={form}
|
|
177
|
-
layout="vertical"
|
|
178
|
-
onFinish={onSubmit}
|
|
179
|
-
>
|
|
159
|
+
<Form initialValues={{ ...formContent }} form={form} layout="vertical" onFinish={onSubmit}>
|
|
180
160
|
{/* Role Name */}
|
|
181
|
-
<Form.Item name="name" label="Enter Role Name" required>
|
|
161
|
+
<Form.Item name="name" label="Enter Role Name" rules={[{ required: true, message: 'Role name is required' }]}>
|
|
182
162
|
<Input placeholder="Enter name" />
|
|
183
163
|
</Form.Item>
|
|
184
164
|
|
|
185
165
|
{/* Description */}
|
|
186
|
-
<Form.Item
|
|
166
|
+
<Form.Item
|
|
167
|
+
name="description"
|
|
168
|
+
label="Enter Description"
|
|
169
|
+
rules={[
|
|
170
|
+
{ required: true, message: 'Description is required' },
|
|
171
|
+
{ max: 250, message: 'Description cannot exceed 255 characters' },
|
|
172
|
+
]}
|
|
173
|
+
>
|
|
187
174
|
<Input placeholder="Enter description" />
|
|
188
175
|
</Form.Item>
|
|
189
176
|
|
|
@@ -193,33 +180,46 @@ const RoleAdd = ({ model, callback, edit, formContent = {}, match, additional_qu
|
|
|
193
180
|
<Title level={5} style={{ marginBottom: 16 }}>
|
|
194
181
|
Menu List
|
|
195
182
|
</Title>
|
|
196
|
-
<p style={{ marginTop: -10, marginBottom: 20, color: '#999' }}>
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
<div
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
183
|
+
<p style={{ marginTop: -10, marginBottom: 20, color: '#999' }}>Choose menus and set permissions</p>
|
|
184
|
+
|
|
185
|
+
<Collapse expandIconPosition="left" style={{ marginBottom: '4px' }}>
|
|
186
|
+
{menuList.map((item) => {
|
|
187
|
+
const hasChildren = item.sub_menus && item.sub_menus.length > 0;
|
|
188
|
+
|
|
189
|
+
if (!hasChildren) {
|
|
190
|
+
// 👉 NO collapse icon
|
|
191
|
+
return (
|
|
192
|
+
<div
|
|
193
|
+
key={item.id}
|
|
194
|
+
style={{
|
|
195
|
+
padding: '12px 16px',
|
|
196
|
+
display: 'flex',
|
|
197
|
+
alignItems: 'center',
|
|
198
|
+
gap: 8,
|
|
199
|
+
borderBottom: '1px solid #f0f0f0',
|
|
200
|
+
}}
|
|
201
|
+
>
|
|
202
|
+
<Checkbox checked={selectedMenus.includes(item.id)} onChange={(e) => toggleMenu(item.id, e.target.checked)} />
|
|
210
203
|
<span>{item.title || item.caption}</span>
|
|
211
204
|
</div>
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// 👉 WITH collapse icon
|
|
209
|
+
return (
|
|
210
|
+
<Panel
|
|
211
|
+
key={item.id}
|
|
212
|
+
header={
|
|
213
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 8, borderBottom: '1px solid #f0f0f0' }}>
|
|
214
|
+
<Checkbox checked={selectedMenus.includes(item.id)} onChange={(e) => toggleMenu(item.id, e.target.checked)} />
|
|
215
|
+
<span>{item.title || item.caption}</span>
|
|
216
|
+
</div>
|
|
217
|
+
}
|
|
218
|
+
>
|
|
219
|
+
<NestedMenu parentId={item.id} step={step + 1} selectedMenus={selectedMenus} toggleMenu={toggleMenu} />
|
|
220
|
+
</Panel>
|
|
221
|
+
);
|
|
222
|
+
})}
|
|
223
223
|
</Collapse>
|
|
224
224
|
</div>
|
|
225
225
|
)}
|
|
@@ -257,13 +257,13 @@ const NestedMenu = ({ parentId, step, selectedMenus, toggleMenu }) => {
|
|
|
257
257
|
setLoading(false);
|
|
258
258
|
})
|
|
259
259
|
.catch(() => setLoading(false));
|
|
260
|
-
}, [parentId, step]);
|
|
260
|
+
}, [parentId, step]);
|
|
261
261
|
|
|
262
262
|
if (loading) return <Skeleton active />;
|
|
263
263
|
if (!items.length) return null;
|
|
264
264
|
|
|
265
265
|
return (
|
|
266
|
-
<Collapse ghost
|
|
266
|
+
<Collapse ghost>
|
|
267
267
|
{items.map((menu) => (
|
|
268
268
|
<Panel
|
|
269
269
|
key={menu.id}
|
|
@@ -277,12 +277,7 @@ const NestedMenu = ({ parentId, step, selectedMenus, toggleMenu }) => {
|
|
|
277
277
|
</div>
|
|
278
278
|
}
|
|
279
279
|
>
|
|
280
|
-
<NestedMenu
|
|
281
|
-
parentId={menu.id}
|
|
282
|
-
step={step + 1}
|
|
283
|
-
selectedMenus={selectedMenus}
|
|
284
|
-
toggleMenu={toggleMenu}
|
|
285
|
-
/>
|
|
280
|
+
<NestedMenu parentId={menu.id} step={step + 1} selectedMenus={selectedMenus} toggleMenu={toggleMenu} />
|
|
286
281
|
</Panel>
|
|
287
282
|
))}
|
|
288
283
|
</Collapse>
|