ui-soxo-bootstrap-core 2.4.24 → 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/.github/workflows/npm-publish.yml +37 -15
- package/core/components/extra-info/extra-info-details.js +109 -126
- package/core/components/landing-api/landing-api.js +22 -30
- package/core/lib/Store.js +20 -18
- package/core/lib/components/index.js +4 -1
- package/core/lib/components/sidemenu/sidemenu.js +153 -256
- package/core/lib/components/sidemenu/sidemenu.scss +39 -26
- package/core/lib/elements/basic/dragabble-wrapper/draggable-wrapper.js +119 -42
- package/core/lib/elements/basic/rangepicker/rangepicker.js +118 -29
- package/core/lib/elements/basic/switch/switch.js +35 -25
- package/core/lib/hooks/index.js +2 -12
- package/core/lib/hooks/use-otp-timer.js +99 -0
- package/core/lib/pages/login/login.js +255 -139
- package/core/lib/pages/login/login.scss +140 -32
- package/core/models/dashboard/dashboard.js +14 -0
- package/core/models/doctor/components/doctor-add/doctor-add.js +403 -0
- package/core/models/doctor/components/doctor-add/doctor-add.scss +32 -0
- package/core/models/menus/components/menu-add/menu-add.js +220 -267
- package/core/models/menus/components/menu-lists/menu-lists.js +366 -211
- package/core/models/menus/components/menu-lists/menu-lists.scss +6 -2
- package/core/models/menus/menus.js +256 -267
- package/core/models/roles/components/role-add/role-add.js +265 -228
- package/core/models/roles/components/role-list/role-list.js +326 -348
- package/core/models/roles/roles.js +191 -174
- 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 +723 -367
- package/core/models/users/components/user-add/user-edit.js +90 -0
- package/core/models/users/users.js +318 -165
- package/core/modules/index.js +5 -8
- package/core/modules/reporting/components/index.js +5 -0
- package/core/modules/reporting/components/reporting-dashboard/reporting-dashboard.js +65 -2
- package/core/modules/steps/action-buttons.js +79 -0
- package/core/modules/steps/steps.js +553 -0
- package/core/modules/steps/steps.scss +158 -0
- package/core/modules/steps/timeline.js +49 -0
- package/package.json +2 -2
|
@@ -1,248 +1,285 @@
|
|
|
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
7
|
|
|
8
8
|
const { Title } = Typography;
|
|
9
|
-
const { Option } = Select;
|
|
10
9
|
const { Panel } = Collapse;
|
|
11
10
|
|
|
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 {
|
|
11
|
+
const RoleAdd = ({ model, callback, edit, formContent = {}, match, additional_queries = [] }) => {
|
|
12
|
+
let mode = 'Add';
|
|
13
|
+
if (formContent.id) mode = 'Edit';
|
|
14
|
+
else if (formContent.copy) mode = 'copy';
|
|
15
|
+
|
|
16
|
+
// Parse attributes safely
|
|
17
|
+
if (formContent.attributes) {
|
|
18
|
+
if (typeof formContent.attributes === 'string' && formContent.attributes !== '') {
|
|
19
|
+
try {
|
|
20
|
+
formContent.attributes = JSON.parse(formContent.attributes);
|
|
21
|
+
} catch {
|
|
26
22
|
formContent.attributes = {};
|
|
23
|
+
}
|
|
24
|
+
} else {
|
|
25
|
+
formContent.attributes = {};
|
|
26
|
+
}
|
|
27
|
+
} else {
|
|
28
|
+
formContent.attributes = {};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const [loading, setLoading] = useState(true);
|
|
32
|
+
const [form] = Form.useForm();
|
|
33
|
+
const [pages, setPages] = useState([]);
|
|
34
|
+
const [models, setModels] = useState([]);
|
|
35
|
+
const [menuList, setMenuList] = useState([]);
|
|
36
|
+
const [showMenus, setShowMenus] = useState(false);
|
|
37
|
+
const [originalMenus, setOriginalMenus] = useState([]); // for deselected menu for editing
|
|
38
|
+
|
|
39
|
+
// Selected menus (array of IDs)
|
|
40
|
+
const [selectedMenus, setSelectedMenus] = useState([]);
|
|
41
|
+
|
|
42
|
+
const location = useLocation();
|
|
43
|
+
const query = new URLSearchParams(location.search);
|
|
44
|
+
let step = parseInt(query.get('step')) || 1;
|
|
45
|
+
|
|
46
|
+
const { user = {} } = useContext(GlobalContext);
|
|
47
|
+
|
|
48
|
+
// On component mount
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
getPages();
|
|
51
|
+
getModels();
|
|
52
|
+
loadMenus();
|
|
53
|
+
setShowMenus(true);
|
|
54
|
+
|
|
55
|
+
// Preselect menus when editing
|
|
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
|
|
27
61
|
}
|
|
28
62
|
|
|
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
|
-
|
|
63
|
+
setLoading(false);
|
|
64
|
+
}, [formContent]);
|
|
65
|
+
|
|
66
|
+
// Load pages
|
|
67
|
+
const getPages = () => {
|
|
68
|
+
PagesAPI.getPages().then((res) => setPages(res.result || []));
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// Load models
|
|
72
|
+
const getModels = () => {
|
|
73
|
+
ModelsAPI.get().then((res) => setModels(res.result || []));
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// Load top-level menus
|
|
77
|
+
const loadMenus = () => {
|
|
78
|
+
MenusAPI.getCoreMenuLists()
|
|
79
|
+
.then((res) => setMenuList(res.result || []))
|
|
80
|
+
.catch(console.error);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// Toggle menu selection
|
|
84
|
+
const toggleMenu = (id, checked) => {
|
|
85
|
+
setSelectedMenus((prev) => (checked ? [...prev, id] : prev.filter((m) => m !== id)));
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// Submit handler
|
|
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 || {}),
|
|
77
131
|
};
|
|
78
132
|
|
|
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
|
-
};
|
|
133
|
+
// include id ONLY for edit
|
|
134
|
+
if (formContent?.id) {
|
|
135
|
+
payload.id = formContent.id;
|
|
136
|
+
}
|
|
123
137
|
|
|
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
|
-
|
|
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
|
+
}
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
return (
|
|
155
|
+
<section className="collection-add">
|
|
156
|
+
{loading ? (
|
|
157
|
+
<Skeleton />
|
|
158
|
+
) : (
|
|
159
|
+
<Form initialValues={{ ...formContent }} form={form} layout="vertical" onFinish={onSubmit}>
|
|
160
|
+
{/* Role Name */}
|
|
161
|
+
<Form.Item name="name" label="Enter Role Name" rules={[{ required: true, message: 'Role name is required' }]}>
|
|
162
|
+
<Input placeholder="Enter name" />
|
|
163
|
+
</Form.Item>
|
|
164
|
+
|
|
165
|
+
{/* Description */}
|
|
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
|
+
>
|
|
174
|
+
<Input placeholder="Enter description" />
|
|
175
|
+
</Form.Item>
|
|
176
|
+
|
|
177
|
+
{/* MENUS COLLAPSE */}
|
|
178
|
+
{showMenus && menuList.length > 0 && (
|
|
179
|
+
<div style={{ marginTop: 30 }}>
|
|
180
|
+
<Title level={5} style={{ marginBottom: 16 }}>
|
|
181
|
+
Menu List
|
|
182
|
+
</Title>
|
|
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)} />
|
|
203
|
+
<span>{item.title || item.caption}</span>
|
|
204
|
+
</div>
|
|
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>
|
|
169
216
|
</div>
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
)}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
</Form>
|
|
192
|
-
)}
|
|
193
|
-
|
|
194
|
-
</section>
|
|
195
|
-
);
|
|
217
|
+
}
|
|
218
|
+
>
|
|
219
|
+
<NestedMenu parentId={item.id} step={step + 1} selectedMenus={selectedMenus} toggleMenu={toggleMenu} />
|
|
220
|
+
</Panel>
|
|
221
|
+
);
|
|
222
|
+
})}
|
|
223
|
+
</Collapse>
|
|
224
|
+
</div>
|
|
225
|
+
)}
|
|
226
|
+
|
|
227
|
+
{/* Submit Button */}
|
|
228
|
+
<Form.Item>
|
|
229
|
+
<Button loading={loading} htmlType="submit" type="primary">
|
|
230
|
+
Save
|
|
231
|
+
</Button>
|
|
232
|
+
</Form.Item>
|
|
233
|
+
</Form>
|
|
234
|
+
)}
|
|
235
|
+
</section>
|
|
236
|
+
);
|
|
196
237
|
};
|
|
197
238
|
|
|
198
239
|
export default RoleAdd;
|
|
199
240
|
|
|
200
|
-
|
|
201
241
|
// ------------------------
|
|
202
242
|
// Recursive Nested Menu Component
|
|
203
243
|
// ------------------------
|
|
204
|
-
const NestedMenu = ({ parentId, step,
|
|
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
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
))}
|
|
246
|
-
</Collapse>
|
|
247
|
-
);
|
|
244
|
+
const NestedMenu = ({ parentId, step, selectedMenus, toggleMenu }) => {
|
|
245
|
+
const [items, setItems] = useState([]);
|
|
246
|
+
const [loading, setLoading] = useState(true);
|
|
247
|
+
|
|
248
|
+
useEffect(() => {
|
|
249
|
+
MenusAPI.get({
|
|
250
|
+
queries: [
|
|
251
|
+
{ field: 'header_id', value: parentId },
|
|
252
|
+
{ field: 'step', value: step },
|
|
253
|
+
],
|
|
254
|
+
})
|
|
255
|
+
.then((res) => {
|
|
256
|
+
setItems(res.result || []);
|
|
257
|
+
setLoading(false);
|
|
258
|
+
})
|
|
259
|
+
.catch(() => setLoading(false));
|
|
260
|
+
}, [parentId, step]);
|
|
261
|
+
|
|
262
|
+
if (loading) return <Skeleton active />;
|
|
263
|
+
if (!items.length) return null;
|
|
264
|
+
|
|
265
|
+
return (
|
|
266
|
+
<Collapse ghost>
|
|
267
|
+
{items.map((menu) => (
|
|
268
|
+
<Panel
|
|
269
|
+
key={menu.id}
|
|
270
|
+
header={
|
|
271
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
|
272
|
+
<Checkbox
|
|
273
|
+
checked={selectedMenus.includes(Number(menu.id))} // ensure number
|
|
274
|
+
onChange={(e) => toggleMenu(Number(menu.id), e.target.checked)}
|
|
275
|
+
/>
|
|
276
|
+
<span>{menu.title || menu.caption}</span>
|
|
277
|
+
</div>
|
|
278
|
+
}
|
|
279
|
+
>
|
|
280
|
+
<NestedMenu parentId={menu.id} step={step + 1} selectedMenus={selectedMenus} toggleMenu={toggleMenu} />
|
|
281
|
+
</Panel>
|
|
282
|
+
))}
|
|
283
|
+
</Collapse>
|
|
284
|
+
);
|
|
248
285
|
};
|