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.
@@ -1,14 +1,13 @@
1
- import React, { useState, useRef, useEffect } from 'react';
2
- import { Space, Switch, Popconfirm, Skeleton, Input, Drawer, Collapse } from 'antd';
3
- import { ReloadOutlined, DeleteOutlined, EditOutlined, CopyOutlined, PlusCircleFilled } from '@ant-design/icons';
4
- import { Link, useParams, useLocation } from 'react-router-dom';
5
-
1
+ import React, { useState, useCallback, useEffect } from 'react';
2
+ import { Space, Popconfirm, Input, Drawer, Skeleton, Collapse, message } from 'antd';
3
+ import { ReloadOutlined, DeleteOutlined, EditOutlined, PlusCircleFilled, CopyOutlined } from '@ant-design/icons';
4
+ import { Button, Card, Switch, DraggableWrapper } from '../../../../lib';
6
5
  // for draggable menu list import { DndProvider } from "react-dnd";
7
6
  import { DndProvider } from 'react-dnd';
7
+ import { Link, useParams, useLocation } from 'react-router-dom';
8
8
  import { HTML5Backend } from 'react-dnd-html5-backend';
9
- import { DraggableWrapper } from '../../../../lib';
9
+
10
10
  import './menu-lists.scss';
11
- import { Button, Card } from '../../../../lib';
12
11
 
13
12
  const { Search } = Input;
14
13
  const { Panel } = Collapse;
@@ -18,36 +17,45 @@ const MenuLists = ({ model, match, relativeAdd = false, additional_queries = [],
18
17
 
19
18
  match = useParams();
20
19
  const id = match.id;
21
- const location = useLocation();
22
-
20
+ // const location = useLocation();
23
21
  const step = parseInt(new URLSearchParams(location.search).get('step')) || 1;
24
22
 
25
23
  const [records, setRecords] = useState([]);
24
+ const [originalRecords, setOriginalRecords] = useState([]);
26
25
  const [loading, setLoading] = useState(false);
27
-
28
26
  const [drawerVisible, setDrawerVisible] = useState(false);
29
-
30
- const [view, setView] = useState(false);
31
-
32
- const [visible, setVisible] = useState(false);
33
-
34
27
  const [single, setSingle] = useState({});
35
28
  const [drawerTitle, setDrawerTitle] = useState('');
36
29
  const [selectedRecord, setSelectedRecord] = useState(null);
37
-
38
30
  const [query, setQuery] = useState('');
39
-
40
- // DEFAULT DRAG MODE: OFF
41
- const [dragMode, setDragMode] = useState(false); // for drag (default OFF)
42
-
43
- // to detect drag changes
31
+ const [dragMode, setDragMode] = useState(false);
44
32
  const [orderChanged, setOrderChanged] = useState(false);
45
33
 
46
- const toggleDragMode = (checked) => setDragMode(checked);
34
+ const [nextId, setNextId] = useState(10000);
47
35
 
48
36
  useEffect(() => {
49
37
  loadMenus();
50
38
  }, [id]);
39
+
40
+ const loadMenus = () => {
41
+ setLoading(true);
42
+
43
+ model
44
+ .getCoreMenuLists
45
+ // {
46
+ // queries: [...additional_queries, { field: 'step', value: step }, { field: 'header_id', value: null }],
47
+ // }
48
+ ()
49
+ .then((res) => {
50
+ const sorted = (res.result || []).sort((a, b) => a.order - b.order);
51
+ setRecords(sorted);
52
+ setOriginalRecords(sorted);
53
+ setLoading(false);
54
+ })
55
+ .catch(() => setLoading(false));
56
+ };
57
+
58
+ console.log('record', records);
51
59
  /**
52
60
  *
53
61
  */
@@ -95,198 +103,321 @@ const MenuLists = ({ model, match, relativeAdd = false, additional_queries = [],
95
103
  });
96
104
  };
97
105
 
98
- // function changeView(result) {
99
- // setView(result);
100
- // }
101
- const loadMenus = () => {
102
- setLoading(true);
106
+ const toggleDragMode = (checked) => {
107
+ setDragMode(checked);
108
+ if (checked) {
109
+ message.info('Drag mode enabled - Drag items anywhere, including the "Drop here" zones!');
110
+ }
111
+ };
103
112
 
104
- model
105
- .get({
106
- queries: [...additional_queries, { field: 'step', value: step }, { field: 'header_id', value: null }],
107
- })
108
- .then((res) => {
109
- const sorted = (res.result || []).sort((a, b) => a.order - b.order);
110
- setRecords(sorted);
111
- setLoading(false);
112
- })
113
- .catch(() => setLoading(false));
113
+ // Function to find and remove an item from anywhere in the tree
114
+ const findAndRemoveItem = (items, itemId) => {
115
+ let foundItem = null;
116
+
117
+ const removeRecursive = (arr) => {
118
+ for (let i = 0; i < arr.length; i++) {
119
+ if (arr[i].id === itemId) {
120
+ foundItem = { ...arr[i] };
121
+ arr.splice(i, 1);
122
+ return true;
123
+ }
124
+ if (arr[i].sub_menus && arr[i].sub_menus.length > 0) {
125
+ if (removeRecursive(arr[i].sub_menus)) {
126
+ return true;
127
+ }
128
+ }
129
+ }
130
+ return false;
131
+ };
132
+
133
+ const newItems = JSON.parse(JSON.stringify(items));
134
+ removeRecursive(newItems);
135
+ return { newItems, foundItem };
114
136
  };
115
137
 
138
+ // Function to insert item at specific location
139
+ const insertItemAt = (items, item, targetLevel, targetParentId, targetIndex) => {
140
+ const newItems = JSON.parse(JSON.stringify(items));
141
+
142
+ // Update item properties for new location
143
+ const updatedItem = {
144
+ ...item,
145
+ step: targetLevel,
146
+ header_id: targetParentId,
147
+ };
148
+
149
+ if (targetLevel === 1) {
150
+ // Insert at main level
151
+ newItems.splice(targetIndex, 0, updatedItem);
152
+ } else {
153
+ // Insert into nested level
154
+ const insertRecursive = (arr) => {
155
+ for (let i = 0; i < arr.length; i++) {
156
+ if (arr[i].id === targetParentId) {
157
+ if (!arr[i].sub_menus) arr[i].sub_menus = [];
158
+ arr[i].sub_menus.splice(targetIndex, 0, updatedItem);
159
+ return true;
160
+ }
161
+ if (arr[i].sub_menus && arr[i].sub_menus.length > 0) {
162
+ if (insertRecursive(arr[i].sub_menus)) {
163
+ return true;
164
+ }
165
+ }
166
+ }
167
+ return false;
168
+ };
169
+ insertRecursive(newItems);
170
+ }
171
+
172
+ return newItems;
173
+ };
174
+
175
+ const handleCrossLevelMove = useCallback(
176
+ (draggedItem, dropTarget) => {
177
+ const { targetLevel, targetParentId, targetIndex } = dropTarget;
178
+
179
+ // Prevent moving item into itself or its children
180
+ if (draggedItem.id === targetParentId) {
181
+ message.warning('Cannot move item into itself');
182
+ return;
183
+ }
184
+
185
+ // Remove item from original location
186
+ const { newItems: itemsAfterRemove, foundItem } = findAndRemoveItem(records, draggedItem.id);
187
+
188
+ if (!foundItem) {
189
+ message.error('Could not find item to move');
190
+ return;
191
+ }
192
+
193
+ // Insert item at new location
194
+ const finalItems = insertItemAt(itemsAfterRemove, foundItem, targetLevel, targetParentId, targetIndex);
195
+
196
+ setRecords(finalItems);
197
+ setOrderChanged(true);
198
+ message.success(`Moved "${foundItem.name}" to level ${targetLevel}`);
199
+ },
200
+ [records]
201
+ );
202
+
116
203
  const deleteRecord = (rec) => {
117
204
  model.delete(rec).then(loadMenus);
118
205
  };
119
206
 
120
- const filtered = records.filter((r) => r.name?.toUpperCase().includes(query.toUpperCase())); /**
121
- * Function to store search value
122
- */
207
+ const filtered = records.filter((r) => r.name?.toUpperCase().includes(query.toUpperCase()));
208
+
209
+ const visibleItems = dragMode ? records : filtered;
210
+
123
211
  const onSearch = (event) => {
124
- setQuery(event.target.value); // <-- use setQuery
212
+ setQuery(event.target.value);
125
213
  };
126
214
 
127
215
  // ------------------------
128
216
  // Recursive function to build payload
129
217
  // ------------------------
130
- const buildOrderPayload = (menus) =>
131
- menus.map((menu, index) => {
132
- const item = { id: menu.id, order: index + 1 };
218
+ const buildUpdatedOrderPayload = (menus, originalMenus) => {
219
+ const result = [];
133
220
 
134
- // FIX: use sub_menus instead of children
135
- if (menu.sub_menus && menu.sub_menus.length > 0) {
136
- item.sub_menus = buildOrderPayload(menu.sub_menus);
221
+ menus.forEach((menu, index) => {
222
+ const original = findMenuById(originalMenus, menu.id);
223
+
224
+ const newOrder = index + 1;
225
+ const oldOrder = original ? original.order : null;
226
+
227
+ const orderChanged = oldOrder !== newOrder;
228
+ const parentChanged = original?.header_id !== menu.header_id;
229
+
230
+ // check sub menus
231
+ let updatedSubMenus = [];
232
+ if (menu.sub_menus?.length) {
233
+ updatedSubMenus = buildUpdatedOrderPayload(menu.sub_menus, originalMenus);
137
234
  }
138
235
 
139
- return item;
236
+ // include only if changed OR has changed children
237
+ if (orderChanged || parentChanged || updatedSubMenus.length) {
238
+ result.push({
239
+ id: menu.id,
240
+ order: newOrder,
241
+ header_id: menu.header_id,
242
+ name: menu.name,
243
+ ...(updatedSubMenus.length && { sub_menus: updatedSubMenus }),
244
+ });
245
+ }
140
246
  });
141
247
 
248
+ return result;
249
+ };
250
+
251
+ // ------------------------
252
+ // Find menu by id (recursive)
253
+ // ------------------------
254
+ const findMenuById = (menus, id) => {
255
+ for (const menu of menus || []) {
256
+ if (menu.id === id) return menu;
257
+
258
+ if (menu.sub_menus?.length) {
259
+ const found = findMenuById(menu.sub_menus, id);
260
+ if (found) return found;
261
+ }
262
+ }
263
+ return null;
264
+ };
265
+
142
266
  // ------------------------
143
267
  // Save order function
144
268
  // ------------------------
145
269
  const saveOrder = () => {
146
- const payload = { menus: buildOrderPayload(records) };
270
+ const updatedMenus = buildUpdatedOrderPayload(records, originalRecords);
271
+
272
+ if (!updatedMenus.length) {
273
+ message.info('No order changes to save');
274
+ return;
275
+ }
276
+
277
+ const payload = { menus: updatedMenus };
147
278
 
148
279
  model
149
280
  .saveOrder({ formBody: payload })
150
- .then((res) => {
281
+ .then(() => {
151
282
  setOrderChanged(false);
152
-
153
- loadMenus(); // force refresh
283
+ loadMenus(); // refresh from backend
154
284
  })
155
- // const { updated_ids, same_order_ids } = res.result;
156
-
157
- // Combine the updated order
158
- // const newOrder = [...updated_ids, ...same_order_ids];
159
- // const reorderedRecords = newOrder.map((id) => records.find((r) => r.id === id)).filter(Boolean); // remove undefined if not found
160
-
161
- // setRecords(reorderedRecords);
162
- // loadMenus(); // <-- reload from backend
163
- // setOrderChanged(false);
164
- // })
165
- .catch((err) => {
166
- console.error(err);
167
-
168
- });
285
+ .catch(console.error);
169
286
  };
170
287
 
288
+ const movePanel = useCallback(
289
+ (from, to) => {
290
+ if (!dragMode) return;
291
+ if (from === to) return;
292
+
293
+ setRecords((prevRecords) => {
294
+ const updated = [...prevRecords];
295
+ const [moved] = updated.splice(from, 1);
296
+ updated.splice(to, 0, moved);
297
+ return updated;
298
+ });
299
+
300
+ setOrderChanged(true);
301
+ },
302
+ [dragMode]
303
+ );
304
+
171
305
  return (
172
306
  <Card className="generic-list">
173
- <div className="table-header">
174
- <div className="table-title">
307
+ <div style={{ marginBottom: 16 }}>
308
+ <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }}>
175
309
  <Search placeholder="Enter Search Value" allowClear style={{ width: 300, marginBottom: '0px' }} onChange={onSearch} />
176
- {/* <Title level={4}>{model.name}</Title> */}
177
310
 
178
- {/* <p>{loading ? 'Loading records' : `${record.length} records`}</p> */}
311
+ <Space size="small">
312
+ <Button onClick={getRecords} size={'small'} type="default">
313
+ <ReloadOutlined />
314
+ </Button>
315
+
316
+ <Switch checked={dragMode} onChange={toggleDragMode} checkedChildren="Order On" unCheckedChildren="Order Off" />
317
+
318
+ {dragMode && orderChanged && (
319
+ <Button onClick={saveOrder} type="primary" size="small">
320
+ Save Order
321
+ </Button>
322
+ )}
323
+
324
+ <Button
325
+ type="primary"
326
+ size="small"
327
+ onClick={() => {
328
+ setSelectedRecord({ step: 1, header_id: null });
329
+ setDrawerTitle('Create New Menu');
330
+ setDrawerVisible(true);
331
+ }}
332
+ >
333
+ Create New Menu
334
+ </Button>
335
+ </Space>
179
336
  </div>
180
337
 
181
- <div className="table-bar">
182
- {/* Table Filters */}
183
- <div className="table-filters">
184
- <Space direction="vertical" size={12}></Space>
185
- </div>
186
- {/* Table Filters Ends */}
187
-
188
- <div className="table-actions">
189
- <div className="button-container">
190
- <Space size="small">
191
- <Button onClick={getRecords} size={'small'} type="default">
192
- <ReloadOutlined />
193
- </Button>
194
-
195
- {/* NEW SWITCH: Drag Mode */}
196
- <Switch
197
- checked={dragMode}
198
- onChange={toggleDragMode}
199
- checkedChildren="Order On"
200
- unCheckedChildren="Order Off"
201
- className="custom-switch"
202
- />
203
- {/* Only show Save Order button when a change happens */}
204
- {dragMode && orderChanged && (
205
- <Button type="primary" size="small" onClick={saveOrder}>
206
- Save Order
207
- </Button>
208
- )}
209
-
210
- {disableAddModal || !model.ModalAddComponent ? null : (
211
- <Button
212
- type="primary"
213
- size="small"
214
- onClick={() => {
215
- setSelectedRecord({}); // empty record for creation
216
- setDrawerTitle('Create New Menu');
217
- setDrawerVisible(true); // this controls Drawer visibility
218
- }}
219
- >
220
- Create New Menu
221
- </Button>
222
- )}
223
- </Space>
224
- </div>
338
+ {dragMode && (
339
+ <div
340
+ style={{
341
+ padding: '8px 12px',
342
+ backgroundColor: '#e6f7ff',
343
+ borderRadius: 4,
344
+ border: '1px solid #91d5ff',
345
+ fontSize: 13,
346
+ }}
347
+ >
348
+ <strong>💡 Tips:</strong>
349
+ <ul style={{ margin: '4px 0', paddingLeft: 20 }}>
350
+ <li>Drag between items to reorder at the same level</li>
351
+ <li>Drop on the green "↳ Drop here" zone to make it a child of that item</li>
352
+ <li>Works at all levels - unlimited nesting supported!</li>
353
+ </ul>
225
354
  </div>
226
- </div>
355
+ )}
227
356
  </div>
228
-
229
357
  {loading ? (
230
358
  <Skeleton active />
231
359
  ) : (
232
360
  <>
233
361
  <>
234
- {!view ? (
235
- <Card>
236
- <DndProvider backend={HTML5Backend}>
237
- <Collapse accordion className="custom-collapse">
238
- {filtered.map((item, index) => (
239
- <Panel
240
- key={item.id}
241
- className="custom-panel"
242
- header={
243
- <DraggableWrapper
244
- id={item.id}
245
- index={index}
246
- movePanel={(from, to) => {
247
- if (!dragMode) return;
248
-
249
- const updated = [...records];
250
- const [moved] = updated.splice(from, 1);
251
- updated.splice(to, 0, moved);
252
-
253
- setRecords(updated);
254
- setOrderChanged(true); // <-- mark as changed
255
- }}
256
- title={item.name}
257
- dragEnabled={dragMode} // pass dragMode to wrapper
258
- />
259
- }
260
- extra={panelActions(item, model, setSelectedRecord, setDrawerTitle, setDrawerVisible, deleteRecord)}
261
- >
362
+ {/* {!view ? ( */}
363
+ <Card>
364
+ <DndProvider backend={HTML5Backend}>
365
+ <Collapse accordion>
366
+ {visibleItems.map((item, index) => (
367
+ <Panel
368
+ key={item.id}
369
+ header={
370
+ <DraggableWrapper
371
+ id={item.id}
372
+ index={index}
373
+ movePanel={movePanel}
374
+ item={item}
375
+ dragEnabled={dragMode}
376
+ level={1}
377
+ parentId={null}
378
+ onCrossLevelMove={handleCrossLevelMove}
379
+ canAcceptChildren={true}
380
+ />
381
+ }
382
+ // only show arrow if sub_menus exist
383
+ showArrow={item.sub_menus && item.sub_menus.length > 0}
384
+ // disable panel
385
+ collapsible={item.sub_menus && item.sub_menus.length > 0 ? 'header' : 'disabled'}
386
+ extra={panelActions(item, model, setSelectedRecord, setDrawerTitle, setDrawerVisible, deleteRecord)}
387
+ >
388
+ {item.sub_menus && item.sub_menus.length > 0 && (
262
389
  <NestedMenu
263
390
  parentId={item.id}
264
- step={step + 1}
391
+ step={item.step + 1}
392
+ items={item.sub_menus || []}
265
393
  model={model}
266
394
  dragMode={dragMode}
267
395
  setSelectedRecord={setSelectedRecord}
268
396
  setDrawerTitle={setDrawerTitle}
269
397
  setDrawerVisible={setDrawerVisible}
270
398
  deleteRecord={deleteRecord}
399
+ level={2}
400
+ onCrossLevelMove={handleCrossLevelMove}
271
401
  onChange={(subMenus) => {
272
402
  const updated = records.map((r) => (r.id === item.id ? { ...r, sub_menus: subMenus } : r));
273
403
  setRecords(updated);
274
404
  setOrderChanged(true);
275
405
  }}
276
406
  />
277
- </Panel>
278
- ))}
279
- </Collapse>
280
- </DndProvider>
281
- </Card>
282
- ) : (
407
+ )}
408
+ </Panel>
409
+ ))}
410
+ </Collapse>
411
+ </DndProvider>
412
+ </Card>
413
+ {/* ) : (
283
414
  <CardList model={model} data={filtered ? filtered : records} />
284
- )}
415
+ )} */}
285
416
  </>
286
417
  </>
287
418
  )}
288
- {/* DRAWER */}
289
- <Drawer title={drawerTitle} open={drawerVisible} width="50%" destroyOnClose onClose={() => setDrawerVisible(false)}>
419
+
420
+ <Drawer title={drawerTitle} open={drawerVisible} width="80%" destroyOnClose onClose={() => setDrawerVisible(false)}>
290
421
  {model.ModalAddComponent && (
291
422
  <model.ModalAddComponent
292
423
  formContent={selectedRecord}
@@ -303,15 +434,8 @@ const MenuLists = ({ model, match, relativeAdd = false, additional_queries = [],
303
434
  </Card>
304
435
  );
305
436
  };
306
- function CardList({ model, data, url, ...props }) {
307
- return data.map((record, index) => {
308
- return <model.Card record={record} model={model} index={index} key={index} {...record} {...props} />;
309
- });
310
- }
311
- export default MenuLists;
312
-
313
437
  /* -----------------------------------------------------------------------
314
- PANEL ACTIONS (NOW FIXED)
438
+ PANEL ACTIONS
315
439
  ------------------------------------------------------------------------ */
316
440
  function panelActions(item, model, setSelectedRecord, setDrawerTitle, setDrawerVisible, deleteRecord) {
317
441
  return (
@@ -322,16 +446,18 @@ function panelActions(item, model, setSelectedRecord, setDrawerTitle, setDrawerV
322
446
  type="dashed"
323
447
  onClick={() => {
324
448
  setSelectedRecord({
325
- header_id: item.id, // parent menu id
449
+ // parent menu id
450
+ header_id: item.id,
326
451
  step: item.step + 1,
327
- id: null, // new record
452
+ // new record
453
+ id: null,
328
454
  });
329
455
  setDrawerTitle(`Add Submenu to "${item.name}"`);
330
456
  setDrawerVisible(true);
331
457
  }}
332
458
  >
333
459
  <PlusCircleFilled />
334
- add submenu
460
+ Add Sub Menu
335
461
  </Button>
336
462
 
337
463
  {model.ModalAddComponent && (
@@ -369,93 +495,83 @@ function panelActions(item, model, setSelectedRecord, setDrawerTitle, setDrawerV
369
495
  );
370
496
  }
371
497
 
372
- // ------------------------
373
- // NestedMenu: Pass sub_menus recursively
374
- // ------------------------
375
- // ------------------------
376
-
377
498
  function NestedMenu({
378
499
  parentId,
379
500
  step,
501
+ items,
380
502
  model,
381
503
  dragMode,
382
-
383
504
  setSelectedRecord,
384
505
  setDrawerTitle,
385
506
  setDrawerVisible,
386
507
  deleteRecord,
387
- onChange, // callback to propagate changes to parent
508
+ level,
509
+ onCrossLevelMove,
510
+ onChange,
388
511
  }) {
389
- const [items, setItems] = useState([]);
390
- const [loading, setLoading] = useState(true);
512
+ // do not render Collapse
513
+ if (!items || items.length === 0) return null;
514
+
515
+ const [localItems, setLocalItems] = useState(items);
391
516
 
392
- // Load child menus
393
- // NestedMenu
394
517
  useEffect(() => {
395
- setLoading(true);
396
- model
397
- .get({
398
- queries: [
399
- { field: 'header_id', value: parentId },
400
- { field: 'step', value: step },
401
- ],
402
- })
403
- .then((res) => {
404
- const sortedMenus = (res.result || [])
405
- .sort((a, b) => a.order - b.order) // sort top-level in this submenu
406
- .map((menu) => ({
407
- ...menu,
408
- sub_menus: menu.sub_menus ? menu.sub_menus.sort((a, b) => a.order - b.order) : [],
409
- }));
410
- setItems(sortedMenus);
411
- setLoading(false);
412
- })
413
- .catch(() => setLoading(false));
414
- }, [parentId, step]);
415
-
416
- // Move submenu
417
- const moveSubMenu = (from, to) => {
418
- const updated = [...items];
419
- const [moved] = updated.splice(from, 1);
420
- updated.splice(to, 0, moved);
421
- setItems(updated);
422
- onChange?.(updated); // propagate to parent
423
- };
518
+ setLocalItems(items);
519
+ }, [items]);
424
520
 
425
- if (loading) return <Skeleton active />;
426
- if (!items.length) return <p style={{ paddingLeft: 20 }}>No submenu</p>;
521
+ const moveSubMenu = useCallback(
522
+ (from, to) => {
523
+ if (!dragMode || from === to) return;
524
+
525
+ const updated = [...localItems];
526
+ const [moved] = updated.splice(from, 1);
527
+ updated.splice(to, 0, moved);
528
+
529
+ setLocalItems(updated);
530
+ onChange?.(updated);
531
+ },
532
+ [dragMode, localItems, onChange]
533
+ );
534
+ if (!localItems || localItems.length === 0) {
535
+ // <-- don’t render anything if no submenus
536
+ return null;
537
+ }
427
538
 
428
539
  return (
429
- <Collapse accordion className="nested-collapse">
430
- {items.map((child, index) => (
540
+ <Collapse accordion>
541
+ {localItems.map((child, index) => (
431
542
  <Panel
432
543
  key={child.id}
433
544
  header={
434
545
  <DraggableWrapper
435
546
  id={child.id}
436
547
  index={index}
437
- movePanel={(from, to) => {
438
- if (!dragMode) return; // block drag when OFF
439
- moveSubMenu(from, to);
440
- }}
441
- title={child.name}
548
+ movePanel={moveSubMenu}
549
+ item={child}
442
550
  dragEnabled={dragMode}
551
+ level={level}
552
+ parentId={parentId}
553
+ onCrossLevelMove={onCrossLevelMove}
554
+ canAcceptChildren={true}
443
555
  />
444
556
  }
557
+ // only show arrow if sub_menus exist
558
+ showArrow={child.sub_menus && child.sub_menus.length > 0}
445
559
  extra={panelActions(child, model, setSelectedRecord, setDrawerTitle, setDrawerVisible, deleteRecord)}
446
560
  >
447
- {/* Recursively render submenus */}
448
561
  <NestedMenu
449
562
  parentId={child.id}
450
563
  step={step + 1}
451
- model={model}
564
+ items={child.sub_menus || []}
565
+ dragMode={dragMode}
452
566
  setSelectedRecord={setSelectedRecord}
453
567
  setDrawerTitle={setDrawerTitle}
454
568
  setDrawerVisible={setDrawerVisible}
455
569
  deleteRecord={deleteRecord}
570
+ level={level + 1}
571
+ onCrossLevelMove={onCrossLevelMove}
456
572
  onChange={(submenus) => {
457
- const updated = items.map((item) => (item.id === child.id ? { ...item, sub_menus: submenus } : item));
458
- setItems(updated);
573
+ const updated = localItems.map((item) => (item.id === child.id ? { ...item, sub_menus: submenus } : item));
574
+ setLocalItems(updated);
459
575
  onChange?.(updated);
460
576
  }}
461
577
  />
@@ -464,3 +580,5 @@ function NestedMenu({
464
580
  </Collapse>
465
581
  );
466
582
  }
583
+
584
+ export default MenuLists;