@topconsultnpm/sdkui-react 6.22.0-dev2.4 → 6.22.0-dev2.40
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 +1 -1
- package/lib/components/NewComponents/FloatingMenuBar/TMFloatingMenuBar.js +1 -1
- package/lib/components/base/TMButton.d.ts +13 -0
- package/lib/components/base/TMButton.js +137 -5
- package/lib/components/base/TMDataGrid.d.ts +2 -0
- package/lib/components/base/TMDataGrid.js +81 -8
- package/lib/components/base/TMEditorBase.d.ts +2 -0
- package/lib/components/base/TMFileManager.js +5 -3
- package/lib/components/base/TMFileManagerUtils.d.ts +1 -0
- package/lib/components/base/TMFileManagerUtils.js +16 -0
- package/lib/components/base/TMModal.d.ts +11 -0
- package/lib/components/base/TMModal.js +47 -13
- package/lib/components/base/TMPanel.d.ts +1 -0
- package/lib/components/base/TMPanel.js +79 -20
- package/lib/components/base/TMPopUp.js +3 -0
- package/lib/components/base/TMTooltip.d.ts +2 -1
- package/lib/components/base/TMTooltip.js +23 -6
- package/lib/components/base/TMTreeView.js +108 -67
- package/lib/components/choosers/TMDcmtTypeChooser.js +4 -3
- package/lib/components/choosers/TMDistinctValues.d.ts +9 -7
- package/lib/components/choosers/TMDistinctValues.js +149 -142
- package/lib/components/choosers/TMMetadataChooser.js +1 -1
- package/lib/components/choosers/TMQuickSearchInfo.d.ts +12 -0
- package/lib/components/choosers/TMQuickSearchInfo.js +279 -0
- package/lib/components/choosers/TMQuickSearchSettingsForm.d.ts +16 -0
- package/lib/components/choosers/TMQuickSearchSettingsForm.js +94 -0
- package/lib/components/choosers/TMSelectedValuesSummary.d.ts +13 -0
- package/lib/components/choosers/TMSelectedValuesSummary.js +91 -0
- package/lib/components/editors/TMDropDown.d.ts +3 -0
- package/lib/components/editors/TMDropDown.js +197 -5
- package/lib/components/editors/TMMetadataEditor.d.ts +5 -1
- package/lib/components/editors/TMMetadataEditor.js +8 -3
- package/lib/components/editors/TMTextBox.js +2 -2
- package/lib/components/features/documents/TMBatchUpdateForm.js +19 -5
- package/lib/components/features/documents/TMDcmtBlog.d.ts +3 -0
- package/lib/components/features/documents/TMDcmtBlog.js +4 -4
- package/lib/components/features/documents/TMDcmtForm.d.ts +7 -0
- package/lib/components/features/documents/TMDcmtForm.js +112 -45
- package/lib/components/features/documents/TMDcmtFormActionButtons.d.ts +11 -0
- package/lib/components/features/documents/TMDcmtFormActionButtons.js +74 -7
- package/lib/components/features/documents/TMDcmtPreview.js +10 -6
- package/lib/components/features/documents/TMMasterDetailDcmts.js +95 -33
- package/lib/components/features/documents/TMMasterInfoFields.d.ts +19 -0
- package/lib/components/features/documents/TMMasterInfoFields.js +186 -0
- package/lib/components/features/documents/TMMultiMasterDetailDcmtsForm.d.ts +45 -0
- package/lib/components/features/documents/TMMultiMasterDetailDcmtsForm.js +323 -0
- package/lib/components/features/documents/TMMultiMasterDetailDcmtsUtils.d.ts +370 -0
- package/lib/components/features/documents/TMMultiMasterDetailDcmtsUtils.js +283 -0
- package/lib/components/features/documents/TMRelationViewer.d.ts +14 -1
- package/lib/components/features/documents/TMRelationViewer.js +69 -20
- package/lib/components/features/search/TMSearch.js +31 -18
- package/lib/components/features/search/TMSearchQueryEditor.js +5 -3
- package/lib/components/features/search/TMSearchQueryPanel.d.ts +2 -1
- package/lib/components/features/search/TMSearchQueryPanel.js +45 -9
- package/lib/components/features/search/TMSearchResult.d.ts +15 -0
- package/lib/components/features/search/TMSearchResult.js +509 -187
- package/lib/components/features/search/TMTreeSelector.js +18 -5
- package/lib/components/features/workflow/TMWorkflowPopup.d.ts +10 -0
- package/lib/components/features/workflow/TMWorkflowPopup.js +8 -4
- package/lib/components/layout/panelManager/TMPanelManagerContainer.d.ts +1 -0
- package/lib/components/layout/panelManager/TMPanelManagerContainer.js +24 -14
- package/lib/components/layout/panelManager/TMPanelManagerContext.js +68 -93
- package/lib/components/layout/panelManager/TMPanelWrapper.js +2 -2
- package/lib/components/layout/panelManager/types.d.ts +3 -0
- package/lib/components/layout/panelManager/utils.js +13 -11
- package/lib/components/viewers/TMMidViewer.js +4 -1
- package/lib/components/viewers/TMTidViewer.js +25 -11
- package/lib/devextreme-license.d.ts +1 -0
- package/lib/devextreme-license.js +3 -0
- package/lib/helper/SDKUI_Globals.d.ts +109 -0
- package/lib/helper/SDKUI_Globals.js +150 -0
- package/lib/helper/SDKUI_Localizator.d.ts +34 -0
- package/lib/helper/SDKUI_Localizator.js +340 -0
- package/lib/helper/TMIcons.d.ts +1 -0
- package/lib/helper/TMIcons.js +3 -0
- package/lib/helper/dataGridSearchHelper.d.ts +28 -0
- package/lib/helper/dataGridSearchHelper.js +51 -0
- package/lib/helper/dcmtsHelper.d.ts +20 -0
- package/lib/helper/dcmtsHelper.js +58 -1
- package/lib/helper/helpers.d.ts +20 -0
- package/lib/helper/helpers.js +55 -1
- package/lib/helper/index.d.ts +1 -0
- package/lib/helper/index.js +1 -0
- package/lib/helper/queryHelper.d.ts +2 -0
- package/lib/helper/queryHelper.js +3 -1
- package/lib/hooks/tmDistinctValuesGridHelper.d.ts +16 -0
- package/lib/hooks/tmDistinctValuesGridHelper.js +31 -0
- package/lib/hooks/useArchiveListForm.d.ts +39 -0
- package/lib/hooks/useArchiveListForm.js +46 -0
- package/lib/hooks/useCaseFlowApprove.d.ts +56 -0
- package/lib/hooks/useCaseFlowApprove.js +157 -0
- package/lib/hooks/useDataUserIdItem.d.ts +1 -0
- package/lib/hooks/useDataUserIdItem.js +1 -0
- package/lib/hooks/useDocumentOperations.d.ts +4 -0
- package/lib/hooks/useDocumentOperations.js +41 -7
- package/lib/hooks/useMultiMasterDetailDcmts.d.ts +113 -0
- package/lib/hooks/useMultiMasterDetailDcmts.js +760 -0
- package/lib/hooks/useRelatedDocuments.js +2 -26
- package/lib/hooks/useShowDossiersV5.d.ts +12 -0
- package/lib/hooks/useShowDossiersV5.js +37 -0
- package/lib/hooks/useTMDistinctValuesMetadataDisplay.d.ts +18 -0
- package/lib/hooks/useTMDistinctValuesMetadataDisplay.js +103 -0
- package/lib/hooks/useTMDistinctValuesQuickSearch.d.ts +49 -0
- package/lib/hooks/useTMDistinctValuesQuickSearch.js +307 -0
- package/lib/hooks/useTMDistinctValuesSelection.d.ts +42 -0
- package/lib/hooks/useTMDistinctValuesSelection.js +103 -0
- package/lib/hooks/useTMDistinctValuesSource.d.ts +29 -0
- package/lib/hooks/useTMDistinctValuesSource.js +105 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.js +5 -2
- package/lib/services/caseflow/CaseFlowService.d.ts +236 -0
- package/lib/services/caseflow/CaseFlowService.js +693 -0
- package/lib/services/caseflow/CaseFlowTemplateCacheService.d.ts +28 -0
- package/lib/services/caseflow/CaseFlowTemplateCacheService.js +121 -0
- package/lib/services/caseflow/caseFlowNameCache.d.ts +32 -0
- package/lib/services/caseflow/caseFlowNameCache.js +116 -0
- package/lib/services/caseflow/caseFlowWorkItemUtils.d.ts +61 -0
- package/lib/services/caseflow/caseFlowWorkItemUtils.js +113 -0
- package/lib/services/index.d.ts +4 -0
- package/lib/services/index.js +4 -0
- package/lib/services/platform_services.d.ts +1 -1
- package/package.json +9 -5
|
@@ -500,6 +500,6 @@ const TMContextMenu = ({ items, trigger = 'right', children, target, externalCon
|
|
|
500
500
|
// display: 'inline-block',
|
|
501
501
|
WebkitTouchCallout: isIOS ? 'none' : undefined,
|
|
502
502
|
WebkitUserSelect: isIOS ? 'none' : undefined,
|
|
503
|
-
}, children: children })), menuState.visible && createPortal(_jsxs(_Fragment, { children: [_jsxs(S.MenuContainer, { ref: menuRef, "$x": menuState.position.x, "$y": menuState.position.y, "$openLeft": openLeft, "$openUp": openUp, "$isPositioned": isCalculated, "$externalControl": !!externalControl, "$needsScroll": needsScroll, "$maxHeight": maxHeight, 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", "data-open-up": submenu.openUp ? 'true' : 'false', onMouseEnter: handleSubmenuMouseEnter, onMouseLeave: () => handleMouseLeave(submenu.depth), children: renderMenuItems(submenu.items, submenu.depth) }, `submenu-${submenu.depth}-${idx}`)))] }), document.body)] }));
|
|
503
|
+
}, children: children })), menuState.visible && createPortal(_jsxs(_Fragment, { children: [_jsxs(S.MenuContainer, { ref: menuRef, className: "notranslate", translate: "no", "$x": menuState.position.x, "$y": menuState.position.y, "$openLeft": openLeft, "$openUp": openUp, "$isPositioned": isCalculated, "$externalControl": !!externalControl, "$needsScroll": needsScroll, "$maxHeight": maxHeight, 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, { className: "notranslate", translate: "no", "$parentRect": submenu.parentRect, "$openUp": submenu.openUp, "data-submenu": "true", "data-open-up": submenu.openUp ? 'true' : 'false', onMouseEnter: handleSubmenuMouseEnter, onMouseLeave: () => handleMouseLeave(submenu.depth), children: renderMenuItems(submenu.items, submenu.depth) }, `submenu-${submenu.depth}-${idx}`)))] }), document.body)] }));
|
|
504
504
|
};
|
|
505
505
|
export default TMContextMenu;
|
|
@@ -837,7 +837,7 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], isConstrained
|
|
|
837
837
|
setState(s => ({ ...s, draggedItemIndex: null }));
|
|
838
838
|
setDragOverIndex(null);
|
|
839
839
|
};
|
|
840
|
-
return (_jsxs(S.FloatingContainer, { ref: floatingRef, "$x": pixelPosition.x, "$y": pixelPosition.y, "$orientation": state.orientation, "$verticalDirection": state.verticalDirection, "$isDragging": state.isDragging, "$isConfigMode": false, "$isConstrained": isConstrained, "$isHidden": isOrientationChanging, "$bgColor": bgColor, onContextMenu: undefined, children: [_jsx(ContextMenu, { items: [
|
|
840
|
+
return (_jsxs(S.FloatingContainer, { ref: floatingRef, className: "notranslate", translate: "no", "$x": pixelPosition.x, "$y": pixelPosition.y, "$orientation": state.orientation, "$verticalDirection": state.verticalDirection, "$isDragging": state.isDragging, "$isConfigMode": false, "$isConstrained": isConstrained, "$isHidden": isOrientationChanging, "$bgColor": bgColor, onContextMenu: undefined, children: [_jsx(ContextMenu, { items: [
|
|
841
841
|
{
|
|
842
842
|
name: state.orientation === 'horizontal' ? 'Floating bar verticale' : 'Floating bar orizzontale',
|
|
843
843
|
icon: _jsx(IconRotate, { fontSize: 16, style: { transform: state.orientation === 'horizontal' ? 'rotate(90deg)' : 'rotate(0deg)' } }),
|
|
@@ -3,6 +3,17 @@ import { ITMEditorBase } from './TMEditorBase';
|
|
|
3
3
|
export type ButtonColors = 'error' | 'warning' | 'success' | 'info' | 'primary' | 'secondary' | 'tertiary' | 'standard' | 'primaryOutline' | 'secondaryOutline' | 'tertiaryOutline' | 'errorOutline' | 'warningOutline' | 'successOutline' | 'infoOutline';
|
|
4
4
|
export type ButtonStyle = 'normal' | 'toolbar' | 'icon' | 'text' | 'advanced';
|
|
5
5
|
export type AdvancedButtonType = 'success' | 'error' | 'tertiary' | 'primary';
|
|
6
|
+
/** Azione secondaria mostrata nel dropdown "split button" (freccia a destra) */
|
|
7
|
+
export interface ITMButtonSecondaryAction {
|
|
8
|
+
/** Etichetta mostrata nella voce di menu */
|
|
9
|
+
name: string;
|
|
10
|
+
/** Icona opzionale a sinistra della voce */
|
|
11
|
+
icon?: React.ReactNode;
|
|
12
|
+
/** Callback al click della voce */
|
|
13
|
+
onClick?: () => void;
|
|
14
|
+
/** Disabilita la singola voce */
|
|
15
|
+
disabled?: boolean;
|
|
16
|
+
}
|
|
6
17
|
export interface ITMButton extends ITMEditorBase {
|
|
7
18
|
color?: ButtonColors;
|
|
8
19
|
btnStyle?: ButtonStyle;
|
|
@@ -17,6 +28,8 @@ export interface ITMButton extends ITMEditorBase {
|
|
|
17
28
|
showTooltip?: boolean;
|
|
18
29
|
onClick?: VoidFunction;
|
|
19
30
|
onMouseDown?: (e: any) => any;
|
|
31
|
+
/** Azioni secondarie: se valorizzate, il bottone mostra una freccia a destra che apre un dropdown con queste azioni */
|
|
32
|
+
secondaryActions?: ITMButtonSecondaryAction[];
|
|
20
33
|
}
|
|
21
34
|
declare const TMButton: React.ForwardRefExoticComponent<ITMButton & React.RefAttributes<HTMLButtonElement>>;
|
|
22
35
|
export default TMButton;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { useState, forwardRef } from 'react';
|
|
2
|
+
import { useState, forwardRef, useRef, useEffect } from 'react';
|
|
3
|
+
import { createPortal } from 'react-dom';
|
|
3
4
|
import styled from 'styled-components';
|
|
4
|
-
import { getColor } from '../../helper';
|
|
5
|
+
import { getColor, IconChevronDown } from '../../helper';
|
|
5
6
|
import { FontSize, TMColors } from '../../utils/theme';
|
|
6
7
|
import TMTooltip from './TMTooltip';
|
|
7
8
|
const StyledNormalButton = styled.button.withConfig({ shouldForwardProp: prop => !['text'].includes(prop) }) `
|
|
@@ -149,10 +150,132 @@ const StyledAdvancedButtonText = styled.div `
|
|
|
149
150
|
border-top-right-radius: 10px;
|
|
150
151
|
border-bottom-right-radius: 10px;
|
|
151
152
|
padding: 5px;
|
|
153
|
+
padding-right: ${props => props.$reserveArrow ? '26px' : '5px'};
|
|
154
|
+
`;
|
|
155
|
+
const StyledSplitContainer = styled.div `
|
|
156
|
+
display: inline-flex;
|
|
157
|
+
align-items: stretch;
|
|
158
|
+
position: relative;
|
|
159
|
+
`;
|
|
160
|
+
const StyledSplitArrow = styled.button `
|
|
161
|
+
position: absolute;
|
|
162
|
+
right: 4px;
|
|
163
|
+
top: 50%;
|
|
164
|
+
transform: translateY(-50%);
|
|
165
|
+
z-index: 2;
|
|
166
|
+
display: flex;
|
|
167
|
+
align-items: center;
|
|
168
|
+
justify-content: center;
|
|
169
|
+
width: 20px;
|
|
170
|
+
height: 20px;
|
|
171
|
+
padding: 0;
|
|
172
|
+
border: none;
|
|
173
|
+
border-radius: 6px;
|
|
174
|
+
cursor: ${props => props.$isDisabled ? 'default' : 'pointer'};
|
|
175
|
+
color: white;
|
|
176
|
+
background-color: rgba(255, 255, 255, 0.22);
|
|
177
|
+
transition: background-color ease 150ms;
|
|
178
|
+
&:hover { background-color: ${props => props.$isDisabled ? 'rgba(255, 255, 255, 0.22)' : 'rgba(255, 255, 255, 0.38)'}; }
|
|
179
|
+
& svg {
|
|
180
|
+
transition: transform ease 150ms;
|
|
181
|
+
transform: rotate(${props => props.$isOpen ? '180deg' : '0deg'});
|
|
182
|
+
}
|
|
183
|
+
`;
|
|
184
|
+
const StyledDropdown = styled.div `
|
|
185
|
+
position: fixed;
|
|
186
|
+
left: ${props => props.$left}px;
|
|
187
|
+
top: ${props => props.$top}px;
|
|
188
|
+
transform: translateY(-100%);
|
|
189
|
+
transform-origin: bottom left;
|
|
190
|
+
min-width: ${props => props.$minWidth}px;
|
|
191
|
+
background: #ffffff;
|
|
192
|
+
border-radius: 14px;
|
|
193
|
+
box-shadow: 0 10px 30px rgba(20, 40, 80, 0.20);
|
|
194
|
+
border: 1px solid rgba(74, 150, 210, 0.18);
|
|
195
|
+
padding: 5px;
|
|
196
|
+
z-index: 100000;
|
|
197
|
+
display: flex;
|
|
198
|
+
flex-direction: column;
|
|
199
|
+
gap: 2px;
|
|
200
|
+
animation: tmSplitDropdownIn cubic-bezier(0.2, 0.8, 0.2, 1) 150ms;
|
|
201
|
+
@keyframes tmSplitDropdownIn {
|
|
202
|
+
from { opacity: 0; transform: translateY(calc(-100% + 8px)); }
|
|
203
|
+
to { opacity: 1; transform: translateY(-100%); }
|
|
204
|
+
}
|
|
205
|
+
`;
|
|
206
|
+
const StyledDropdownItem = styled.button `
|
|
207
|
+
display: flex;
|
|
208
|
+
align-items: center;
|
|
209
|
+
gap: 9px;
|
|
210
|
+
width: 100%;
|
|
211
|
+
padding: 5px 9px;
|
|
212
|
+
border: none;
|
|
213
|
+
border-radius: 8px;
|
|
214
|
+
background: transparent;
|
|
215
|
+
text-align: left;
|
|
216
|
+
font-size: ${FontSize.defaultFontSize};
|
|
217
|
+
font-weight: 500;
|
|
218
|
+
color: ${props => props.$disabled ? '#b4b4b4' : '#2c3e50'};
|
|
219
|
+
cursor: ${props => props.$disabled ? 'default' : 'pointer'};
|
|
220
|
+
transition: background ease 150ms, color ease 150ms;
|
|
221
|
+
&:hover {
|
|
222
|
+
background: ${props => props.$disabled ? 'transparent' : 'linear-gradient(135deg, rgba(74, 150, 210, 0.16) 0%, rgba(37, 89, 165, 0.16) 100%)'};
|
|
223
|
+
color: ${props => props.$disabled ? '#b4b4b4' : '#2559A5'};
|
|
224
|
+
}
|
|
225
|
+
& > .tm-split-item-icon {
|
|
226
|
+
display: flex;
|
|
227
|
+
align-items: center;
|
|
228
|
+
justify-content: center;
|
|
229
|
+
width: 22px;
|
|
230
|
+
height: 22px;
|
|
231
|
+
flex-shrink: 0;
|
|
232
|
+
border-radius: 6px;
|
|
233
|
+
background: rgba(74, 150, 210, 0.14);
|
|
234
|
+
color: #4A96D2;
|
|
235
|
+
transition: background ease 150ms, color ease 150ms;
|
|
236
|
+
}
|
|
237
|
+
&:hover > .tm-split-item-icon {
|
|
238
|
+
background: ${props => props.$disabled ? 'rgba(74, 150, 210, 0.14)' : '#4A96D2'};
|
|
239
|
+
color: ${props => props.$disabled ? '#4A96D2' : '#ffffff'};
|
|
240
|
+
}
|
|
152
241
|
`;
|
|
153
242
|
const TMButton = forwardRef((props, ref) => {
|
|
154
|
-
const { width, height, keyGesture, btnStyle = 'normal', advancedColor, advancedType = 'primary', color = 'primary', fontSize = FontSize.defaultFontSize, disabled = false, showTooltip = true, caption, icon, description, padding, elementStyle, onClick = () => { }, onMouseDown = () => { } } = props;
|
|
243
|
+
const { width, height, keyGesture, btnStyle = 'normal', advancedColor, advancedType = 'primary', color = 'primary', fontSize = FontSize.defaultFontSize, disabled = false, showTooltip = true, caption, icon, description, padding, elementStyle, onClick = () => { }, onMouseDown = () => { }, secondaryActions } = props;
|
|
155
244
|
const [isHovered, setIsHovered] = useState(false);
|
|
245
|
+
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
|
|
246
|
+
const [dropdownPos, setDropdownPos] = useState({ left: 0, top: 0, minWidth: 200 });
|
|
247
|
+
const splitContainerRef = useRef(null);
|
|
248
|
+
const dropdownRef = useRef(null);
|
|
249
|
+
const hasSecondaryActions = !!secondaryActions && secondaryActions.length > 0;
|
|
250
|
+
useEffect(() => {
|
|
251
|
+
if (!isDropdownOpen)
|
|
252
|
+
return;
|
|
253
|
+
const handlePointerDown = (e) => {
|
|
254
|
+
const target = e.target;
|
|
255
|
+
if (splitContainerRef.current?.contains(target) || dropdownRef.current?.contains(target))
|
|
256
|
+
return;
|
|
257
|
+
setIsDropdownOpen(false);
|
|
258
|
+
};
|
|
259
|
+
const handleKeyDown = (e) => { if (e.key === 'Escape')
|
|
260
|
+
setIsDropdownOpen(false); };
|
|
261
|
+
document.addEventListener('mousedown', handlePointerDown);
|
|
262
|
+
document.addEventListener('touchstart', handlePointerDown);
|
|
263
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
264
|
+
return () => {
|
|
265
|
+
document.removeEventListener('mousedown', handlePointerDown);
|
|
266
|
+
document.removeEventListener('touchstart', handlePointerDown);
|
|
267
|
+
document.removeEventListener('keydown', handleKeyDown);
|
|
268
|
+
};
|
|
269
|
+
}, [isDropdownOpen]);
|
|
270
|
+
const toggleDropdown = () => {
|
|
271
|
+
if (disabled)
|
|
272
|
+
return;
|
|
273
|
+
if (!isDropdownOpen && splitContainerRef.current) {
|
|
274
|
+
const rect = splitContainerRef.current.getBoundingClientRect();
|
|
275
|
+
setDropdownPos({ left: rect.left, top: rect.top - 6, minWidth: Math.max(rect.width, 200) });
|
|
276
|
+
}
|
|
277
|
+
setIsDropdownOpen(prev => !prev);
|
|
278
|
+
};
|
|
156
279
|
const renderedButton = () => {
|
|
157
280
|
if (btnStyle === 'normal') {
|
|
158
281
|
return (_jsx(StyledNormalButton, { ref: ref, width: width, height: height, color: color, caption: caption, disabled: disabled, fontSize: fontSize, onClick: onClick, onMouseDown: (e) => !disabled && onMouseDown?.(e), children: caption }));
|
|
@@ -164,7 +287,7 @@ const TMButton = forwardRef((props, ref) => {
|
|
|
164
287
|
return (_jsx(StyledIconButton, { ref: ref, color: color, caption: caption, disabled: disabled, fontSize: fontSize, padding: padding, onClick: onClick, onMouseDown: (e) => !disabled && onMouseDown?.(e), children: icon }));
|
|
165
288
|
}
|
|
166
289
|
else if (btnStyle === 'advanced') {
|
|
167
|
-
return (_jsxs(StyledAdvancedButton, { ref: ref, "$width": width ?? '90px', "$height": height ?? '30px', onMouseOver: () => !disabled && setIsHovered(true), onMouseOut: () => setIsHovered(false), "$isDisabled": disabled, "$isPrimaryOutline": advancedType === 'primary' && (advancedColor === 'primaryOutline' || color === 'primaryOutline'), onClick: () => !disabled && onClick?.(), onMouseDown: (e) => !disabled && onMouseDown?.(e), children: [_jsx(StyledAdvancedButtonIcon, { "$color": advancedColor, "$isVisible": isHovered, "$isDisabled": disabled, "$type": advancedType, children: icon }), _jsx(StyledAdvancedButtonText, { "$color": advancedColor, "$isDisabled": disabled, "$type": advancedType, children: caption })] }));
|
|
290
|
+
return (_jsxs(StyledAdvancedButton, { ref: ref, "$width": width ?? '90px', "$height": height ?? '30px', onMouseOver: () => !disabled && setIsHovered(true), onMouseOut: () => setIsHovered(false), "$isDisabled": disabled, "$isPrimaryOutline": advancedType === 'primary' && (advancedColor === 'primaryOutline' || color === 'primaryOutline'), onClick: () => !disabled && onClick?.(), onMouseDown: (e) => !disabled && onMouseDown?.(e), children: [_jsx(StyledAdvancedButtonIcon, { "$color": advancedColor, "$isVisible": isHovered, "$isDisabled": disabled, "$type": advancedType, children: icon }), _jsx(StyledAdvancedButtonText, { "$color": advancedColor, "$isDisabled": disabled, "$type": advancedType, "$reserveArrow": hasSecondaryActions, children: caption })] }));
|
|
168
291
|
}
|
|
169
292
|
else {
|
|
170
293
|
return (_jsx(StyledTextButton, { ref: ref, color: color, caption: caption, disabled: disabled, fontSize: fontSize, onClick: onClick, onMouseDown: (e) => !disabled && onMouseDown?.(e), children: caption }));
|
|
@@ -175,6 +298,15 @@ const TMButton = forwardRef((props, ref) => {
|
|
|
175
298
|
_jsxs(_Fragment, { children: [caption &&
|
|
176
299
|
_jsx(StyledButtonTooltipHeader, { "$description": description, children: _jsx(StyledButtonTooltipItem, { "$color": color, children: caption }) }), description && _jsx(StyledButtonTooltipItem, { children: description })] }));
|
|
177
300
|
};
|
|
178
|
-
|
|
301
|
+
const buttonWithTooltip = showTooltip
|
|
302
|
+
? _jsx(TMTooltip, { hideAfterDelay: true, content: renderTooltip(), children: renderedButton() })
|
|
303
|
+
: renderedButton();
|
|
304
|
+
const renderSplitButton = () => (_jsxs(StyledSplitContainer, { ref: splitContainerRef, children: [buttonWithTooltip, _jsx(StyledSplitArrow, { type: 'button', "$isDisabled": disabled, "$isOpen": isDropdownOpen, "aria-haspopup": 'menu', "aria-expanded": isDropdownOpen, onClick: (e) => { e.stopPropagation(); toggleDropdown(); }, onMouseDown: (e) => e.stopPropagation(), children: _jsx(IconChevronDown, { fontSize: 14 }) }), isDropdownOpen && createPortal(_jsx(StyledDropdown, { ref: dropdownRef, role: 'menu', "$left": dropdownPos.left, "$top": dropdownPos.top, "$minWidth": dropdownPos.minWidth, children: secondaryActions.map((action, idx) => (_jsxs(StyledDropdownItem, { type: 'button', role: 'menuitem', "$disabled": action.disabled, disabled: action.disabled, onClick: () => {
|
|
305
|
+
if (action.disabled)
|
|
306
|
+
return;
|
|
307
|
+
setIsDropdownOpen(false);
|
|
308
|
+
action.onClick?.();
|
|
309
|
+
}, children: [action.icon && _jsx("span", { className: 'tm-split-item-icon', children: action.icon }), _jsx("span", { children: action.name })] }, `${action.name}-${idx}`))) }), document.body)] }));
|
|
310
|
+
return (_jsx("div", { style: elementStyle, children: hasSecondaryActions ? renderSplitButton() : buttonWithTooltip }));
|
|
179
311
|
});
|
|
180
312
|
export default TMButton;
|
|
@@ -46,6 +46,8 @@ export interface TMDataGridProps<T> extends IDataGridOptions {
|
|
|
46
46
|
showSearchPanel?: boolean;
|
|
47
47
|
/** Show the group panel */
|
|
48
48
|
showGroupPanel?: boolean;
|
|
49
|
+
/** Rende il paginatore compatto (meno alto, font e pulsanti ridotti, page numbers sostituiti da "pagina / totale"): utile quando la griglia è dentro pannelli bassi */
|
|
50
|
+
compactPager?: boolean;
|
|
49
51
|
/** Counter configuration */
|
|
50
52
|
counterConfig?: ITMCounterContainerProps;
|
|
51
53
|
/** Master Detail */
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import React, { useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react';
|
|
2
|
+
import React, { useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
|
|
3
|
+
import styled, { css } from 'styled-components';
|
|
3
4
|
import DataGrid, { Column, HeaderFilter, Selection, Scrolling, LoadPanel, SearchPanel, Pager, Sorting, Paging, FilterPanel, ColumnChooser, Grouping, GroupPanel, Summary, Editing, FilterRow, StateStoring, RowDragging, MasterDetail, Position, ColumnChooserSearch, ColumnChooserSelection } from 'devextreme-react/data-grid';
|
|
4
5
|
import DataSource from 'devextreme/data/data_source';
|
|
5
6
|
import { IconAll, IconSelected, IconVisible, SDKUI_Globals, SDKUI_Localizator } from '../../helper';
|
|
@@ -18,7 +19,7 @@ const TMDataGrid = React.forwardRef((props, ref) => {
|
|
|
18
19
|
// main properties
|
|
19
20
|
keyExpr = 'id', dataSource, focusedRowEnabled = true, hoverStateEnabled = true, focusedRowKey, selectedRowKeys = [],
|
|
20
21
|
// custom options
|
|
21
|
-
dataColumns = [], pageSize = TMDataGridPageSize.Large, showHeaderFilter = true, showFilterPanel = true, showHeaderColumnChooser = false, showLoadPanel = true, showSearchPanel = true, searchPanelToolbarPosition = 'before', searchPanelFocusTrigger = 0, counterConfig = { show: false, items: new Map() }, onHasFiltersChange, customContextMenuItems,
|
|
22
|
+
dataColumns = [], pageSize = TMDataGridPageSize.Large, showHeaderFilter = true, showFilterPanel = true, showHeaderColumnChooser = false, showLoadPanel = true, showSearchPanel = true, searchPanelToolbarPosition = 'before', searchPanelFocusTrigger = 0, counterConfig = { show: false, items: new Map() }, compactPager = false, onHasFiltersChange, customContextMenuItems,
|
|
22
23
|
// events and callbacks
|
|
23
24
|
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,
|
|
24
25
|
// other properties
|
|
@@ -35,6 +36,9 @@ const TMDataGrid = React.forwardRef((props, ref) => {
|
|
|
35
36
|
const [customContextMenuVisible, setCustomContextMenuVisible] = useState(false);
|
|
36
37
|
const [customContextMenuPosition, setCustomContextMenuPosition] = useState({ x: 0, y: 0 });
|
|
37
38
|
const [customContextMenuRowKey, setCustomContextMenuRowKey] = useState(undefined);
|
|
39
|
+
// true quando il menu contestuale viene aperto su spazio vuoto senza alcuna riga di riferimento:
|
|
40
|
+
// in tal caso i comandi legati alla riga (singleRow/multiRow) devono risultare disabilitati
|
|
41
|
+
const [customContextMenuOnEmptyArea, setCustomContextMenuOnEmptyArea] = useState(false);
|
|
38
42
|
const gridContainerRef = useRef(null);
|
|
39
43
|
// Ref per evitare focus ripetuti sulla search panel
|
|
40
44
|
const searchPanelFocusedRef = useRef(false);
|
|
@@ -75,9 +79,17 @@ const TMDataGrid = React.forwardRef((props, ref) => {
|
|
|
75
79
|
const rowKey = internalRef.current.instance().getKeyByRowIndex(rowIndex);
|
|
76
80
|
internalRef.current.instance().option('focusedRowKey', rowKey);
|
|
77
81
|
setCustomContextMenuRowKey(rowKey);
|
|
82
|
+
setCustomContextMenuOnEmptyArea(false);
|
|
78
83
|
}
|
|
79
84
|
else {
|
|
80
|
-
|
|
85
|
+
// Right-click su spazio vuoto: ricade sulla riga focalizzata solo se esiste ancora.
|
|
86
|
+
// Se non c'è una riga focalizzata (es. primo click dopo l'apertura) o se focusedRowKey punta a
|
|
87
|
+
// una riga non più presente (dopo un'eliminazione), il menu viene comunque mostrato ma senza
|
|
88
|
+
// riga di riferimento: i comandi di riga restano disabilitati, quelli generali utilizzabili
|
|
89
|
+
const grid = internalRef.current?.instance();
|
|
90
|
+
const focusedRowExists = focusedRowKey !== undefined && !!grid && grid.getRowIndexByKey(focusedRowKey) >= 0;
|
|
91
|
+
setCustomContextMenuRowKey(focusedRowExists ? focusedRowKey : undefined);
|
|
92
|
+
setCustomContextMenuOnEmptyArea(!focusedRowExists);
|
|
81
93
|
}
|
|
82
94
|
setCustomContextMenuVisible(true);
|
|
83
95
|
setCustomContextMenuPosition({ x: e.clientX, y: e.clientY });
|
|
@@ -114,6 +126,7 @@ const TMDataGrid = React.forwardRef((props, ref) => {
|
|
|
114
126
|
setCustomContextMenuVisible(true);
|
|
115
127
|
setCustomContextMenuPosition({ x: clientX, y: clientY });
|
|
116
128
|
setCustomContextMenuRowKey(rowKey);
|
|
129
|
+
setCustomContextMenuOnEmptyArea(false);
|
|
117
130
|
},
|
|
118
131
|
});
|
|
119
132
|
// Creating a ref to store the timestamp of the last selection change
|
|
@@ -222,7 +235,8 @@ const TMDataGrid = React.forwardRef((props, ref) => {
|
|
|
222
235
|
return items.map(item => {
|
|
223
236
|
let disabled = item.disabled ?? false;
|
|
224
237
|
let disabledCalculation = false;
|
|
225
|
-
|
|
238
|
+
// Menu aperto su spazio vuoto senza riga di riferimento: nessun id, i comandi di riga sono disabilitati
|
|
239
|
+
const id = customContextMenuOnEmptyArea ? undefined : (focusedRowEnabled ? focusedRowKey : rowID);
|
|
226
240
|
if (item.operationType === 'singleRow') {
|
|
227
241
|
disabledCalculation = selectedRowKeys.length > 1 || id === undefined;
|
|
228
242
|
}
|
|
@@ -237,7 +251,10 @@ const TMDataGrid = React.forwardRef((props, ref) => {
|
|
|
237
251
|
if (item.operationType === 'singleRow' && id !== undefined) {
|
|
238
252
|
originalOnClick(id);
|
|
239
253
|
}
|
|
240
|
-
else if (item.operationType === 'multiRow' && id !== undefined) {
|
|
254
|
+
else if (item.operationType === 'multiRow' && (selectedRowKeys.length > 0 || id !== undefined)) {
|
|
255
|
+
// Condizione allineata a quella di disabilitazione: con righe selezionate il comando è
|
|
256
|
+
// attivo anche senza riga di riferimento (es. menu aperto su spazio vuoto), quindi qui
|
|
257
|
+
// devono comunque essere passate le chiavi selezionate e non un onClick senza argomenti
|
|
241
258
|
if (selectedRowKeys.length > 0) {
|
|
242
259
|
originalOnClick(selectedRowKeys);
|
|
243
260
|
}
|
|
@@ -252,7 +269,7 @@ const TMDataGrid = React.forwardRef((props, ref) => {
|
|
|
252
269
|
submenu: item.submenu ? processCustomContextMenuItems(item.submenu, id) : undefined,
|
|
253
270
|
};
|
|
254
271
|
});
|
|
255
|
-
}, [focusedRowEnabled, focusedRowKey, selectedRowKeys]);
|
|
272
|
+
}, [focusedRowEnabled, focusedRowKey, selectedRowKeys, customContextMenuOnEmptyArea]);
|
|
256
273
|
// Gestisce la preparazione del menu contestuale della griglia (header e righe)
|
|
257
274
|
const onContextMenuPreparingCallback = useCallback((e) => {
|
|
258
275
|
if (e === undefined)
|
|
@@ -358,7 +375,14 @@ const TMDataGrid = React.forwardRef((props, ref) => {
|
|
|
358
375
|
// Propaga l'evento originale
|
|
359
376
|
onOptionChanged?.(e);
|
|
360
377
|
}, [onOptionChanged, onHasFiltersChange]);
|
|
361
|
-
|
|
378
|
+
// In modalità compatta il pager usa il displayMode 'compact' di DevExtreme (page numbers sostituiti da "pagina / totale"),
|
|
379
|
+
// salvo displayMode esplicito passato dal chiamante
|
|
380
|
+
const effectivePager = useMemo(() => {
|
|
381
|
+
if (!pager || !compactPager)
|
|
382
|
+
return pager;
|
|
383
|
+
return { ...pager, displayMode: pager.displayMode ?? 'compact' };
|
|
384
|
+
}, [pager, compactPager]);
|
|
385
|
+
return _jsxs(StyledDataGridContainer, { "$compactPager": compactPager, 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' : ''}`,
|
|
362
386
|
// main properties
|
|
363
387
|
keyExpr: keyExpr, dataSource: dataSource, selectedRowKeys: selectedRowKeys, focusedRowEnabled: focusedRowEnabled, hoverStateEnabled: hoverStateEnabled,
|
|
364
388
|
// events and callbacks
|
|
@@ -371,13 +395,62 @@ const TMDataGrid = React.forwardRef((props, ref) => {
|
|
|
371
395
|
// Temporary: iOS-specific styles to prevent default long-press menu and text selection
|
|
372
396
|
WebkitTouchCallout: 'none',
|
|
373
397
|
WebkitUserSelect: 'none'
|
|
374
|
-
}, 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 }),
|
|
398
|
+
}, children: [dataColumns.map((column, index) => (_jsx(Column, { ...column, alignment: column.alignment ?? 'left' }, 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, search: { enabled: true, ...headerFilter?.search } }), 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 }), effectivePager && _jsx(Pager, { ...effectivePager, 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, { target: ".dx-data-row", items: processCustomContextMenuItems(customContextMenuItems, customContextMenuRowKey), externalControl: {
|
|
375
399
|
visible: customContextMenuVisible,
|
|
376
400
|
position: customContextMenuPosition,
|
|
377
401
|
onClose: () => setCustomContextMenuVisible(false)
|
|
378
402
|
} }))] });
|
|
379
403
|
});
|
|
380
404
|
export default TMDataGrid;
|
|
405
|
+
// Contenitore della griglia: in modalità compatta riduce l'ingombro verticale del pager DevExtreme
|
|
406
|
+
// (padding, altezza minima, font e dimensione dei pulsanti di pagina).
|
|
407
|
+
// Con compactPager = false non emette nessuna regola: width/height restano inline come in precedenza,
|
|
408
|
+
// così il rendering è identico a quello del div originale.
|
|
409
|
+
const StyledDataGridContainer = styled.div `
|
|
410
|
+
${({ $compactPager }) => $compactPager && css `
|
|
411
|
+
& .dx-datagrid-pager {
|
|
412
|
+
padding: 2px 4px !important;
|
|
413
|
+
min-height: 0 !important;
|
|
414
|
+
font-size: 11px;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
& .dx-datagrid-pager .dx-page,
|
|
418
|
+
& .dx-datagrid-pager .dx-page-size,
|
|
419
|
+
& .dx-datagrid-pager .dx-selection,
|
|
420
|
+
& .dx-datagrid-pager .dx-navigate-button {
|
|
421
|
+
min-width: 18px !important;
|
|
422
|
+
min-height: 18px !important;
|
|
423
|
+
height: auto !important;
|
|
424
|
+
padding: 1px 5px !important;
|
|
425
|
+
line-height: 16px !important;
|
|
426
|
+
font-size: 11px;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
& .dx-datagrid-pager .dx-pages,
|
|
430
|
+
& .dx-datagrid-pager .dx-page-sizes,
|
|
431
|
+
& .dx-datagrid-pager .dx-info,
|
|
432
|
+
& .dx-datagrid-pager .dx-page-index,
|
|
433
|
+
& .dx-datagrid-pager .dx-pages .dx-info {
|
|
434
|
+
font-size: 11px;
|
|
435
|
+
margin-top: 0 !important;
|
|
436
|
+
margin-bottom: 0 !important;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
& .dx-datagrid-pager .dx-info {
|
|
440
|
+
margin-left: 4px !important;
|
|
441
|
+
margin-right: 4px !important;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/* Editor del numero di pagina (displayMode compact) */
|
|
445
|
+
& .dx-datagrid-pager .dx-page-index .dx-texteditor-input,
|
|
446
|
+
& .dx-datagrid-pager .dx-page-sizes .dx-texteditor-input {
|
|
447
|
+
min-height: 18px !important;
|
|
448
|
+
height: 18px !important;
|
|
449
|
+
padding: 0 4px !important;
|
|
450
|
+
font-size: 11px;
|
|
451
|
+
}
|
|
452
|
+
`}
|
|
453
|
+
`;
|
|
381
454
|
const getRecordCount = (dataSource) => {
|
|
382
455
|
if (Array.isArray(dataSource)) {
|
|
383
456
|
return dataSource.length; // For array
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import React from "react";
|
|
1
2
|
import { ValidationItem } from "@topconsultnpm/sdk-ts";
|
|
2
3
|
export type Colors = 'error' | 'warning' | 'success' | 'info' | 'primary' | 'secondary' | 'tertiary' | 'standard';
|
|
3
4
|
type IconModes = 'outside' | 'inside' | 'none';
|
|
@@ -6,6 +7,7 @@ export type Sizes = 'small' | 'medium' | 'large';
|
|
|
6
7
|
export type TMEditorButtonData = {
|
|
7
8
|
icon: any;
|
|
8
9
|
text?: string;
|
|
10
|
+
tooltipContent?: React.ReactNode;
|
|
9
11
|
onClick?: () => void;
|
|
10
12
|
};
|
|
11
13
|
export interface ITMEditorBase {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
3
|
-
import { extractTextsFromDirectory, findFileItems, setFolderTreeViewItems } from "./TMFileManagerUtils";
|
|
3
|
+
import { extractTextsFromDirectory, findFileItems, resolveFileItemUserNames, setFolderTreeViewItems } from "./TMFileManagerUtils";
|
|
4
4
|
import { DeviceType, useDeviceType } from "./TMDeviceProvider";
|
|
5
5
|
import { formatBytes, Globalization, IconDashboard, IconFolder, IconHide, IconList, IconMenuVertical, IconRefresh, IconShow, SDKUI_Globals, SDKUI_Localizator, TMCommandsContextMenu, TMConditionalWrapper } from "../../helper";
|
|
6
6
|
import TMContextMenu from '../NewComponents/ContextMenu/TMContextMenu';
|
|
@@ -91,10 +91,12 @@ const TMFileManager = (props) => {
|
|
|
91
91
|
(Globalization.getDateTimeDisplayValue(item.lastUpdateTime)?.toLowerCase().toString().includes(searchText.toLowerCase().trim())) ||
|
|
92
92
|
(Globalization.getDateTimeDisplayValue(item.creationTime)?.toLowerCase().toString().includes(searchText.toLowerCase().trim())) ||
|
|
93
93
|
(item.version?.toString().includes(searchText.toLowerCase().trim()))));
|
|
94
|
-
|
|
94
|
+
// I nomi utente possono mancare quando la lista viene ricostruita senza i partecipanti
|
|
95
|
+
// (es. refresh dopo la condivisione di un file): vengono risolti qui dagli ID
|
|
96
|
+
setFilteredFileItems(filtered.map(item => resolveFileItemUserNames(item, allUsers)));
|
|
95
97
|
};
|
|
96
98
|
filterItems();
|
|
97
|
-
}, [searchText, selectedFolder]);
|
|
99
|
+
}, [searchText, selectedFolder, allUsers]);
|
|
98
100
|
// Callback to handle the "back" action (closing the draft list view)
|
|
99
101
|
const onBackCallback = useCallback(() => {
|
|
100
102
|
setOpenDraftList(false);
|
|
@@ -26,6 +26,7 @@ export interface FileItem {
|
|
|
26
26
|
version?: number;
|
|
27
27
|
isSigned?: number;
|
|
28
28
|
}
|
|
29
|
+
export declare const resolveFileItemUserNames: (item: FileItem, users: Array<UserDescriptor>) => FileItem;
|
|
29
30
|
export interface TMFileManagerTreeViewDirectory {
|
|
30
31
|
id: number;
|
|
31
32
|
text: string;
|
|
@@ -12,6 +12,22 @@ export const isSigned = (ext) => {
|
|
|
12
12
|
const parts = normalized.split('.');
|
|
13
13
|
return parts.some(part => SIGNATURE_EXTENSIONS.has(part));
|
|
14
14
|
};
|
|
15
|
+
// Completa i nomi utente (autore e utente di check-out) di un FileItem quando mancano, risolvendoli dagli ID.
|
|
16
|
+
export const resolveFileItemUserNames = (item, users) => {
|
|
17
|
+
if (!users || users.length === 0)
|
|
18
|
+
return item;
|
|
19
|
+
const isValidUserID = (id) => id !== undefined && Number.isFinite(id) && id > 0;
|
|
20
|
+
const needsUpdaterName = !item.updaterName && isValidUserID(item.updaterID);
|
|
21
|
+
const needsCheckOutUserName = !item.checkOutUserName && isValidUserID(item.checkOutUserID);
|
|
22
|
+
if (!needsUpdaterName && !needsCheckOutUserName)
|
|
23
|
+
return item;
|
|
24
|
+
const resolved = { ...item };
|
|
25
|
+
if (needsUpdaterName)
|
|
26
|
+
resolved.updaterName = users.find(user => user.id === item.updaterID)?.name ?? item.updaterName;
|
|
27
|
+
if (needsCheckOutUserName)
|
|
28
|
+
resolved.checkOutUserName = users.find(user => user.id === item.checkOutUserID)?.name ?? item.checkOutUserName;
|
|
29
|
+
return resolved;
|
|
30
|
+
};
|
|
15
31
|
export var TMFileManagerPageSize;
|
|
16
32
|
(function (TMFileManagerPageSize) {
|
|
17
33
|
TMFileManagerPageSize[TMFileManagerPageSize["Small"] = 30] = "Small";
|
|
@@ -14,6 +14,17 @@ interface ITMModal {
|
|
|
14
14
|
askClosingConfirm?: boolean;
|
|
15
15
|
showCloseButton?: boolean;
|
|
16
16
|
showHeader?: boolean;
|
|
17
|
+
/** Se true, ricentra il popup ogni volta che cambiano width/height, annullando lo spostamento manuale */
|
|
18
|
+
recenterOnResize?: boolean;
|
|
19
|
+
/** Contenuto aggiuntivo nella barra del titolo, prima del pulsante di chiusura (es. un menu di opzioni) */
|
|
20
|
+
titleActions?: React.ReactNode;
|
|
21
|
+
/** Dimensioni in px al termine di un ridimensionamento manuale */
|
|
22
|
+
onResized?: (size: {
|
|
23
|
+
width: number;
|
|
24
|
+
height: number;
|
|
25
|
+
}) => void;
|
|
26
|
+
/** A ogni cambio di valore le dimensioni tornano a `width`/`height`, anche se invariate */
|
|
27
|
+
sizeResetToken?: number | string;
|
|
17
28
|
}
|
|
18
29
|
declare const TMModal: React.FC<ITMModal>;
|
|
19
30
|
export default TMModal;
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useState, useEffect, useRef } from 'react';
|
|
3
3
|
import { Popup } from 'devextreme-react';
|
|
4
|
+
import { ToolbarItem } from 'devextreme-react/popup';
|
|
4
5
|
import styled from 'styled-components';
|
|
5
6
|
import { TMCard } from './TMLayout';
|
|
6
7
|
import { FontSize, TMColors } from '../../utils/theme';
|
|
7
8
|
import { IconWindowMaximize, IconWindowMinimize, svgToString } from '../../helper';
|
|
9
|
+
/** Valori di `height` con cui il popup si dimensiona sul contenuto (vedi applyContentFillLayout) */
|
|
10
|
+
const CONTENT_DRIVEN_HEIGHTS = ['auto', 'max-content', 'min-content', 'fit-content'];
|
|
8
11
|
const StyledModal = styled.div `
|
|
9
12
|
width: ${props => props.$width};
|
|
10
13
|
height: ${props => props.$height};
|
|
@@ -39,10 +42,13 @@ const StyledModalContext = styled.div `
|
|
|
39
42
|
overflow: auto;
|
|
40
43
|
height: 100%;
|
|
41
44
|
`;
|
|
42
|
-
const TMModal = ({ resizable = true, expandable = false, isModal = true, title = '', toolbar, onClose, children, width = '100%', height = '100%', fontSize = FontSize.defaultFontSize, hidePopup = true, askClosingConfirm = false, showCloseButton = true, showHeader = true }) => {
|
|
45
|
+
const TMModal = ({ resizable = true, expandable = false, isModal = true, title = '', toolbar, onClose, children, width = '100%', height = '100%', fontSize = FontSize.defaultFontSize, hidePopup = true, askClosingConfirm = false, showCloseButton = true, showHeader = true, recenterOnResize = false, titleActions, onResized, sizeResetToken }) => {
|
|
43
46
|
const popupRef = useRef(null);
|
|
44
47
|
const [initialWidth, setInitialWidth] = useState(width);
|
|
45
48
|
const [initialHeight, setInitialHeight] = useState(height);
|
|
49
|
+
// Posizione controllata: un nuovo riferimento oggetto forza DevExtreme a riposizionare (ricentrare).
|
|
50
|
+
// Lasciata invariata durante il drag, così lo spostamento manuale viene preservato
|
|
51
|
+
const [position, setPosition] = useState(undefined);
|
|
46
52
|
const [showPopup, setShowPopup] = useState(false);
|
|
47
53
|
const [isResizing, setIsResizing] = useState(false);
|
|
48
54
|
const [isFullScreen, setIsFullScreen] = useState(false);
|
|
@@ -51,6 +57,26 @@ const TMModal = ({ resizable = true, expandable = false, isModal = true, title =
|
|
|
51
57
|
const DEFAULT_MIN_HEIGHT = 400;
|
|
52
58
|
const [minWidth, setMinWidth] = useState(0);
|
|
53
59
|
const [minHeight, setMinHeight] = useState(0);
|
|
60
|
+
/**
|
|
61
|
+
* DevExtreme fissa in px l'altezza del contenuto del popup: se le dimensioni cambiano a popup
|
|
62
|
+
* già aperto (es. cambio step) resta quella del passo precedente e sotto rimane spazio vuoto.
|
|
63
|
+
* Come figlio flex il contenuto riempie il popup senza dipendere da quel calcolo.
|
|
64
|
+
* Non si applica alle altezze dimensionate sul contenuto (`auto`, `max-content`, ...):
|
|
65
|
+
* lì è il contenuto a dare l'altezza al popup e non c'è spazio da riempire.
|
|
66
|
+
*/
|
|
67
|
+
const applyContentFillLayout = () => {
|
|
68
|
+
const content = popupRef.current?.instance()?.content();
|
|
69
|
+
const overlayContent = content?.parentElement;
|
|
70
|
+
if (!content || !overlayContent)
|
|
71
|
+
return;
|
|
72
|
+
const fill = !CONTENT_DRIVEN_HEIGHTS.some(value => initialHeight.trim().startsWith(value));
|
|
73
|
+
overlayContent.style.display = fill ? 'flex' : '';
|
|
74
|
+
overlayContent.style.flexDirection = fill ? 'column' : '';
|
|
75
|
+
content.style.flex = fill ? '1 1 auto' : '';
|
|
76
|
+
// Testata e toolbar inferiore restano alla loro altezza naturale
|
|
77
|
+
overlayContent.querySelectorAll(':scope > .dx-popup-title, :scope > .dx-popup-bottom')
|
|
78
|
+
.forEach(el => { el.style.flexShrink = fill ? '0' : ''; });
|
|
79
|
+
};
|
|
54
80
|
/**
|
|
55
81
|
* Gestore chiamato quando il popup viene mostrato.
|
|
56
82
|
* Calcola e imposta le dimensioni minime del popup in base allo spazio disponibile.
|
|
@@ -59,6 +85,7 @@ const TMModal = ({ resizable = true, expandable = false, isModal = true, title =
|
|
|
59
85
|
const handleShown = () => {
|
|
60
86
|
// Usa requestAnimationFrame per attendere il completamento del rendering
|
|
61
87
|
requestAnimationFrame(() => {
|
|
88
|
+
applyContentFillLayout();
|
|
62
89
|
if (popupRef.current && popupRef.current.instance) {
|
|
63
90
|
const instance = popupRef.current.instance();
|
|
64
91
|
if (instance) {
|
|
@@ -85,14 +112,27 @@ const TMModal = ({ resizable = true, expandable = false, isModal = true, title =
|
|
|
85
112
|
useEffect(() => {
|
|
86
113
|
setInitialWidth(width);
|
|
87
114
|
setInitialHeight(height);
|
|
88
|
-
|
|
115
|
+
// Al cambio di dimensioni (es. cambio step) ricentra il popup con un nuovo riferimento oggetto,
|
|
116
|
+
// annullando l'eventuale spostamento manuale precedente
|
|
117
|
+
if (recenterOnResize)
|
|
118
|
+
setPosition({ my: 'center', at: 'center', of: window });
|
|
119
|
+
// Il token annulla un ridimensionamento manuale, che non cambia width/height
|
|
120
|
+
}, [width, height, recenterOnResize, sizeResetToken]);
|
|
121
|
+
// Dimensioni, testata e schermo pieno fanno rigenerare geometria ed elementi del popup:
|
|
122
|
+
// il layout flex del contenuto va riapplicato dopo il ridisegno (vedi applyContentFillLayout)
|
|
123
|
+
useEffect(() => {
|
|
124
|
+
const rafId = requestAnimationFrame(() => applyContentFillLayout());
|
|
125
|
+
return () => cancelAnimationFrame(rafId);
|
|
126
|
+
}, [initialWidth, initialHeight, title, isFullScreen, showPopup]);
|
|
89
127
|
const handleResizeStart = () => {
|
|
90
128
|
setIsResizing(true);
|
|
91
129
|
};
|
|
92
130
|
const handleResizeEnd = (e) => {
|
|
93
131
|
setIsResizing(false);
|
|
94
|
-
|
|
95
|
-
|
|
132
|
+
// Le dimensioni arrivano in px: senza unità non sarebbero un valore CSS valido
|
|
133
|
+
setInitialWidth(`${e.width}px`);
|
|
134
|
+
setInitialHeight(`${e.height}px`);
|
|
135
|
+
onResized?.({ width: e.width, height: e.height });
|
|
96
136
|
};
|
|
97
137
|
const onHiding = (e) => {
|
|
98
138
|
if (askClosingConfirm)
|
|
@@ -101,15 +141,9 @@ const TMModal = ({ resizable = true, expandable = false, isModal = true, title =
|
|
|
101
141
|
setShowPopup(false);
|
|
102
142
|
onClose && onClose();
|
|
103
143
|
};
|
|
104
|
-
return (_jsx(_Fragment, { children: isModal ? (
|
|
105
|
-
{
|
|
106
|
-
widget: 'dxButton',
|
|
107
|
-
location: 'after',
|
|
108
|
-
options: {
|
|
144
|
+
return (_jsx(_Fragment, { children: isModal ? (_jsxs(Popup, { ref: popupRef, showCloseButton: showHeader ? showCloseButton : false, showTitle: showHeader, animation: undefined, minWidth: minWidth, minHeight: minHeight, maxHeight: '95%', maxWidth: '95%', dragEnabled: !isResizing && !isFullScreen, resizeEnabled: resizable && !isFullScreen, position: expandable && isFullScreen ? { my: 'center', at: 'center', of: window } : position, width: expandable && isFullScreen ? '95%' : initialWidth, height: expandable && isFullScreen ? '95%' : initialHeight, title: title, visible: showPopup, onShown: handleShown, onResizeStart: handleResizeStart, onResizeEnd: handleResizeEnd, onHiding: onHiding, children: [showHeader && titleActions && (_jsx(ToolbarItem, { toolbar: 'top', location: 'after', render: () => _jsx(_Fragment, { children: titleActions }) })), showHeader && expandable && (_jsx(ToolbarItem, { toolbar: 'top', location: 'after', widget: 'dxButton', options: {
|
|
109
145
|
icon: isFullScreen ? svgToString(_jsx(IconWindowMinimize, {})) : svgToString(_jsx(IconWindowMaximize, {})),
|
|
110
146
|
onClick: () => setIsFullScreen(!isFullScreen)
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
] : undefined, children: _jsxs("div", { style: { display: 'flex', flexDirection: 'column', height: '100%', width: '100%' }, children: [toolbar && (_jsx(StyledModalToolbar, { children: toolbar })), _jsx("div", { style: { flex: 1, overflow: 'hidden', minHeight: 0 }, children: _jsx(TMCard, { showBorder: false, padding: false, scrollY: true, children: children }) })] }) })) : (_jsxs(StyledModal, { "$isModal": isModal, className: "temp-modal", "$fontSize": fontSize, "$width": initialWidth, "$height": initialHeight, children: [toolbar ? _jsx(StyledModalToolbar, { children: toolbar }) : _jsx(_Fragment, {}), _jsx(StyledModalContext, { children: children })] })) }));
|
|
147
|
+
} })), _jsxs("div", { style: { display: 'flex', flexDirection: 'column', height: '100%', width: '100%' }, children: [toolbar && (_jsx(StyledModalToolbar, { children: toolbar })), _jsx("div", { style: { flex: 1, overflow: 'hidden', minHeight: 0 }, children: _jsx(TMCard, { showBorder: false, padding: false, scrollY: true, children: children }) })] })] })) : (_jsxs(StyledModal, { "$isModal": isModal, className: "temp-modal", "$fontSize": fontSize, "$width": initialWidth, "$height": initialHeight, children: [toolbar ? _jsx(StyledModalToolbar, { children: toolbar }) : _jsx(_Fragment, {}), _jsx(StyledModalContext, { children: children })] })) }));
|
|
114
148
|
};
|
|
115
149
|
export default TMModal;
|