@topconsultnpm/sdkui-react 6.20.0-dev1.30 → 6.20.0-dev1.31
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/lib/components/NewComponents/ContextMenu/TMContextMenu.js +27 -9
- package/lib/components/NewComponents/ContextMenu/styles.d.ts +1 -0
- package/lib/components/NewComponents/ContextMenu/styles.js +15 -20
- package/lib/components/NewComponents/ContextMenu/types.d.ts +9 -0
- package/lib/components/NewComponents/FloatingMenuBar/TMFloatingMenuBar.js +7 -27
- package/lib/components/NewComponents/FloatingMenuBar/styles.js +0 -1
- package/lib/components/NewComponents/FloatingMenuBar/types.d.ts +0 -1
- package/lib/components/base/TMCustomButton.js +20 -12
- package/lib/components/base/TMDataGrid.d.ts +3 -0
- package/lib/components/base/TMDataGrid.js +85 -5
- package/lib/components/features/search/TMSearchResult.js +10 -24
- package/lib/components/features/search/TMSearchResultsMenuItems.d.ts +2 -2
- package/lib/components/features/search/TMSearchResultsMenuItems.js +139 -139
- package/lib/helper/SDKUI_Globals.d.ts +0 -1
- package/lib/helper/SDKUI_Globals.js +0 -1
- package/package.json +1 -1
|
@@ -2,7 +2,8 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
2
2
|
import { useState, useRef, useEffect } from 'react';
|
|
3
3
|
import * as S from './styles';
|
|
4
4
|
import { useIsMobile, useMenuPosition } from './hooks';
|
|
5
|
-
|
|
5
|
+
import { IconArrowLeft } from '../../../helper';
|
|
6
|
+
const TMContextMenu = ({ items, trigger = 'right', children, externalControl }) => {
|
|
6
7
|
const [menuState, setMenuState] = useState({
|
|
7
8
|
visible: false,
|
|
8
9
|
position: { x: 0, y: 0 },
|
|
@@ -16,14 +17,31 @@ const TMContextMenu = ({ items, trigger = 'right', children }) => {
|
|
|
16
17
|
const submenuTimeoutRef = useRef(null);
|
|
17
18
|
const { openLeft, openUp, isCalculated } = useMenuPosition(menuRef, menuState.position);
|
|
18
19
|
const handleClose = () => {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
if (externalControl) {
|
|
21
|
+
externalControl.onClose();
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
setMenuState(prev => ({
|
|
25
|
+
...prev,
|
|
26
|
+
visible: false,
|
|
27
|
+
submenuStack: [items],
|
|
28
|
+
parentNames: [],
|
|
29
|
+
}));
|
|
30
|
+
}
|
|
25
31
|
setHoveredSubmenus([]);
|
|
26
32
|
};
|
|
33
|
+
// Sync with external control when provided
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
if (externalControl) {
|
|
36
|
+
setMenuState(prev => ({
|
|
37
|
+
...prev,
|
|
38
|
+
visible: externalControl.visible,
|
|
39
|
+
position: externalControl.position,
|
|
40
|
+
submenuStack: [items],
|
|
41
|
+
parentNames: [],
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
44
|
+
}, [externalControl, items]);
|
|
27
45
|
useEffect(() => {
|
|
28
46
|
if (!menuState.visible)
|
|
29
47
|
return;
|
|
@@ -178,10 +196,10 @@ const TMContextMenu = ({ items, trigger = 'right', children }) => {
|
|
|
178
196
|
};
|
|
179
197
|
const currentMenu = menuState.submenuStack.at(-1) || items;
|
|
180
198
|
const currentParentName = menuState.parentNames.at(-1) || '';
|
|
181
|
-
return (_jsxs(_Fragment, { children: [_jsx("div", { ref: triggerRef, onContextMenu: handleContextMenu, onClick: handleClick, onKeyDown: (e) => {
|
|
199
|
+
return (_jsxs(_Fragment, { children: [!externalControl && children && (_jsx("div", { ref: triggerRef, onContextMenu: handleContextMenu, onClick: handleClick, onKeyDown: (e) => {
|
|
182
200
|
if (e.key === 'Enter' || e.key === ' ') {
|
|
183
201
|
handleClick(e);
|
|
184
202
|
}
|
|
185
|
-
}, role: "button", tabIndex: 0, style: { display: 'inline-block' }, children: children }), menuState.visible && (_jsxs(_Fragment, { children: [_jsx(S.Overlay, { onClick: handleClose }), _jsxs(S.MenuContainer, { ref: menuRef, "$x": menuState.position.x, "$y": menuState.position.y, "$openLeft": openLeft, "$openUp": openUp, "$isPositioned": isCalculated, children: [isMobile && menuState.parentNames.length > 0 && (_jsxs(S.MobileMenuHeader, { children: [_jsx(S.BackButton, { onClick: handleBack, "aria-label": "Go back", children:
|
|
203
|
+
}, role: "button", tabIndex: 0, style: { display: 'inline-block' }, children: children })), menuState.visible && (_jsxs(_Fragment, { children: [_jsx(S.Overlay, { onClick: handleClose }), _jsxs(S.MenuContainer, { ref: menuRef, "$x": menuState.position.x, "$y": menuState.position.y, "$openLeft": openLeft, "$openUp": openUp, "$isPositioned": isCalculated, "$externalControl": !!externalControl, children: [isMobile && menuState.parentNames.length > 0 && (_jsxs(S.MobileMenuHeader, { children: [_jsx(S.BackButton, { onClick: handleBack, "aria-label": "Go back", children: _jsx(IconArrowLeft, {}) }), _jsx(S.HeaderTitle, { children: currentParentName })] })), renderMenuItems(currentMenu, 0)] }), !isMobile && hoveredSubmenus.map((submenu, idx) => (_jsx(S.Submenu, { "$parentRect": submenu.parentRect, "$openUp": submenu.openUp, "data-submenu": "true", onMouseEnter: handleSubmenuMouseEnter, onMouseLeave: () => handleMouseLeave(submenu.depth), children: renderMenuItems(submenu.items, submenu.depth) }, `submenu-${submenu.depth}-${idx}`)))] }))] }));
|
|
186
204
|
};
|
|
187
205
|
export default TMContextMenu;
|
|
@@ -4,6 +4,7 @@ export declare const MenuContainer: import("styled-components/dist/types").IStyl
|
|
|
4
4
|
$openLeft: boolean;
|
|
5
5
|
$openUp: boolean;
|
|
6
6
|
$isPositioned: boolean;
|
|
7
|
+
$externalControl?: boolean;
|
|
7
8
|
}>> & string;
|
|
8
9
|
export declare const MenuItem: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components/dist/types").Substitute<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, {
|
|
9
10
|
$disabled?: boolean;
|
|
@@ -54,6 +54,16 @@ export const MenuContainer = styled.div `
|
|
|
54
54
|
[data-theme='dark'] & *:not(svg):not(.right-icon-btn):not(.right-icon-btn *) {
|
|
55
55
|
color: #e0e0e0 !important;
|
|
56
56
|
}
|
|
57
|
+
|
|
58
|
+
${props => props.$externalControl && `
|
|
59
|
+
@media (max-width: 768px) {
|
|
60
|
+
left: 100px !important;
|
|
61
|
+
right: 100px !important;
|
|
62
|
+
max-width: calc(100vw - 200px);
|
|
63
|
+
width: auto;
|
|
64
|
+
min-width: auto;
|
|
65
|
+
}
|
|
66
|
+
`}
|
|
57
67
|
`;
|
|
58
68
|
export const MenuItem = styled.div `
|
|
59
69
|
display: flex;
|
|
@@ -123,8 +133,8 @@ export const MenuItem = styled.div `
|
|
|
123
133
|
}
|
|
124
134
|
|
|
125
135
|
@media (max-width: 768px) {
|
|
126
|
-
padding:
|
|
127
|
-
font-size:
|
|
136
|
+
padding: 4px 10px;
|
|
137
|
+
font-size: 12px;
|
|
128
138
|
}
|
|
129
139
|
`;
|
|
130
140
|
export const MenuItemContent = styled.div `
|
|
@@ -266,7 +276,7 @@ export const Submenu = styled.div `
|
|
|
266
276
|
export const MobileMenuHeader = styled.div `
|
|
267
277
|
display: flex;
|
|
268
278
|
align-items: center;
|
|
269
|
-
padding:
|
|
279
|
+
padding: 4px 8px;
|
|
270
280
|
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
|
|
271
281
|
margin-bottom: 8px;
|
|
272
282
|
gap: 12px;
|
|
@@ -282,33 +292,18 @@ export const BackButton = styled.button `
|
|
|
282
292
|
display: flex;
|
|
283
293
|
align-items: center;
|
|
284
294
|
justify-content: center;
|
|
285
|
-
background: #0066cc;
|
|
286
|
-
color: white;
|
|
287
295
|
border: none;
|
|
288
296
|
border-radius: 8px;
|
|
289
297
|
width: 32px;
|
|
290
298
|
height: 32px;
|
|
291
299
|
cursor: pointer;
|
|
292
|
-
font-size: 18px;
|
|
293
300
|
transition: all 0.15s ease;
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
background: #0052a3;
|
|
297
|
-
transform: scale(1.05);
|
|
298
|
-
}
|
|
301
|
+
font-size: 16px;
|
|
302
|
+
transform: translateY(-2px);
|
|
299
303
|
|
|
300
304
|
&:active {
|
|
301
305
|
transform: scale(0.95);
|
|
302
306
|
}
|
|
303
|
-
|
|
304
|
-
[data-theme='dark'] & {
|
|
305
|
-
background: #4db8ff;
|
|
306
|
-
color: #1a1a1a;
|
|
307
|
-
|
|
308
|
-
&:hover {
|
|
309
|
-
background: #66c2ff;
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
307
|
`;
|
|
313
308
|
export const HeaderTitle = styled.h3 `
|
|
314
309
|
margin: 0;
|
|
@@ -9,11 +9,20 @@ export interface TMContextMenuItemProps {
|
|
|
9
9
|
onRightIconClick?: () => void;
|
|
10
10
|
beginGroup?: boolean;
|
|
11
11
|
tooltip?: string;
|
|
12
|
+
operationType?: 'singleRow' | 'multiRow';
|
|
12
13
|
}
|
|
13
14
|
export interface TMContextMenuProps {
|
|
14
15
|
items: TMContextMenuItemProps[];
|
|
15
16
|
trigger?: 'right' | 'left';
|
|
16
17
|
children?: React.ReactNode;
|
|
18
|
+
externalControl?: {
|
|
19
|
+
visible: boolean;
|
|
20
|
+
position: {
|
|
21
|
+
x: number;
|
|
22
|
+
y: number;
|
|
23
|
+
};
|
|
24
|
+
onClose: () => void;
|
|
25
|
+
};
|
|
17
26
|
}
|
|
18
27
|
export interface Position {
|
|
19
28
|
x: number;
|
|
@@ -6,8 +6,7 @@ import TMTooltip from '../../base/TMTooltip';
|
|
|
6
6
|
import * as S from './styles';
|
|
7
7
|
import { IconApply, IconMenuKebab, IconMenuVertical, IconPencil, IconPin, SDKUI_Globals } from '../../../helper';
|
|
8
8
|
const IconDraggableDots = (props) => (_jsx("svg", { fontSize: 18, viewBox: "0 0 24 24", fill: "currentColor", height: "1em", width: "1em", ...props, children: _jsx("path", { d: "M9 3a2 2 0 11-4 0 2 2 0 014 0zm0 9a2 2 0 11-4 0 2 2 0 014 0zm0 9a2 2 0 11-4 0 2 2 0 014 0zm10-18a2 2 0 11-4 0 2 2 0 014 0zm0 9a2 2 0 11-4 0 2 2 0 014 0zm0 9a2 2 0 11-4 0 2 2 0 014 0z" }) }));
|
|
9
|
-
const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [],
|
|
10
|
-
isConstrained = false, defaultPosition = { x: 100, y: 100 }, maxItems = 8, }) => {
|
|
9
|
+
const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], isConstrained = false, defaultPosition = { x: 100, y: 100 }, maxItems = 8, }) => {
|
|
11
10
|
const loadConfig = () => {
|
|
12
11
|
try {
|
|
13
12
|
const settings = SDKUI_Globals.userSettings.searchSettings.floatingMenuBar;
|
|
@@ -17,7 +16,6 @@ isConstrained = false, defaultPosition = { x: 100, y: 100 }, maxItems = 8, }) =>
|
|
|
17
16
|
(settings.itemIds && settings.itemIds.length > 0));
|
|
18
17
|
return {
|
|
19
18
|
orientation: settings.orientation || 'horizontal',
|
|
20
|
-
pinnedItemIds: new Set(settings.pinnedItemIds || []),
|
|
21
19
|
savedItemIds: settings.itemIds || [],
|
|
22
20
|
position: hasSavedPosition ? settings.position : defaultPosition,
|
|
23
21
|
};
|
|
@@ -26,7 +24,6 @@ isConstrained = false, defaultPosition = { x: 100, y: 100 }, maxItems = 8, }) =>
|
|
|
26
24
|
console.error('Failed to load FloatingMenuBar config:', error);
|
|
27
25
|
return {
|
|
28
26
|
orientation: 'horizontal',
|
|
29
|
-
pinnedItemIds: new Set(),
|
|
30
27
|
savedItemIds: [],
|
|
31
28
|
position: defaultPosition,
|
|
32
29
|
};
|
|
@@ -43,7 +40,6 @@ isConstrained = false, defaultPosition = { x: 100, y: 100 }, maxItems = 8, }) =>
|
|
|
43
40
|
});
|
|
44
41
|
const floatingRef = useRef(null);
|
|
45
42
|
const dragOffset = useRef({ x: 0, y: 0 });
|
|
46
|
-
const [pinnedItemIds, setPinnedItemIds] = useState(initialConfig.pinnedItemIds);
|
|
47
43
|
const [dragOverIndex, setDragOverIndex] = useState(null);
|
|
48
44
|
// Use refs to track item IDs without causing re-renders
|
|
49
45
|
const floatingBarItemIds = useRef(new Set());
|
|
@@ -60,13 +56,15 @@ isConstrained = false, defaultPosition = { x: 100, y: 100 }, maxItems = 8, }) =>
|
|
|
60
56
|
const itemId = `${parentPath}${item.name}-${index}`;
|
|
61
57
|
// Only add items that have onClick (final actions, not submenu parents)
|
|
62
58
|
if (item.onClick && !item.submenu) {
|
|
59
|
+
// Check if item is currently in the floating bar
|
|
60
|
+
const isPinned = state.items.some(i => i.id === itemId || i.name === item.name);
|
|
63
61
|
result.push({
|
|
64
62
|
id: itemId,
|
|
65
63
|
name: item.name,
|
|
66
64
|
icon: item.icon || _jsx(IconPin, {}),
|
|
67
65
|
onClick: item.onClick,
|
|
68
66
|
disabled: item.disabled,
|
|
69
|
-
isPinned:
|
|
67
|
+
isPinned: isPinned,
|
|
70
68
|
originalMenuItem: item,
|
|
71
69
|
});
|
|
72
70
|
}
|
|
@@ -76,7 +74,7 @@ isConstrained = false, defaultPosition = { x: 100, y: 100 }, maxItems = 8, }) =>
|
|
|
76
74
|
}
|
|
77
75
|
});
|
|
78
76
|
return result;
|
|
79
|
-
}, [
|
|
77
|
+
}, [state.items]);
|
|
80
78
|
// Restore items on mount from savedItemIds
|
|
81
79
|
useEffect(() => {
|
|
82
80
|
if (contextMenuItems.length > 0) {
|
|
@@ -112,17 +110,6 @@ isConstrained = false, defaultPosition = { x: 100, y: 100 }, maxItems = 8, }) =>
|
|
|
112
110
|
return { ...s, items: [...s.items, item] };
|
|
113
111
|
}
|
|
114
112
|
});
|
|
115
|
-
// Update pinned IDs for context menu items
|
|
116
|
-
setPinnedItemIds(prev => {
|
|
117
|
-
const newSet = new Set(prev);
|
|
118
|
-
if (newSet.has(item.id)) {
|
|
119
|
-
newSet.delete(item.id);
|
|
120
|
-
}
|
|
121
|
-
else {
|
|
122
|
-
newSet.add(item.id);
|
|
123
|
-
}
|
|
124
|
-
return newSet;
|
|
125
|
-
});
|
|
126
113
|
}, [maxItems]);
|
|
127
114
|
// Get current item state (disabled and onClick) from contextMenuItems
|
|
128
115
|
const getCurrentItemState = useCallback((itemName) => {
|
|
@@ -242,7 +229,6 @@ isConstrained = false, defaultPosition = { x: 100, y: 100 }, maxItems = 8, }) =>
|
|
|
242
229
|
// Replace the entire object to trigger the Proxy
|
|
243
230
|
SDKUI_Globals.userSettings.searchSettings.floatingMenuBar = {
|
|
244
231
|
orientation: state.orientation,
|
|
245
|
-
pinnedItemIds: Array.from(pinnedItemIds),
|
|
246
232
|
itemIds: state.items.map(item => item.id),
|
|
247
233
|
position: state.position,
|
|
248
234
|
};
|
|
@@ -250,7 +236,7 @@ isConstrained = false, defaultPosition = { x: 100, y: 100 }, maxItems = 8, }) =>
|
|
|
250
236
|
catch (error) {
|
|
251
237
|
console.error('Failed to save FloatingMenuBar config:', error);
|
|
252
238
|
}
|
|
253
|
-
}, [state.orientation, state.items, state.position
|
|
239
|
+
}, [state.orientation, state.items, state.position]);
|
|
254
240
|
const toggleConfigMode = () => {
|
|
255
241
|
setState(s => ({ ...s, isConfigMode: !s.isConfigMode }));
|
|
256
242
|
};
|
|
@@ -294,12 +280,6 @@ isConstrained = false, defaultPosition = { x: 100, y: 100 }, maxItems = 8, }) =>
|
|
|
294
280
|
...s,
|
|
295
281
|
items: s.items.filter(item => item.id !== itemId),
|
|
296
282
|
}));
|
|
297
|
-
// Also remove from pinned items if it was pinned
|
|
298
|
-
setPinnedItemIds(prev => {
|
|
299
|
-
const newSet = new Set(prev);
|
|
300
|
-
newSet.delete(itemId);
|
|
301
|
-
return newSet;
|
|
302
|
-
});
|
|
303
283
|
};
|
|
304
284
|
// Drag and drop for reordering
|
|
305
285
|
const handleDragStart = (e, index) => {
|
|
@@ -373,6 +353,6 @@ isConstrained = false, defaultPosition = { x: 100, y: 100 }, maxItems = 8, }) =>
|
|
|
373
353
|
currentOnClick();
|
|
374
354
|
}
|
|
375
355
|
}, disabled: isDisabled, children: item.icon }) })), state.isConfigMode && (_jsx(S.RemoveButton, { onClick: () => removeItem(item.id), children: "\u00D7" }))] }, item.id));
|
|
376
|
-
}), !state.isConfigMode && contextMenuItems.length > 0 && (_jsx(ContextMenu, { items: enhancedContextMenuItems(), trigger: "left", children: _jsx(S.ContextMenuButton, { children: _jsx(IconMenuVertical, {}) }) },
|
|
356
|
+
}), !state.isConfigMode && contextMenuItems.length > 0 && (_jsx(ContextMenu, { items: enhancedContextMenuItems(), trigger: "left", children: _jsx(S.ContextMenuButton, { children: _jsx(IconMenuVertical, {}) }) }, state.items.map(i => i.id).join(','))), _jsx(S.ConfigButton, { onClick: toggleConfigMode, "$isActive": state.isConfigMode, children: state.isConfigMode ? _jsx(IconApply, {}) : _jsx(IconPencil, {}) }), !state.isConfigMode && (_jsx(S.OrientationToggle, { "$orientation": state.orientation, onClick: toggleOrientation, children: _jsx(IconMenuKebab, {}) }))] })] }));
|
|
377
357
|
};
|
|
378
358
|
export default TMFloatingMenuBar;
|
|
@@ -11,7 +11,6 @@ export interface TMFloatingMenuItem {
|
|
|
11
11
|
export interface TMFloatingMenuBarProps {
|
|
12
12
|
containerRef: React.RefObject<HTMLElement | null>;
|
|
13
13
|
contextMenuItems?: TMContextMenuItemProps[];
|
|
14
|
-
storageKey?: string;
|
|
15
14
|
isConstrained?: boolean;
|
|
16
15
|
defaultPosition?: Position;
|
|
17
16
|
maxItems?: number;
|
|
@@ -52,12 +52,12 @@ const TMCustomButton = (props) => {
|
|
|
52
52
|
const RunOnce = button.mode === "RunOnce";
|
|
53
53
|
const [loading, setLoading] = useState(true);
|
|
54
54
|
const [error, setError] = useState(false);
|
|
55
|
-
const
|
|
55
|
+
const itemsToProcess = useMemo(() => selectedItems && selectedItems.length > 0 ? selectedItems : [attributes], [selectedItems, attributes]);
|
|
56
56
|
// Stati per il wait panel
|
|
57
|
-
const [showWaitPanel, setShowWaitPanel] = useState(
|
|
58
|
-
const [waitPanelText, setWaitPanelText] = useState(SDKUI_Localizator.CustomButtonActions.replaceParams(1,
|
|
57
|
+
const [showWaitPanel, setShowWaitPanel] = useState(itemsToProcess.length > 0 && !RunOnce);
|
|
58
|
+
const [waitPanelText, setWaitPanelText] = useState(SDKUI_Localizator.CustomButtonActions.replaceParams(1, itemsToProcess.length));
|
|
59
59
|
const [waitPanelValue, setWaitPanelValue] = useState(0);
|
|
60
|
-
const [waitPanelMaxValue, setWaitPanelMaxValue] = useState(
|
|
60
|
+
const [waitPanelMaxValue, setWaitPanelMaxValue] = useState(itemsToProcess.length);
|
|
61
61
|
const [abortController, setAbortController] = useState(undefined);
|
|
62
62
|
// Aggiungi timestamp all'URL per evitare cache
|
|
63
63
|
const iframeUrl = useMemo(() => {
|
|
@@ -84,21 +84,29 @@ const TMCustomButton = (props) => {
|
|
|
84
84
|
setError(true);
|
|
85
85
|
};
|
|
86
86
|
const executeSequentially = async (controller) => {
|
|
87
|
-
if (!
|
|
87
|
+
if (!itemsToProcess)
|
|
88
88
|
return;
|
|
89
|
-
for (const [index, item] of
|
|
89
|
+
for (const [index, item] of itemsToProcess.entries()) {
|
|
90
90
|
if (controller.signal.aborted)
|
|
91
91
|
break;
|
|
92
|
-
setWaitPanelText(SDKUI_Localizator.CustomButtonActions.replaceParams(index + 1,
|
|
92
|
+
setWaitPanelText(SDKUI_Localizator.CustomButtonActions.replaceParams(index + 1, itemsToProcess.length));
|
|
93
93
|
setWaitPanelValue(index);
|
|
94
94
|
// Attendi che l'iframe sia pronto e invia il messaggio
|
|
95
95
|
await new Promise((resolve) => {
|
|
96
96
|
const checkIframe = setInterval(() => {
|
|
97
97
|
if (iframeRef.current?.contentWindow) {
|
|
98
98
|
clearInterval(checkIframe);
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
99
|
+
if (selectedItems && selectedItems.length > 0) {
|
|
100
|
+
//devo convertire item in formData
|
|
101
|
+
const processedItem = getSelectedItem(args, formData, item);
|
|
102
|
+
postMessageIframe(processedItem);
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
postMessageIframe(item);
|
|
106
|
+
}
|
|
107
|
+
//imposta 100% se sono all'ultimo item
|
|
108
|
+
if (index === itemsToProcess.length - 1)
|
|
109
|
+
setWaitPanelValue(index + 1);
|
|
102
110
|
// Attendi prima di passare al prossimo
|
|
103
111
|
setTimeout(() => {
|
|
104
112
|
setWaitPanelValue(index + 1);
|
|
@@ -120,7 +128,7 @@ const TMCustomButton = (props) => {
|
|
|
120
128
|
useEffect(() => {
|
|
121
129
|
if (loading || error)
|
|
122
130
|
return;
|
|
123
|
-
if (!RunOnce &&
|
|
131
|
+
if (!RunOnce && itemsToProcess.length > 0) {
|
|
124
132
|
// esegui per ogni item selezionato
|
|
125
133
|
const controller = new AbortController();
|
|
126
134
|
controller.signal.addEventListener('abort', () => {
|
|
@@ -128,7 +136,7 @@ const TMCustomButton = (props) => {
|
|
|
128
136
|
onClose?.();
|
|
129
137
|
});
|
|
130
138
|
setAbortController(controller);
|
|
131
|
-
setWaitPanelMaxValue(
|
|
139
|
+
setWaitPanelMaxValue(itemsToProcess.length);
|
|
132
140
|
executeSequentially(controller);
|
|
133
141
|
}
|
|
134
142
|
else {
|
|
@@ -2,6 +2,7 @@ import React from 'react';
|
|
|
2
2
|
import { IColumnProps, IDataGridOptions, IMasterDetailProps } from 'devextreme-react/data-grid';
|
|
3
3
|
import dxDataGrid from 'devextreme/ui/data_grid';
|
|
4
4
|
import { ITMCounterContainerProps } from './TMCounterContainer';
|
|
5
|
+
import { TMContextMenuItemProps } from '../NewComponents/ContextMenu/types';
|
|
5
6
|
export interface TMDataGridContextMenuItem {
|
|
6
7
|
text: string;
|
|
7
8
|
icon: string;
|
|
@@ -51,6 +52,8 @@ export interface TMDataGridProps<T> extends IDataGridOptions {
|
|
|
51
52
|
masterDetail?: IMasterDetailProps;
|
|
52
53
|
/** On Has Filters Change */
|
|
53
54
|
onHasFiltersChange?: (hasFilters: boolean) => void;
|
|
55
|
+
/** Custom context menu items - when provided, replaces DevExtreme's native context menu with TMContextMenu */
|
|
56
|
+
customContextMenuItems?: TMContextMenuItemProps[];
|
|
54
57
|
}
|
|
55
58
|
declare const TMDataGrid: React.ForwardRefExoticComponent<TMDataGridProps<unknown> & React.RefAttributes<dxDataGrid<any, any>>>;
|
|
56
59
|
export default TMDataGrid;
|
|
@@ -4,6 +4,7 @@ import DataGrid, { Column, HeaderFilter, Selection, Scrolling, LoadPanel, Search
|
|
|
4
4
|
import DataSource from 'devextreme/data/data_source';
|
|
5
5
|
import { IconAll, IconSelected, IconVisible, SDKUI_Globals, SDKUI_Localizator } from '../../helper';
|
|
6
6
|
import TMCounterContainer, { CounterItemKey } from './TMCounterContainer';
|
|
7
|
+
import TMContextMenu from '../NewComponents/ContextMenu/TMContextMenu';
|
|
7
8
|
;
|
|
8
9
|
export var TMDataGridPageSize;
|
|
9
10
|
(function (TMDataGridPageSize) {
|
|
@@ -16,7 +17,7 @@ const TMDataGrid = React.forwardRef((props, ref) => {
|
|
|
16
17
|
// main properties
|
|
17
18
|
keyExpr = 'id', dataSource, focusedRowEnabled = true, hoverStateEnabled = true, focusedRowKey, selectedRowKeys = [],
|
|
18
19
|
// custom options
|
|
19
|
-
dataColumns = [], pageSize = TMDataGridPageSize.Large, showHeaderFilter = true, showFilterPanel = true, showHeaderColumnChooser = false, showLoadPanel = true, showSearchPanel = true, searchPanelToolbarPosition = 'before', searchPanelFocusStarting = false, counterConfig = { show: false, items: new Map() }, onHasFiltersChange,
|
|
20
|
+
dataColumns = [], pageSize = TMDataGridPageSize.Large, showHeaderFilter = true, showFilterPanel = true, showHeaderColumnChooser = false, showLoadPanel = true, showSearchPanel = true, searchPanelToolbarPosition = 'before', searchPanelFocusStarting = false, counterConfig = { show: false, items: new Map() }, onHasFiltersChange, customContextMenuItems,
|
|
20
21
|
// events and callbacks
|
|
21
22
|
onSelectionChanged, onFocusedRowChanged, onRowDblClick, onRowClick, onCellClick, onCellDblClick, onOptionChanged, onContentReady, onContextMenuPreparing, onInitialized, onEditorPreparing, onCellPrepared, onRowPrepared, onRowUpdating, onRowExpanded, onRowCollapsed, onRowUpdated, onSaved, onEditCanceled, onEditingStart, onEditingChange, customizeColumns, onKeyDown, scrolling = { mode: 'standard', useNative: SDKUI_Globals.userSettings?.themeSettings.gridSettings.useNativeScrollbar === 1 }, paging = { enabled: true, pageSize: pageSize }, pager = { visible: true, showInfo: true, showNavigationButtons: true }, selection = { mode: 'multiple', showCheckBoxesMode: "always", selectAllMode: "allPages" }, sorting, summary, stateStoring, grouping, groupPanel, filterRow, headerFilter, editing, rowDragging, masterDetail,
|
|
22
23
|
// other properties
|
|
@@ -29,10 +30,42 @@ const TMDataGrid = React.forwardRef((props, ref) => {
|
|
|
29
30
|
const [totalRecordCount, setTotalRecordCount] = useState(0);
|
|
30
31
|
const [visibleItemsCount, setVisibleItemsCount] = useState(0);
|
|
31
32
|
const [hasFilters, setHasFilters] = useState(false);
|
|
33
|
+
// Custom context menu states
|
|
34
|
+
const [customContextMenuVisible, setCustomContextMenuVisible] = useState(false);
|
|
35
|
+
const [customContextMenuPosition, setCustomContextMenuPosition] = useState({ x: 0, y: 0 });
|
|
36
|
+
const [customContextMenuRowKey, setCustomContextMenuRowKey] = useState(undefined);
|
|
37
|
+
const gridContainerRef = useRef(null);
|
|
32
38
|
useEffect(() => {
|
|
33
39
|
const count = getRecordCount(dataSource);
|
|
34
40
|
setTotalRecordCount(count);
|
|
35
41
|
}, [dataSource]);
|
|
42
|
+
// Handle custom context menu (only when customContextMenuItems is provided)
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
if (!customContextMenuItems || !gridContainerRef.current)
|
|
45
|
+
return;
|
|
46
|
+
const gridContainer = gridContainerRef.current;
|
|
47
|
+
const handleContextMenu = (e) => {
|
|
48
|
+
e.preventDefault();
|
|
49
|
+
e.stopPropagation();
|
|
50
|
+
// Get the clicked row
|
|
51
|
+
const target = e.target;
|
|
52
|
+
const rowElement = target.closest('.dx-data-row');
|
|
53
|
+
if (rowElement && internalRef.current) {
|
|
54
|
+
const rowIndex = Array.from(rowElement.parentElement?.children || []).indexOf(rowElement);
|
|
55
|
+
const rowKey = internalRef.current.instance().getKeyByRowIndex(rowIndex);
|
|
56
|
+
// Change focused row
|
|
57
|
+
internalRef.current.instance().option('focusedRowKey', rowKey);
|
|
58
|
+
// Show custom context menu
|
|
59
|
+
setCustomContextMenuVisible(true);
|
|
60
|
+
setCustomContextMenuPosition({ x: e.clientX, y: e.clientY });
|
|
61
|
+
setCustomContextMenuRowKey(rowKey);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
gridContainer.addEventListener('contextmenu', handleContextMenu);
|
|
65
|
+
return () => {
|
|
66
|
+
gridContainer.removeEventListener('contextmenu', handleContextMenu);
|
|
67
|
+
};
|
|
68
|
+
}, [customContextMenuItems]);
|
|
36
69
|
// Creating a ref to store the timestamp of the last selection change
|
|
37
70
|
const lastSelectionChangeTime = useRef(Date.now());
|
|
38
71
|
useEffect(() => {
|
|
@@ -101,6 +134,8 @@ const TMDataGrid = React.forwardRef((props, ref) => {
|
|
|
101
134
|
}
|
|
102
135
|
return {
|
|
103
136
|
...item,
|
|
137
|
+
// Ensure icon is not null/undefined to prevent DevExtreme errors
|
|
138
|
+
icon: item.icon || '',
|
|
104
139
|
disabled: disabled || disabledCalculation, // An item is disabled if it's explicitly set to `true` or if the calculation above determines so
|
|
105
140
|
// Define the behavior when the menu item is clicked
|
|
106
141
|
onClick: () => {
|
|
@@ -132,10 +167,51 @@ const TMDataGrid = React.forwardRef((props, ref) => {
|
|
|
132
167
|
};
|
|
133
168
|
});
|
|
134
169
|
}, [focusedRowEnabled, focusedRowKey, selectedRowKeys]);
|
|
170
|
+
// Process custom context menu items (for TMContextMenuItemProps)
|
|
171
|
+
const processCustomContextMenuItems = useCallback((items, rowID) => {
|
|
172
|
+
return items.map(item => {
|
|
173
|
+
let disabled = item.disabled ?? false;
|
|
174
|
+
let disabledCalculation = false;
|
|
175
|
+
const id = focusedRowEnabled ? focusedRowKey : rowID;
|
|
176
|
+
if (item.operationType === 'singleRow') {
|
|
177
|
+
disabledCalculation = selectedRowKeys.length > 1 || id === undefined;
|
|
178
|
+
}
|
|
179
|
+
if (item.operationType === 'multiRow') {
|
|
180
|
+
disabledCalculation = selectedRowKeys.length === 0 && id === undefined;
|
|
181
|
+
}
|
|
182
|
+
const originalOnClick = item.onClick;
|
|
183
|
+
return {
|
|
184
|
+
...item,
|
|
185
|
+
disabled: disabled || disabledCalculation,
|
|
186
|
+
onClick: originalOnClick ? () => {
|
|
187
|
+
if (item.operationType === 'singleRow' && id !== undefined) {
|
|
188
|
+
originalOnClick(id);
|
|
189
|
+
}
|
|
190
|
+
else if (item.operationType === 'multiRow' && id !== undefined) {
|
|
191
|
+
if (selectedRowKeys.length > 0) {
|
|
192
|
+
originalOnClick(selectedRowKeys);
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
originalOnClick([id]);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
originalOnClick();
|
|
200
|
+
}
|
|
201
|
+
} : undefined,
|
|
202
|
+
submenu: item.submenu ? processCustomContextMenuItems(item.submenu, id) : undefined,
|
|
203
|
+
};
|
|
204
|
+
});
|
|
205
|
+
}, [focusedRowEnabled, focusedRowKey, selectedRowKeys]);
|
|
135
206
|
// Handle context menu preparation
|
|
136
207
|
const onContextMenuPreparingCallback = useCallback((e) => {
|
|
137
208
|
if (e === undefined)
|
|
138
209
|
return;
|
|
210
|
+
// If custom context menu is enabled, completely disable DevExtreme's native context menu
|
|
211
|
+
if (customContextMenuItems && e.target === 'content') {
|
|
212
|
+
e.items = undefined;
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
139
215
|
if (onContextMenuPreparing)
|
|
140
216
|
onContextMenuPreparing(e);
|
|
141
217
|
if (e.target === 'content') {
|
|
@@ -157,7 +233,7 @@ const TMDataGrid = React.forwardRef((props, ref) => {
|
|
|
157
233
|
}
|
|
158
234
|
});
|
|
159
235
|
}
|
|
160
|
-
}, [updateContextMenuItems, onContextMenuPreparing, showHeaderColumnChooser]);
|
|
236
|
+
}, [updateContextMenuItems, onContextMenuPreparing, showHeaderColumnChooser, customContextMenuItems]);
|
|
161
237
|
// Handle toolbar preparation, especially for the search panel
|
|
162
238
|
const onToolbarPreparingCallback = useCallback((e) => {
|
|
163
239
|
if (e === undefined || e.toolbarOptions === undefined || e.toolbarOptions.items === undefined)
|
|
@@ -226,15 +302,19 @@ const TMDataGrid = React.forwardRef((props, ref) => {
|
|
|
226
302
|
// Propaga l'evento originale
|
|
227
303
|
onOptionChanged?.(e);
|
|
228
304
|
}, [onOptionChanged, onHasFiltersChange]);
|
|
229
|
-
return _jsxs("div", { style: { width: "100%", height: "100%" }, children: [_jsx("div", { style: { width: "100%", height: counterConfig.show ? "calc(100% - 25px)" : "100%" }, children: _jsxs(DataGrid, { ref: internalRef, id: id, className: `tm-datagrid ${hasFilters ? 'has-filters' : ''}`,
|
|
305
|
+
return _jsxs("div", { style: { width: "100%", height: "100%" }, children: [_jsx("div", { ref: gridContainerRef, style: { width: "100%", height: counterConfig.show ? "calc(100% - 25px)" : "100%" }, children: _jsxs(DataGrid, { ref: internalRef, id: id, className: `tm-datagrid ${hasFilters ? 'has-filters' : ''}`,
|
|
230
306
|
// main properties
|
|
231
307
|
keyExpr: keyExpr, dataSource: dataSource, selectedRowKeys: selectedRowKeys, focusedRowEnabled: focusedRowEnabled, hoverStateEnabled: hoverStateEnabled,
|
|
232
308
|
// events and callbacks
|
|
233
|
-
onSelectionChanged: onSelectionChangedCallback, onRowDblClick: onRowDblClickCallback, onRowPrepared: onRowPrepared, onContextMenuPreparing: onContextMenuPreparingCallback, onToolbarPreparing: onToolbarPreparingCallback, onFocusedRowChanged: onFocusedRowChanged, onRowClick: onRowClick, onCellClick: onCellClick, onCellDblClick: onCellDblClick, onOptionChanged: onOptionChangedCallback, onContentReady: onContentReadyCallback, onInitialized: onInitialized, customizeColumns: customizeColumns, onEditorPreparing: onEditorPreparing, onCellPrepared: onCellPrepared, onRowUpdating: onRowUpdating, onRowExpanded: onRowExpanded, onRowCollapsed: onRowCollapsed, onRowUpdated: onRowUpdated, onSaved: onSaved, onEditCanceled: onEditCanceled, onEditingStart: onEditingStart, onEditingChange: onEditingChange, onKeyDown: onKeyDown,
|
|
309
|
+
onSelectionChanged: onSelectionChangedCallback, onRowDblClick: onRowDblClickCallback, onRowPrepared: onRowPrepared, onContextMenuPreparing: customContextMenuItems ? undefined : onContextMenuPreparingCallback, onToolbarPreparing: onToolbarPreparingCallback, onFocusedRowChanged: onFocusedRowChanged, onRowClick: onRowClick, onCellClick: onCellClick, onCellDblClick: onCellDblClick, onOptionChanged: onOptionChangedCallback, onContentReady: onContentReadyCallback, onInitialized: onInitialized, customizeColumns: customizeColumns, onEditorPreparing: onEditorPreparing, onCellPrepared: onCellPrepared, onRowUpdating: onRowUpdating, onRowExpanded: onRowExpanded, onRowCollapsed: onRowCollapsed, onRowUpdated: onRowUpdated, onSaved: onSaved, onEditCanceled: onEditCanceled, onEditingStart: onEditingStart, onEditingChange: onEditingChange, onKeyDown: onKeyDown,
|
|
234
310
|
// other properties
|
|
235
311
|
disabled: disabled, autoNavigateToFocusedRow: autoNavigateToFocusedRow, focusedRowKey: focusedRowKey, columnHidingEnabled: columnHidingEnabled, columnResizingMode: columnResizingMode, columnAutoWidth: columnAutoWidth, allowColumnResizing: allowColumnResizing, allowColumnReordering: allowColumnReordering, showBorders: showBorders, showRowLines: showRowLines, showColumnLines: showColumnLines, showColumnHeaders: showColumnHeaders, rowAlternationEnabled: rowAlternationEnabled, wordWrapEnabled: wordWrapEnabled, noDataText: noDataText,
|
|
236
312
|
// styles
|
|
237
|
-
width: width, height: height, style: { userSelect: 'none' }, children: [dataColumns.map((column, index) => (_jsx(Column, { ...column }, column.caption + index.toString()))), sorting && _jsx(Sorting, { ...sorting }), selection && _jsx(Selection, { ...selection }), scrolling && _jsx(Scrolling, { ...scrolling }), summary && _jsx(Summary, { ...summary }), showHeaderFilter && _jsx(HeaderFilter, { visible: true, ...headerFilter }), rowDragging && _jsx(RowDragging, { ...rowDragging }), filterRow && _jsx(FilterRow, { ...filterRow }), showFilterPanel && _jsx(FilterPanel, { visible: true }), showHeaderColumnChooser && _jsxs(ColumnChooser, { height: "400px", enabled: !showHeaderColumnChooser, mode: "select", children: [_jsx(Position, { my: "center", at: "center", of: window }), _jsx(ColumnChooserSearch, { enabled: true }), _jsx(ColumnChooserSelection, { allowSelectAll: false, selectByClick: true, recursive: true })] }), stateStoring && _jsx(StateStoring, { ...stateStoring }), groupPanel && _jsx(GroupPanel, { ...groupPanel }), _jsx(Grouping, { contextMenuEnabled: true, ...grouping }), _jsx(LoadPanel, { enabled: showLoadPanel }), _jsx(SearchPanel, { visible: showSearchPanel, searchVisibleColumnsOnly: true, highlightSearchText: true }), editing && _jsx(Editing, { ...editing }), paging && _jsx(Paging, { ...paging }), pager && _jsx(Pager, { ...pager, visible: totalRecordCount > pageSize }), masterDetail && _jsx(MasterDetail, { ...masterDetail })] }) }), counterConfig.show && _jsx("div", { style: { width: "100%", height: "25px", display: "flex", alignItems: "center", gap: "15px", backgroundColor: "#e0e0e0" }, children: _jsx(TMCounterContainer, { items: counterValues, bgColorContainer: counterConfig.bgColorContainer, bgColorItem: counterConfig.bgColorItem, hoverColorItem: counterConfig.hoverColorItem, textColorItem: counterConfig.textColorItem }) })
|
|
313
|
+
width: width, height: height, style: { userSelect: 'none' }, children: [dataColumns.map((column, index) => (_jsx(Column, { ...column }, column.caption + index.toString()))), sorting && _jsx(Sorting, { ...sorting }), selection && _jsx(Selection, { ...selection }), scrolling && _jsx(Scrolling, { ...scrolling }), summary && _jsx(Summary, { ...summary }), showHeaderFilter && _jsx(HeaderFilter, { visible: true, ...headerFilter }), rowDragging && _jsx(RowDragging, { ...rowDragging }), filterRow && _jsx(FilterRow, { ...filterRow }), showFilterPanel && _jsx(FilterPanel, { visible: true }), showHeaderColumnChooser && _jsxs(ColumnChooser, { height: "400px", enabled: !showHeaderColumnChooser, mode: "select", children: [_jsx(Position, { my: "center", at: "center", of: window }), _jsx(ColumnChooserSearch, { enabled: true }), _jsx(ColumnChooserSelection, { allowSelectAll: false, selectByClick: true, recursive: true })] }), stateStoring && _jsx(StateStoring, { ...stateStoring }), groupPanel && _jsx(GroupPanel, { ...groupPanel }), _jsx(Grouping, { contextMenuEnabled: true, ...grouping }), _jsx(LoadPanel, { enabled: showLoadPanel }), _jsx(SearchPanel, { visible: showSearchPanel, searchVisibleColumnsOnly: true, highlightSearchText: true }), editing && _jsx(Editing, { ...editing }), paging && _jsx(Paging, { ...paging }), pager && _jsx(Pager, { ...pager, visible: totalRecordCount > pageSize }), masterDetail && _jsx(MasterDetail, { ...masterDetail })] }) }), counterConfig.show && _jsx("div", { style: { width: "100%", height: "25px", display: "flex", alignItems: "center", gap: "15px", backgroundColor: "#e0e0e0" }, children: _jsx(TMCounterContainer, { items: counterValues, bgColorContainer: counterConfig.bgColorContainer, bgColorItem: counterConfig.bgColorItem, hoverColorItem: counterConfig.hoverColorItem, textColorItem: counterConfig.textColorItem }) }), customContextMenuItems && (_jsx(TMContextMenu, { items: processCustomContextMenuItems(customContextMenuItems, customContextMenuRowKey), externalControl: {
|
|
314
|
+
visible: customContextMenuVisible,
|
|
315
|
+
position: customContextMenuPosition,
|
|
316
|
+
onClose: () => setCustomContextMenuVisible(false)
|
|
317
|
+
} }))] });
|
|
238
318
|
});
|
|
239
319
|
export default TMDataGrid;
|
|
240
320
|
const getRecordCount = (dataSource) => {
|