ui-soxo-bootstrap-core 2.5.6 → 2.5.7

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,61 +1,136 @@
1
- import React, { useRef } from "react";
2
- import { useDrag, useDrop } from "react-dnd";
1
+ import React, { useRef } from 'react';
2
+ import { useDrag, useDrop } from 'react-dnd';
3
3
 
4
- const ItemTypes = { PANEL: "panel" };
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;
5
8
 
6
- export default function DraggableWrapper({ id, index, movePanel, title, dragEnabled = true }) {
7
- const ref = useRef(null);
9
+ const EDGE = 80;
10
+ const SPEED = 20;
8
11
 
9
- const [, drop] = useDrop({
10
- accept: ItemTypes.PANEL,
11
- hover(item, monitor) {
12
- if (!dragEnabled) return; // ignore hover if dragging is disabled
13
- if (!ref.current) return;
12
+ const viewportHeight = window.innerHeight;
14
13
 
15
- const dragIndex = item.index;
16
- const hoverIndex = index;
14
+ // 🔼 scroll UP
15
+ if (offset.y < EDGE) {
16
+ window.scrollBy(0, -SPEED);
17
+ }
17
18
 
18
- if (dragIndex === hoverIndex) return;
19
+ // 🔽 scroll DOWN
20
+ if (offset.y > viewportHeight - EDGE) {
21
+ window.scrollBy(0, SPEED);
22
+ }
23
+ };
19
24
 
20
- const hoverBoundingRect = ref.current.getBoundingClientRect();
21
- const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
22
- const clientOffset = monitor.getClientOffset();
23
- const hoverClientY = clientOffset.y - hoverBoundingRect.top;
25
+ const [{ isDragging }, drag] = useDrag({
26
+ type: 'PANEL',
27
+ item: { id, index, level, parentId },
28
+ canDrag: dragEnabled,
29
+ collect: (monitor) => ({
30
+ isDragging: monitor.isDragging(),
31
+ }),
32
+ });
33
+
34
+ const [{ isOver, canDrop }, drop] = useDrop({
35
+ accept: 'PANEL',
36
+ hover: (dragItem, monitor) => {
37
+ // THIS FIXES BOTTOM → TOP
38
+ autoScrollWindow(monitor);
39
+
40
+ if (dragItem.index === index) return;
41
+
42
+ if (dragItem.level === level && dragItem.parentId === parentId) {
43
+ movePanel(dragItem.index, index);
44
+ dragItem.index = index; // keep in sync
45
+ }
46
+ },
24
47
 
25
- if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) return;
26
- if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) return;
48
+ canDrop: (item) => dragEnabled,
49
+ drop: (dragItem, monitor) => {
50
+ if (monitor.didDrop()) return;
27
51
 
28
- movePanel(dragIndex, hoverIndex);
29
- item.index = hoverIndex;
52
+ if (dragItem.level !== level || dragItem.parentId !== parentId) {
53
+ onCrossLevelMove?.(dragItem, {
54
+ targetLevel: level,
55
+ targetParentId: parentId,
56
+ targetIndex: index,
57
+ });
58
+ }
30
59
  },
60
+ collect: (monitor) => ({
61
+ isOver: monitor.isOver({ shallow: true }),
62
+ canDrop: monitor.canDrop(),
63
+ }),
31
64
  });
32
65
 
33
- const [{ isDragging }, drag] = useDrag({
34
- type: ItemTypes.PANEL,
35
- item: { id, index },
36
- canDrag: dragEnabled, // only allow dragging if dragEnabled
66
+ // Drop zone for making items children of this item
67
+ const [{ isOverChild, canDropChild }, dropChild] = useDrop({
68
+ accept: 'PANEL',
69
+ canDrop: (item) => dragEnabled && item.id !== id && canAcceptChildren,
70
+ drop: (item, monitor) => {
71
+ // if (monitor.didDrop()) return;
72
+
73
+ if (onCrossLevelMove && item.id !== id) {
74
+ // Drop as child of this item
75
+ onCrossLevelMove(item, { targetLevel: level + 1, targetParentId: id, targetIndex: 0 });
76
+ }
77
+ },
37
78
  collect: (monitor) => ({
38
- isDragging: monitor.isDragging(),
79
+ isOverChild: monitor.isOver({ shallow: true }),
80
+ canDropChild: monitor.canDrop(),
39
81
  }),
40
82
  });
41
83
 
42
- drag(drop(ref));
84
+ const backgroundColor = isOver && canDrop ? '#bae7ff' : isDragging ? '#ffff' : 'transparent';
85
+ const childZoneBackgroundColor = isOverChild && canDropChild ? '#d4f4dd' : 'transparent';
43
86
 
44
87
  return (
45
- <div
46
- ref={ref}
47
- style={{
48
- display: "flex",
49
- width: "100%",
50
- cursor: dragEnabled ? "grab" : "default", // show cursor only if draggable
51
- opacity: isDragging ? 0.6 : 1,
52
- transition: "transform 0.2s ease, opacity 0.2s ease",
53
- transform: isDragging ? "scale(1.05)" : "scale(1)",
54
- boxShadow: isDragging ? "0px 5px 10px rgba(0,0,0,0.15)" : "none",
55
- padding: "5px 0",
56
- }}
57
- >
58
- {title}
88
+ <div style={{ width: '100%', display: 'flex' }}>
89
+ {/* HEADER DROP — reorder only */}
90
+ <div ref={drop}>
91
+ <div
92
+ ref={drag}
93
+ style={{
94
+ opacity: isDragging ? 0.5 : 1,
95
+ cursor: dragEnabled ? 'move' : 'default',
96
+ backgroundColor,
97
+ border: isOver && canDrop ? '2px dashed #1890ff' : '2px solid transparent',
98
+ display: 'flex',
99
+ justifyContent: 'space-between',
100
+ alignItems: 'center',
101
+ }}
102
+ >
103
+ <div style={{ flex: 1 }}>
104
+ {dragEnabled && <span style={{ marginRight: 8 }}>⋮⋮</span>}
105
+ <span>{item.name}</span>
106
+ {dragEnabled ? (
107
+ <span style={{ marginLeft: 8, fontSize: 11, color: '#999' }}>(Level {level})</span>
108
+ ) : (
109
+ <span style={{ marginLeft: 8, fontSize: 11, color: '#999' }}>{item.path}</span>
110
+ )}
111
+ </div>
112
+ </div>
113
+ </div>
114
+
115
+ {/* CHILD DROP — nesting */}
116
+ {canAcceptChildren && dragEnabled && (
117
+ <div
118
+ ref={dropChild}
119
+ style={{
120
+ marginLeft: 8,
121
+ // flex:1,
122
+ // marginTop: 4,
123
+ padding: '4px 12px',
124
+ backgroundColor: isOverChild && canDropChild ? '#d4f4dd' : 'transparent',
125
+ border: isOverChild && canDropChild ? '2px solid #52c41a' : '1px dashed #d9d9d9',
126
+ borderRadius: 4,
127
+ fontSize: 11,
128
+ color: isOverChild && canDropChild ? '#52c41a' : '#999',
129
+ }}
130
+ >
131
+ {isOverChild && canDropChild ? '✓ Drop as child' : '↳ Drop here'}
132
+ </div>
133
+ )}
59
134
  </div>
60
135
  );
61
136
  }
@@ -0,0 +1,114 @@
1
+ /**
2
+ * MenuTree Component
3
+ *
4
+ * @description
5
+ * Renders a hierarchical menu structure with optional checkboxes.
6
+ * Supports:
7
+ * - Recursive menu rendering
8
+ * - Parent–child checkbox synchronization
9
+ * - Indeterminate state for partially selected parents
10
+ *
11
+ * Common use cases:
12
+ * - Role-based menu preview
13
+ * - Permission assignment trees
14
+ *
15
+ * @param {Object} props
16
+ * @param {Array<Object>} props.menus - Menu tree data
17
+ * @param {Array<number>} props.selectedMenus - Selected menu IDs
18
+ * @param {(menuId:number, checked:boolean) => void} props.toggleMenu - Callback for menu selection
19
+ * @param {number|null} props.parentId - Parent menu ID (used internally for recursion)
20
+ * @param {boolean} props.showCheckbox - Whether to display checkboxes
21
+ *
22
+ * @returns {JSX.Element}
23
+ */
24
+
25
+ import React from 'react';
26
+ import { Checkbox, Collapse } from 'antd';
27
+
28
+ const { Panel } = Collapse;
29
+
30
+ export const MenuTree = ({ menus, selectedMenus = [], toggleMenu, parentId = null, showCheckbox = true }) => {
31
+ const renderTree = (menuList, parentId = null) => {
32
+ return menuList.map((menu) => {
33
+ const children = menu.sub_menus || [];
34
+
35
+ /**
36
+ * Toggle menu selection recursively for all child menus
37
+ *
38
+ * @param {Object} m - Menu object
39
+ * @param {boolean} checked - Checkbox state
40
+ */
41
+ const toggleMenuRecursive = (m, checked) => {
42
+ toggleMenu && toggleMenu(m.id, checked);
43
+ if (m.sub_menus && m.sub_menus.length > 0) {
44
+ m.sub_menus.forEach((c) => toggleMenuRecursive(c, checked));
45
+ }
46
+ };
47
+
48
+ /**
49
+ * Handle parent checkbox change
50
+ * - Updates parent menu
51
+ * - Cascades selection to all children
52
+ *
53
+ * @param {boolean} checked
54
+ */
55
+
56
+ const onParentChange = (checked) => {
57
+ toggleMenu && toggleMenu(menu.id, checked);
58
+ children.forEach((c) => toggleMenuRecursive(c, checked));
59
+ };
60
+
61
+ /**
62
+ * Leaf menu (no sub-menus)
63
+ */
64
+
65
+ if (children.length === 0) {
66
+ return (
67
+ <div
68
+ key={menu.id}
69
+ style={{
70
+ border: '1px solid rgba(198, 195, 195, 0.85)',
71
+ padding: '12px 16px',
72
+ marginBottom: 6,
73
+ background: '#fff',
74
+ display: 'flex',
75
+ alignItems: 'center',
76
+ gap: 8,
77
+ }}
78
+ >
79
+ {showCheckbox && <Checkbox checked={selectedMenus.includes(menu.id)} onChange={(e) => onParentChange(e.target.checked)} />}
80
+ <span>{menu.title || menu.caption}</span>
81
+ </div>
82
+ );
83
+ }
84
+
85
+ /**
86
+ * Parent menu (with sub-menus)
87
+ */
88
+
89
+ return (
90
+ <Collapse key={menu.id} style={{ marginBottom: 6 }}>
91
+ <Panel
92
+ key={menu.id}
93
+ header={
94
+ <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
95
+ {showCheckbox && (
96
+ <Checkbox
97
+ checked={children.every((c) => selectedMenus.includes(c.id))}
98
+ indeterminate={children.some((c) => selectedMenus.includes(c.id)) && !children.every((c) => selectedMenus.includes(c.id))}
99
+ onChange={(e) => onParentChange(e.target.checked)}
100
+ />
101
+ )}
102
+ <span>{menu.title || menu.caption}</span>
103
+ </div>
104
+ }
105
+ >
106
+ <div style={{ paddingLeft: 20 }}>{renderTree(children, menu.id)}</div>
107
+ </Panel>
108
+ </Collapse>
109
+ );
110
+ });
111
+ };
112
+
113
+ return <>{renderTree(menus, parentId)}</>;
114
+ };
@@ -65,4 +65,4 @@ Switch.propTypes = {
65
65
  className: PropTypes.string,
66
66
  /** A custom style object for the switch component. */
67
67
  style: PropTypes.object,
68
- };
68
+ };
@@ -107,13 +107,14 @@ const MenuAdd = ({ model, callback, edit, history, formContent, match, additiona
107
107
  * Submit values
108
108
  */
109
109
  const onSubmit = (values) => {
110
- // console.log(values);
111
110
  setLoading(true);
112
111
 
113
112
  let id = formContent.id;
114
113
 
115
- // Add the step to form content
116
- // values.step = step;
114
+ // ONLY set step if it's NOT already provided
115
+ if (!values.step) {
116
+ values.step = formContent.step || step;
117
+ }
117
118
 
118
119
  if (values.attributes && typeof values === 'object') {
119
120
  values = {
@@ -123,30 +124,29 @@ const MenuAdd = ({ model, callback, edit, history, formContent, match, additiona
123
124
  }
124
125
 
125
126
  if (id) {
126
- // Update of model
127
127
  model.update({ id, values }).then(() => {
128
- // callback();
129
128
  message.success('Menu Updated');
130
-
131
129
  setLoading(false);
132
-
133
130
  callback();
134
131
  });
135
132
  } else {
136
- values.step = step;
137
-
138
- // Append the additional queries to the object
139
133
  additional_queries.forEach(({ field, value }) => {
140
134
  values[field] = value;
141
135
  });
142
136
 
143
- // add new model
144
- model.add({ values }).then(() => {
145
- // callback();
146
- message.success('Menu Added');
137
+ MenusAPI.createMenu({ formBody: values }).then((res) => {
138
+ const apiMessage = res?.result?.message;
147
139
 
148
- setLoading(false);
140
+ // (duplicate)
141
+ if (apiMessage?.toLowerCase().includes('already exists')) {
142
+ message.warning(apiMessage);
143
+ setLoading(false);
144
+ return; // stop further execution
145
+ }
149
146
 
147
+ // Real success
148
+ message.success('Menu Added');
149
+ setLoading(false);
150
150
  callback();
151
151
  });
152
152
  }
@@ -154,7 +154,7 @@ const MenuAdd = ({ model, callback, edit, history, formContent, match, additiona
154
154
 
155
155
  return (
156
156
  <section className="collection-add menu-add">
157
- <Title level={4}>{mode} Menu</Title>
157
+ {/* <Title level={4}>{mode} Menu</Title> */}
158
158
 
159
159
  {loading ? (
160
160
  <Skeleton />
@@ -163,21 +163,21 @@ const MenuAdd = ({ model, callback, edit, history, formContent, match, additiona
163
163
  <div className="form-container">
164
164
  <div className="left-container">
165
165
  {/* Caption */}
166
- <Form.Item name={'caption'} label="Caption" required>
166
+ <Form.Item name={'caption'} label="Caption" rules={[{ required: true, message: 'Caption is required' }]}>
167
167
  <Input placeholder="Enter caption" />
168
168
  </Form.Item>
169
169
  {/* Caption Ends */}
170
170
 
171
171
  {/* Name */}
172
- <Form.Item name={'name'} label="Name" required>
172
+ <Form.Item name={'name'} label="Name" rules={[{ required: true, message: 'Name is required' }]}>
173
173
  <Input placeholder="Enter name" />
174
174
  </Form.Item>
175
175
  {/* Name Ends */}
176
176
 
177
177
  {/* Description */}
178
- <Form.Item name={'description'} label="Description">
179
- <TextArea placeholder="Enter Description" />
180
- </Form.Item>
178
+ {/* <Form.Item name={"description"} label="Description">
179
+ <TextArea placeholder="Enter Description" />
180
+ </Form.Item> */}
181
181
  {/* Description Ends */}
182
182
 
183
183
  {/* Model */}
@@ -229,27 +229,28 @@ const MenuAdd = ({ model, callback, edit, history, formContent, match, additiona
229
229
  {/* Pages Ends */}
230
230
 
231
231
  {/* Path */}
232
- <Form.Item name="path" label="Path" required>
232
+ <Form.Item name="path" label="Path" rules={[{ required: true, message: 'Path is required' }]}>
233
233
  <Input placeholder="Enter path" />
234
234
  </Form.Item>
235
235
  {/* Path Ends */}
236
236
 
237
237
  {/* Route */}
238
- <Form.Item name="route" label="Route" required>
238
+ <Form.Item name="route" label="Route" rules={[{ required: true, message: 'Route is required' }]}>
239
239
  <Input placeholder="Enter route" />
240
240
  </Form.Item>
241
241
  {/* Route Ends */}
242
242
 
243
243
  {/* Switch */}
244
- <Form.Item name="is_visible" label="Visible" required>
245
- <Switch defaultChecked={body.is_visible} />
244
+ <Form.Item name="is_visible" label="Visible" valuePropName="checked" required>
245
+ <Switch />
246
246
  </Form.Item>
247
+
247
248
  {/* Switch Ends */}
248
249
 
249
250
  {/* Step */}
250
- <Form.Item name={'order'} label="Order" required>
251
- <InputNumber placeholder="Enter order" />
252
- </Form.Item>
251
+ {/* <Form.Item name={"order"} label="Order" required>
252
+ <InputNumber placeholder="Enter order" />
253
+ </Form.Item> */}
253
254
  {/* Step Ends */}
254
255
 
255
256
  {/* Icon Name*/}