ui-soxo-bootstrap-core 2.6.37 → 2.6.40-dev.0

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.
@@ -1,26 +1,27 @@
1
- import React, { useRef } from 'react';
1
+ import React, { useRef, useEffect } from 'react';
2
2
  import { useDrag, useDrop } from 'react-dnd';
3
3
 
4
- export default function DraggableWrapper({ id, index, movePanel, item, dragEnabled, level, parentId, onCrossLevelMove, canAcceptChildren }) {
5
- const autoScrollWindow = (monitor) => {
6
- const offset = monitor.getClientOffset();
7
- if (!offset) return;
8
-
9
- const EDGE = 80;
10
- const SPEED = 20;
11
-
12
- const viewportHeight = window.innerHeight;
13
-
14
- // 🔼 scroll UP
15
- if (offset.y < EDGE) {
16
- window.scrollBy(0, -SPEED);
17
- }
4
+ // Walk up the DOM to find the nearest vertically-scrollable ancestor.
5
+ // Returns null when the page (window) is the scroller.
6
+ function getScrollableAncestor(node) {
7
+ let el = node?.parentElement;
8
+ while (el) {
9
+ const { overflowY } = window.getComputedStyle(el);
10
+ const scrollable = overflowY === 'auto' || overflowY === 'scroll' || overflowY === 'overlay';
11
+ if (scrollable && el.scrollHeight > el.clientHeight) return el;
12
+ el = el.parentElement;
13
+ }
14
+ return null;
15
+ }
18
16
 
19
- // 🔽 scroll DOWN
20
- if (offset.y > viewportHeight - EDGE) {
21
- window.scrollBy(0, SPEED);
22
- }
23
- };
17
+ export default function DraggableWrapper({ id, index, movePanel, item, dragEnabled, level, parentId, onCrossLevelMove, canAcceptChildren }) {
18
+ // Root DOM node, used to locate the scrollable ancestor at drag time
19
+ const rootRef = useRef(null);
20
+ // Latest pointer Y, fed by a document-level `dragover` listener (see effect below)
21
+ const pointerY = useRef(0);
22
+ const rafRef = useRef(null);
23
+ // The scrollable container to auto-scroll (resolved when a drag starts)
24
+ const scrollContainerRef = useRef(null);
24
25
 
25
26
  const [{ isDragging }, drag] = useDrag({
26
27
  type: 'PANEL',
@@ -31,12 +32,78 @@ export default function DraggableWrapper({ id, index, movePanel, item, dragEnabl
31
32
  }),
32
33
  });
33
34
 
35
+ /**
36
+ * Continuous edge auto-scroll while THIS item is being dragged.
37
+ *
38
+ * Driven by a document-level `dragover` listener + requestAnimationFrame loop
39
+ * rather than the drop target's `hover` handler. `hover` only fires while the
40
+ * pointer is over a droppable panel and only on movement, so scrolling UP
41
+ * failed: the top of the viewport is the (non-droppable) header, and holding
42
+ * the pointer still at an edge produced no events. The rAF loop keeps
43
+ * scrolling from the last known pointer position regardless of what's beneath.
44
+ *
45
+ * The list may scroll inside an overflow container rather than the window, so
46
+ * we resolve the nearest scrollable ancestor and scroll that (falling back to
47
+ * the window), computing the edges from the container's own bounds.
48
+ */
49
+ useEffect(() => {
50
+ if (!isDragging) return;
51
+
52
+ const EDGE = 80;
53
+ const SPEED = 20;
54
+
55
+ scrollContainerRef.current = getScrollableAncestor(rootRef.current);
56
+
57
+ const onDragOver = (e) => {
58
+ pointerY.current = e.clientY;
59
+ };
60
+
61
+ const tick = () => {
62
+ const y = pointerY.current;
63
+
64
+ if (y > 0) {
65
+ const container = scrollContainerRef.current;
66
+
67
+ if (container) {
68
+ const rect = container.getBoundingClientRect();
69
+ // near container top
70
+ if (y < rect.top + EDGE) {
71
+ container.scrollTop -= SPEED;
72
+ }
73
+ // near container bottom
74
+ else if (y > rect.bottom - EDGE) {
75
+ container.scrollTop += SPEED;
76
+ }
77
+ } else {
78
+ const viewportHeight = window.innerHeight;
79
+ // near viewport top
80
+ if (y < EDGE) {
81
+ window.scrollBy(0, -SPEED);
82
+ }
83
+ // near viewport bottom
84
+ else if (y > viewportHeight - EDGE) {
85
+ window.scrollBy(0, SPEED);
86
+ }
87
+ }
88
+ }
89
+
90
+ rafRef.current = requestAnimationFrame(tick);
91
+ };
92
+
93
+ window.addEventListener('dragover', onDragOver);
94
+ rafRef.current = requestAnimationFrame(tick);
95
+
96
+ return () => {
97
+ window.removeEventListener('dragover', onDragOver);
98
+ if (rafRef.current) cancelAnimationFrame(rafRef.current);
99
+ pointerY.current = 0;
100
+ scrollContainerRef.current = null;
101
+ };
102
+ }, [isDragging]);
103
+
34
104
  const [{ isOver, canDrop }, drop] = useDrop({
35
105
  accept: 'PANEL',
36
106
  hover: (dragItem, monitor) => {
37
- // THIS FIXES BOTTOM → TOP
38
- autoScrollWindow(monitor);
39
-
40
107
  if (dragItem.index === index) return;
41
108
 
42
109
  if (dragItem.level === level && dragItem.parentId === parentId) {
@@ -85,7 +152,7 @@ export default function DraggableWrapper({ id, index, movePanel, item, dragEnabl
85
152
  const childZoneBackgroundColor = isOverChild && canDropChild ? '#d4f4dd' : 'transparent';
86
153
 
87
154
  return (
88
- <div style={{ width: '100%', display: 'flex' }}>
155
+ <div ref={rootRef} style={{ width: '100%', display: 'flex' }}>
89
156
  {/* HEADER DROP — reorder only */}
90
157
  <div ref={drop}>
91
158
  <div
@@ -58,12 +58,7 @@ const TableComponent = ({ columns, dataSource, loading, fixed, scroll, summary,
58
58
 
59
59
  return (
60
60
  <Table
61
- title={() =>
62
- // <div style={{ fontWeight: 'bold', fontSize: 16, padding: '8px 16px' }}>
63
- title ? title : ''
64
- // </div>
65
- }
66
- // columns={updatedColumns}
61
+ title={title ? () => title : undefined}
67
62
  scroll={scroll}
68
63
  dataSource={dataSource}
69
64
  loading={loading}
@@ -8,7 +8,7 @@
8
8
  */
9
9
 
10
10
  import React, { useEffect, useRef } from 'react';
11
- import { Html5Qrcode } from 'html5-qrcode';
11
+ import { Html5Qrcode } from 'html5-qrcode/cjs/index.js';
12
12
 
13
13
  /**
14
14
  * QrScanner Component
@@ -0,0 +1,14 @@
1
+ // ------------------------
2
+ // Menu caption + path label
3
+ // ------------------------
4
+ const MenuLabel = ({ menu }) => {
5
+ const path = menu.path || menu.route;
6
+ return (
7
+ <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', gap: 8, lineHeight: 1.3 }}>
8
+ <span>{menu.caption}</span>
9
+ {path ? <span style={{ color: '#999', fontSize: 12 }}>{path}</span> : null}
10
+ </div>
11
+ );
12
+ };
13
+
14
+ export default MenuLabel;
@@ -0,0 +1,127 @@
1
+ import { Collapse, Checkbox, Tag } from 'antd';
2
+
3
+ import MenuLabel from './menu-label';
4
+
5
+ const { Panel } = Collapse;
6
+
7
+ // ------------------------
8
+ // Recursive Nested Menu Component
9
+ // ------------------------
10
+ const MenuTree = ({ menus, selectedMenus, toggleMenu, parentId = null, searchActive = false }) => {
11
+ // Helper: check if parent should be checked
12
+ const isParentChecked = (menu) => {
13
+ if (!menu.sub_menus || menu.sub_menus.length === 0) {
14
+ return selectedMenus.includes(menu.id);
15
+ }
16
+ const allChildIds = menu.sub_menus.map((c) => c.id);
17
+ return allChildIds.every((id) => selectedMenus.includes(id));
18
+ };
19
+
20
+ // Helper: check if parent is indeterminate
21
+ const isParentIndeterminate = (menu) => {
22
+ if (!menu.sub_menus || menu.sub_menus.length === 0) return false;
23
+ const allChildIds = menu.sub_menus.map((c) => c.id);
24
+ const checkedCount = allChildIds.filter((id) => selectedMenus.includes(id)).length;
25
+ return checkedCount > 0 && checkedCount < allChildIds.length;
26
+ };
27
+
28
+ return (
29
+ <>
30
+ {menus.map((menu) => {
31
+ const children = menu.sub_menus || [];
32
+ const parentChecked = isParentChecked(menu);
33
+ const parentIndeterminate = isParentIndeterminate(menu);
34
+
35
+ const onParentChange = (checked) => {
36
+ toggleMenu(menu.id, checked);
37
+ // toggle children recursively
38
+ children.forEach((c) => toggleMenuRecursive(c, checked));
39
+ };
40
+
41
+ const toggleMenuRecursive = (menu, checked) => {
42
+ toggleMenu(menu.id, checked);
43
+ if (menu.sub_menus && menu.sub_menus.length > 0) {
44
+ menu.sub_menus.forEach((c) => toggleMenuRecursive(c, checked));
45
+ }
46
+ };
47
+
48
+ if (children.length === 0) {
49
+ return (
50
+ <div
51
+ style={{
52
+ justifyContent: 'space-between',
53
+ display: 'flex',
54
+ alignItems: 'center',
55
+ border: '1px solid rgba(198, 195, 195, 0.85)',
56
+ // borderRadius: 6,
57
+ padding: '12px 16px',
58
+ marginBottom: 6,
59
+ background: '#fff',
60
+ }}
61
+ >
62
+ <div
63
+ key={menu.id}
64
+ style={{
65
+ display: 'flex',
66
+ alignItems: 'center',
67
+ gap: 8,
68
+ }}
69
+ >
70
+ <Checkbox
71
+ checked={selectedMenus.includes(menu.id)}
72
+ onChange={(e) => {
73
+ const checked = e.target.checked;
74
+
75
+ toggleMenu(menu.id, checked);
76
+
77
+ // FORCE parent selection
78
+ if (checked && parentId) {
79
+ toggleMenu(parentId, true);
80
+ }
81
+ }}
82
+ />
83
+ <MenuLabel menu={menu} />
84
+ </div>
85
+ <Tag color={menu.is_visible === true ? 'green' : 'blue'}>{menu.is_visible === true ? 'VISIBLE' : 'HIDDEN'}</Tag>{' '}
86
+ </div>
87
+ );
88
+ }
89
+
90
+ return (
91
+ <Collapse
92
+ // remount when search toggles so panels open while searching and collapse when cleared
93
+ key={`${menu.id}-${searchActive}`}
94
+ style={{ marginBottom: 6 }}
95
+ defaultActiveKey={searchActive ? [menu.id] : undefined}
96
+ >
97
+ <Panel
98
+ key={menu.id}
99
+ header={
100
+ <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
101
+ <div
102
+ style={{
103
+ display: 'flex',
104
+ alignItems: 'center',
105
+ gap: 8,
106
+ }}
107
+ onClick={(e) => e.stopPropagation()}
108
+ >
109
+ <Checkbox checked={parentChecked} indeterminate={parentIndeterminate} onChange={(e) => onParentChange(e.target.checked)} />
110
+ <MenuLabel menu={menu} />
111
+ </div>
112
+ <Tag color={menu.is_visible === true ? 'green' : 'blue'}>{menu.is_visible === true ? 'VISIBLE' : 'HIDDEN'}</Tag>{' '}
113
+ </div>
114
+ }
115
+ >
116
+ <div style={{ paddingLeft: 20 }}>
117
+ <MenuTree menus={children} selectedMenus={selectedMenus} toggleMenu={toggleMenu} parentId={menu.id} searchActive={searchActive} />
118
+ </div>
119
+ </Panel>
120
+ </Collapse>
121
+ );
122
+ })}
123
+ </>
124
+ );
125
+ };
126
+
127
+ export default MenuTree;
@@ -1,15 +1,14 @@
1
1
  import React, { useState, useEffect, useContext } from 'react';
2
2
  import { useLocation, useParams } from 'react-router-dom';
3
- import { Skeleton, Typography, message, Form, Input, Collapse, Checkbox, Tag } from 'antd';
3
+ import { Skeleton, Typography, message, Form, Input } from 'antd';
4
4
  import { GlobalContext } from './../../../../lib';
5
- import { Button } from './../../../../lib';
6
5
  import { ModelsAPI, PagesAPI, RolesAPI, MenusAPI } from '../../..';
6
+ import MenuTree from './menu-tree';
7
7
  import './role-add.scss';
8
8
 
9
9
  const { Title } = Typography;
10
- const { Panel } = Collapse;
11
10
 
12
- const RoleAdd = ({ model, callback, edit, formContent = {}, match, additional_queries = [] }) => {
11
+ const RoleAdd = ({ model, callback, edit, formContent = {}, match, additional_queries = [], onSubmittingChange, formRef }) => {
13
12
  let mode = 'Add';
14
13
  if (formContent.id) mode = 'Edit';
15
14
  else if (formContent.copy) mode = 'copy';
@@ -31,8 +30,10 @@ const RoleAdd = ({ model, callback, edit, formContent = {}, match, additional_qu
31
30
 
32
31
  const isEdit = !!(formContent.id || formContent.copy);
33
32
  const [initialLoading, setInitialLoading] = useState(isEdit);
34
- const [loading, setLoading] = useState(false);
35
33
  const [form] = Form.useForm();
34
+
35
+ // expose the form instance so the Drawer footer Save button can submit it
36
+ if (formRef) formRef.current = form;
36
37
  const [pages, setPages] = useState([]);
37
38
  const [models, setModels] = useState([]);
38
39
  const [menuList, setMenuList] = useState([]);
@@ -43,6 +44,9 @@ const RoleAdd = ({ model, callback, edit, formContent = {}, match, additional_qu
43
44
  // Selected menus (array of IDs)
44
45
  const [selectedMenus, setSelectedMenus] = useState([]);
45
46
 
47
+ // Search term for filtering the menu list
48
+ const [menuSearch, setMenuSearch] = useState('');
49
+
46
50
  const location = useLocation();
47
51
  const query = new URLSearchParams(location.search);
48
52
  let step = parseInt(query.get('step')) || 1;
@@ -97,40 +101,28 @@ const RoleAdd = ({ model, callback, edit, formContent = {}, match, additional_qu
97
101
  setSelectedMenus((prev) => (checked ? [...prev, id] : prev.filter((m) => m !== id)));
98
102
  };
99
103
 
100
- // Submit handler
101
- // const onSubmit = (values) => {
102
- // setLoading(true);
103
-
104
- // const payload = {
105
- // ...values,
106
- // menu_ids
107
- // : selectedMenus,
108
- // attributes: JSON.stringify(values.attributes || {}),
109
- // };
110
-
111
- // if (formContent.id) {
112
- // RolesAPI.updateRole({ id: formContent.id, formBody: payload })
113
- // .then(() => {
114
- // message.success('Role Updated');
115
- // setLoading(false);
116
- // callback();
117
- // })
118
- // .catch(() => setLoading(false));
119
- // } else {
120
- // additional_queries.forEach(({ field, value }) => {
121
- // payload[field] = value;
122
- // });
123
- // RolesAPI.createRole(payload)
124
- // .then(() => {
125
- // message.success('Role Added');
126
- // setLoading(false);
127
- // callback();
128
- // })
129
- // .catch(() => setLoading(false));
130
- // }
131
- // };
104
+ // Recursively filter menus by caption. A menu is kept if its caption matches
105
+ // the search term, or if any of its descendants match (ancestors are kept so
106
+ // the matching child stays reachable).
107
+ const filterMenus = (menus, term) => {
108
+ if (!term) return menus;
109
+ const lower = term.toLowerCase();
110
+
111
+ return menus.reduce((acc, menu) => {
112
+ const children = filterMenus(menu.sub_menus || [], term);
113
+ const selfMatch = (menu.caption || '').toLowerCase().includes(lower);
114
+
115
+ if (selfMatch || children.length > 0) {
116
+ acc.push({ ...menu, sub_menus: children });
117
+ }
118
+ return acc;
119
+ }, []);
120
+ };
121
+
122
+ const filteredMenuList = filterMenus(menuList, menuSearch);
123
+
132
124
  const onSubmit = async (values) => {
133
- setLoading(true);
125
+ onSubmittingChange?.(true);
134
126
 
135
127
  // Find menus that were originally selected but now deselected
136
128
  const deselectedMenus = originalMenus.filter((id) => !selectedMenus.includes(id));
@@ -159,7 +151,7 @@ const RoleAdd = ({ model, callback, edit, formContent = {}, match, additional_qu
159
151
  } catch (err) {
160
152
  // message.error('Something went wrong');
161
153
  } finally {
162
- setLoading(false);
154
+ onSubmittingChange?.(false);
163
155
  }
164
156
  };
165
157
 
@@ -194,6 +186,7 @@ const RoleAdd = ({ model, callback, edit, formContent = {}, match, additional_qu
194
186
  display: 'flex',
195
187
  justifyContent: 'space-between',
196
188
  alignItems: 'center',
189
+ gap: 16,
197
190
  marginBottom: 16,
198
191
  }}
199
192
  >
@@ -204,14 +197,20 @@ const RoleAdd = ({ model, callback, edit, formContent = {}, match, additional_qu
204
197
  <p style={{ color: '#999', marginBottom: 0 }}>Choose menus and set permissions</p>
205
198
  </div>
206
199
 
207
- <Form.Item style={{ marginBottom: 0 }}>
208
- <Button loading={loading} htmlType="submit" type="primary">
209
- Save
210
- </Button>
211
- </Form.Item>
200
+ <Input.Search
201
+ allowClear
202
+ placeholder="Search menus"
203
+ value={menuSearch}
204
+ onChange={(e) => setMenuSearch(e.target.value)}
205
+ style={{ maxWidth: 260 }}
206
+ />
212
207
  </div>
213
208
 
214
- <MenuTree menus={menuList} selectedMenus={selectedMenus} toggleMenu={toggleMenu} />
209
+ {filteredMenuList.length > 0 ? (
210
+ <MenuTree menus={filteredMenuList} selectedMenus={selectedMenus} toggleMenu={toggleMenu} searchActive={!!menuSearch} />
211
+ ) : (
212
+ <p style={{ color: '#999', textAlign: 'center', padding: '16px 0' }}>No menus found</p>
213
+ )}
215
214
  </div>
216
215
  )}
217
216
  </Form>
@@ -221,125 +220,3 @@ const RoleAdd = ({ model, callback, edit, formContent = {}, match, additional_qu
221
220
  };
222
221
 
223
222
  export default RoleAdd;
224
-
225
- // ------------------------
226
- // Recursive Nested Menu Component
227
- // ------------------------
228
- // ------------------------
229
- // Recursive Nested Menu Component
230
- // ------------------------
231
- const MenuTree = ({ menus, selectedMenus, toggleMenu, parentId = null }) => {
232
- // Helper: check if parent should be checked
233
- const isParentChecked = (menu) => {
234
- if (!menu.sub_menus || menu.sub_menus.length === 0) {
235
- return selectedMenus.includes(menu.id);
236
- }
237
- const allChildIds = menu.sub_menus.map((c) => c.id);
238
- return allChildIds.every((id) => selectedMenus.includes(id));
239
- };
240
-
241
- // Helper: check if parent is indeterminate
242
- const isParentIndeterminate = (menu) => {
243
- if (!menu.sub_menus || menu.sub_menus.length === 0) return false;
244
- const allChildIds = menu.sub_menus.map((c) => c.id);
245
- const checkedCount = allChildIds.filter((id) => selectedMenus.includes(id)).length;
246
- return checkedCount > 0 && checkedCount < allChildIds.length;
247
- };
248
-
249
- return (
250
- <>
251
- {menus.map((menu) => {
252
- const children = menu.sub_menus || [];
253
- const parentChecked = isParentChecked(menu);
254
- const parentIndeterminate = isParentIndeterminate(menu);
255
-
256
- const onParentChange = (checked) => {
257
- toggleMenu(menu.id, checked);
258
- // toggle children recursively
259
- children.forEach((c) => toggleMenuRecursive(c, checked));
260
- };
261
-
262
- const toggleMenuRecursive = (menu, checked) => {
263
- toggleMenu(menu.id, checked);
264
- if (menu.sub_menus && menu.sub_menus.length > 0) {
265
- menu.sub_menus.forEach((c) => toggleMenuRecursive(c, checked));
266
- }
267
- };
268
-
269
- if (children.length === 0) {
270
- return (
271
- <div
272
- style={{
273
- justifyContent: 'space-between',
274
- display: 'flex',
275
- alignItems: 'center',
276
- border: '1px solid rgba(198, 195, 195, 0.85)',
277
- // borderRadius: 6,
278
- padding: '12px 16px',
279
- marginBottom: 6,
280
- background: '#fff',
281
- }}
282
- >
283
- <div
284
- key={menu.id}
285
- style={{
286
- display: 'flex',
287
- alignItems: 'center',
288
- gap: 8,
289
- }}
290
- >
291
- <Checkbox
292
- checked={selectedMenus.includes(menu.id)}
293
- onChange={(e) => {
294
- const checked = e.target.checked;
295
-
296
- toggleMenu(menu.id, checked);
297
-
298
- // FORCE parent selection
299
- if (checked && parentId) {
300
- toggleMenu(parentId, true);
301
- }
302
- }}
303
- />
304
- <span>{menu.caption}</span>
305
- </div>
306
- <Tag color={menu.is_visible === true ? 'green' : 'blue'}>{menu.is_visible === true ? 'VISIBLE' : 'HIDDEN'}</Tag>{' '}
307
- </div>
308
- );
309
- }
310
-
311
- return (
312
- <Collapse
313
- key={menu.id}
314
- style={{ marginBottom: 6 }}
315
- // defaultActiveKey={[menu.id]}
316
- >
317
- <Panel
318
- key={menu.id}
319
- header={
320
- <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
321
- <div
322
- style={{
323
- display: 'flex',
324
- alignItems: 'center',
325
- gap: 8,
326
- }}
327
- onClick={(e) => e.stopPropagation()}
328
- >
329
- <Checkbox checked={parentChecked} indeterminate={parentIndeterminate} onChange={(e) => onParentChange(e.target.checked)} />
330
- <span>{menu.caption}</span>
331
- </div>
332
- <Tag color={menu.is_visible === true ? 'green' : 'blue'}>{menu.is_visible === true ? 'VISIBLE' : 'HIDDEN'}</Tag>{' '}
333
- </div>
334
- }
335
- >
336
- <div style={{ paddingLeft: 20 }}>
337
- <MenuTree menus={children} selectedMenus={selectedMenus} toggleMenu={toggleMenu} parentId={menu.id} />
338
- </div>
339
- </Panel>
340
- </Collapse>
341
- );
342
- })}
343
- </>
344
- );
345
- };
@@ -40,6 +40,12 @@ const RoleList = ({ model, match, relativeAdd = false, additional_queries = [],
40
40
 
41
41
  const [single, setSingle] = useState({});
42
42
 
43
+ // tracks RoleAdd form submission so the Drawer footer button shows loading
44
+ const [submitting, setSubmitting] = useState(false);
45
+
46
+ // holds the RoleAdd form instance so the Drawer footer Save button can submit it
47
+ const formRef = useRef(null);
48
+
43
49
  var [queryValue, setQueryValue] = useState('');
44
50
 
45
51
  const { Search } = Input;
@@ -360,12 +366,26 @@ const RoleList = ({ model, match, relativeAdd = false, additional_queries = [],
360
366
  destroyOnClose
361
367
  onClose={closeModal}
362
368
  bodyStyle={{ paddingBottom: 80 }}
369
+ footer={
370
+ <div style={{ textAlign: 'right' }}>
371
+ <Space size="small">
372
+ <Button type="default" onClick={closeModal}>
373
+ Cancel
374
+ </Button>
375
+ <Button type="primary" loading={submitting} onClick={() => formRef.current?.submit()}>
376
+ Save
377
+ </Button>
378
+ </Space>
379
+ </div>
380
+ }
363
381
  >
364
382
  <model.ModalAddComponent
365
383
  match={match}
366
384
  model={model}
367
385
  additional_queries={additional_queries}
368
386
  formContent={single}
387
+ formRef={formRef}
388
+ onSubmittingChange={setSubmitting}
369
389
  callback={(event) => {
370
390
  closeModal();
371
391
  getRecords();