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.
Files changed (37) hide show
  1. package/.github/workflows/npm-publish.yml +37 -15
  2. package/core/components/extra-info/extra-info-details.js +109 -126
  3. package/core/components/landing-api/landing-api.js +22 -30
  4. package/core/lib/Store.js +20 -18
  5. package/core/lib/components/index.js +4 -1
  6. package/core/lib/components/sidemenu/sidemenu.js +153 -256
  7. package/core/lib/components/sidemenu/sidemenu.scss +39 -26
  8. package/core/lib/elements/basic/dragabble-wrapper/draggable-wrapper.js +119 -42
  9. package/core/lib/elements/basic/rangepicker/rangepicker.js +118 -29
  10. package/core/lib/elements/basic/switch/switch.js +35 -25
  11. package/core/lib/hooks/index.js +2 -12
  12. package/core/lib/hooks/use-otp-timer.js +99 -0
  13. package/core/lib/pages/login/login.js +255 -139
  14. package/core/lib/pages/login/login.scss +140 -32
  15. package/core/models/dashboard/dashboard.js +14 -0
  16. package/core/models/doctor/components/doctor-add/doctor-add.js +403 -0
  17. package/core/models/doctor/components/doctor-add/doctor-add.scss +32 -0
  18. package/core/models/menus/components/menu-add/menu-add.js +220 -267
  19. package/core/models/menus/components/menu-lists/menu-lists.js +366 -211
  20. package/core/models/menus/components/menu-lists/menu-lists.scss +6 -2
  21. package/core/models/menus/menus.js +256 -267
  22. package/core/models/roles/components/role-add/role-add.js +265 -228
  23. package/core/models/roles/components/role-list/role-list.js +326 -348
  24. package/core/models/roles/roles.js +191 -174
  25. package/core/models/staff/components/staff-add/staff-add.js +352 -0
  26. package/core/models/staff/components/staff-add/staff-add.scss +0 -0
  27. package/core/models/users/components/user-add/user-add.js +723 -367
  28. package/core/models/users/components/user-add/user-edit.js +90 -0
  29. package/core/models/users/users.js +318 -165
  30. package/core/modules/index.js +5 -8
  31. package/core/modules/reporting/components/index.js +5 -0
  32. package/core/modules/reporting/components/reporting-dashboard/reporting-dashboard.js +65 -2
  33. package/core/modules/steps/action-buttons.js +79 -0
  34. package/core/modules/steps/steps.js +553 -0
  35. package/core/modules/steps/steps.scss +158 -0
  36. package/core/modules/steps/timeline.js +49 -0
  37. 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, Select, Collapse, Checkbox } from 'antd';
4
- import { Table, Card, Button, JSONInput, Switch, GlobalContext } from './../../../../lib';
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, history, formContent, match, additional_queries, ...props }) => {
13
-
14
- let mode = 'Add';
15
- if (formContent.id) mode = 'Edit';
16
- else if (formContent.copy) mode = 'copy';
17
-
18
- // Parse attributes safely
19
- if (formContent.attributes) {
20
- if (typeof formContent.attributes === 'string' && formContent.attributes !== '') {
21
- formContent.attributes = JSON.parse(formContent.attributes);
22
- } else {
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
- const [loading, setLoading] = useState(true);
30
- const [form] = Form.useForm();
31
- const [pages, setPages] = useState([]);
32
- const [models, setModels] = useState([]);
33
- const [body, setBody] = useState(formContent);
34
-
35
- const [showMenus, setShowMenus] = useState(false);
36
- const [menuList, setMenuList] = useState([]);
37
-
38
- const { params } = match;
39
- const { dispatch, user = {} } = useContext(GlobalContext);
40
-
41
- const location = useLocation();
42
- const query = new URLSearchParams(location.search);
43
-
44
- let step = parseInt(query.get('step')) || 1;
45
-
46
- match = useParams();
47
- const id = match.id;
48
-
49
- useEffect(() => {
50
- getPages();
51
- getModels();
52
- loadMenus();
53
-
54
- setLoading(false);
55
- }, []);
56
- useEffect(() => {
57
-
58
- loadMenus();
59
- setShowMenus(true)
60
-
61
- // setLoading(false);
62
- }, [id]);
63
-
64
- // Load pages
65
- const getPages = () => {
66
- PagesAPI.getPages().then((result) => {
67
- setPages(result.result);
68
- });
69
- };
70
-
71
- // Load models
72
- const getModels = () => {
73
- ModelsAPI.get().then((result) => {
74
- console.log(result);
75
- setModels(result.result);
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
- // Load menus dynamically
80
- const loadMenus = () => {
81
- MenusAPI.get({
82
- queries: [
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
- return (
125
- <section className="collection-add">
126
-
127
- {loading ? (
128
- <Skeleton />
129
- ) : (
130
- <Form initialValues={{ ...body }} form={form} layout="vertical" onFinish={onSubmit}>
131
-
132
- {/* Role Name */}
133
- <Form.Item name="name" label="Enter Role Name" required>
134
- <Input placeholder="Enter name" />
135
- </Form.Item>
136
- {/* Name */}
137
- {/* <Form.Item name={"identifier"} label="Identifier" required>
138
- <Input placeholder="Enter identifier" />
139
- </Form.Item> */}
140
- {/* Name Ends */}
141
-
142
- {/* Path */}
143
- {/* <Form.Item name="weight" label="Weight" required>
144
- <Input placeholder="Enter weight" />
145
- </Form.Item> */}
146
- {/* Path Ends */}
147
-
148
- {/* Description */}
149
- <Form.Item name="description" label="Enter Description" required>
150
- <Input placeholder="Enter description" />
151
- </Form.Item>
152
-
153
- {/* MENUS COLLAPSE */}
154
- {showMenus && menuList.length > 0 && (
155
- <div style={{ marginTop: 30 }}>
156
- <Title level={5} style={{ marginBottom: 16 }}>Menu List</Title>
157
- <p style={{ marginTop: -10, marginBottom: 20, color: "#999" }}>
158
- Choose menus and set permissions
159
- </p>
160
-
161
- <Collapse expandIconPosition="left">
162
- {menuList.map(item => (
163
- <Panel
164
- key={item.id}
165
- header={
166
- <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
167
- <Checkbox />
168
- <span>{item.title || item.caption}</span>
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
- {/* Load submenus recursively */}
173
- <NestedMenu parentId={item.id} step={step + 1} />
174
- </Panel>
175
- ))}
176
- </Collapse>
177
- </div>
178
- )}
179
- {/* <Button type="primary" onClick={loadMenus()}>
180
- Get Menus
181
- </Button> */}
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
- );
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, api, additional_queries }) => {
205
- const [items, setItems] = useState([]);
206
- const [loading, setLoading] = useState(true);
207
-
208
- useEffect(() => {
209
- MenusAPI.get({
210
- queries: [
211
- // ...additional_queries,
212
- { field: 'header_id', value: parentId },
213
- { field: 'step', value: step }
214
- ],
215
- })
216
- .then(res => {
217
- setItems(res.result || []);
218
- setLoading(false);
219
- })
220
- .catch(() => setLoading(false));
221
- }, [parentId]);
222
-
223
- if (loading) return <Skeleton active />;
224
- if (!items.length) return null;
225
-
226
- return (
227
- <Collapse ghost style={{ marginLeft: 20 }}>
228
- {items.map(menu => (
229
- <Panel
230
- key={menu.id}
231
- header={
232
- <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
233
- <Checkbox />
234
- <span>{menu.title || menu.caption}</span>
235
- </div>
236
- }
237
- >
238
- <NestedMenu
239
- parentId={menu.id}
240
- step={step + 1}
241
- api={api}
242
- additional_queries={additional_queries}
243
- />
244
- </Panel>
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
  };