@topconsultnpm/sdkui-react 6.22.0-dev2.4 → 6.22.0-dev2.41

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.
Files changed (124) hide show
  1. package/lib/components/NewComponents/ContextMenu/TMContextMenu.js +1 -1
  2. package/lib/components/NewComponents/FloatingMenuBar/TMFloatingMenuBar.js +1 -1
  3. package/lib/components/base/TMButton.d.ts +13 -0
  4. package/lib/components/base/TMButton.js +137 -5
  5. package/lib/components/base/TMDataGrid.d.ts +2 -0
  6. package/lib/components/base/TMDataGrid.js +81 -8
  7. package/lib/components/base/TMEditorBase.d.ts +2 -0
  8. package/lib/components/base/TMFileManager.js +5 -3
  9. package/lib/components/base/TMFileManagerUtils.d.ts +1 -0
  10. package/lib/components/base/TMFileManagerUtils.js +16 -0
  11. package/lib/components/base/TMModal.d.ts +11 -0
  12. package/lib/components/base/TMModal.js +47 -13
  13. package/lib/components/base/TMPanel.d.ts +1 -0
  14. package/lib/components/base/TMPanel.js +79 -20
  15. package/lib/components/base/TMPopUp.js +3 -0
  16. package/lib/components/base/TMTooltip.d.ts +2 -1
  17. package/lib/components/base/TMTooltip.js +23 -6
  18. package/lib/components/base/TMTreeView.js +108 -67
  19. package/lib/components/choosers/TMDcmtTypeChooser.js +4 -3
  20. package/lib/components/choosers/TMDistinctValues.d.ts +9 -7
  21. package/lib/components/choosers/TMDistinctValues.js +149 -142
  22. package/lib/components/choosers/TMMetadataChooser.js +1 -1
  23. package/lib/components/choosers/TMQuickSearchInfo.d.ts +12 -0
  24. package/lib/components/choosers/TMQuickSearchInfo.js +279 -0
  25. package/lib/components/choosers/TMQuickSearchSettingsForm.d.ts +16 -0
  26. package/lib/components/choosers/TMQuickSearchSettingsForm.js +94 -0
  27. package/lib/components/choosers/TMSelectedValuesSummary.d.ts +13 -0
  28. package/lib/components/choosers/TMSelectedValuesSummary.js +91 -0
  29. package/lib/components/editors/TMDropDown.d.ts +3 -0
  30. package/lib/components/editors/TMDropDown.js +197 -5
  31. package/lib/components/editors/TMMetadataEditor.d.ts +5 -1
  32. package/lib/components/editors/TMMetadataEditor.js +8 -3
  33. package/lib/components/editors/TMTextBox.js +2 -2
  34. package/lib/components/features/documents/TMBatchUpdateForm.js +19 -5
  35. package/lib/components/features/documents/TMDcmtBlog.d.ts +3 -0
  36. package/lib/components/features/documents/TMDcmtBlog.js +4 -4
  37. package/lib/components/features/documents/TMDcmtForm.d.ts +7 -0
  38. package/lib/components/features/documents/TMDcmtForm.js +112 -45
  39. package/lib/components/features/documents/TMDcmtFormActionButtons.d.ts +11 -0
  40. package/lib/components/features/documents/TMDcmtFormActionButtons.js +74 -7
  41. package/lib/components/features/documents/TMDcmtPreview.js +10 -6
  42. package/lib/components/features/documents/TMMasterDetailDcmts.js +95 -33
  43. package/lib/components/features/documents/TMMasterInfoFields.d.ts +19 -0
  44. package/lib/components/features/documents/TMMasterInfoFields.js +186 -0
  45. package/lib/components/features/documents/TMMergeToPdfForm.d.ts +1 -2
  46. package/lib/components/features/documents/TMMergeToPdfForm.js +30 -5
  47. package/lib/components/features/documents/TMMultiMasterDetailDcmtsForm.d.ts +45 -0
  48. package/lib/components/features/documents/TMMultiMasterDetailDcmtsForm.js +323 -0
  49. package/lib/components/features/documents/TMMultiMasterDetailDcmtsUtils.d.ts +370 -0
  50. package/lib/components/features/documents/TMMultiMasterDetailDcmtsUtils.js +283 -0
  51. package/lib/components/features/documents/TMRelationViewer.d.ts +14 -1
  52. package/lib/components/features/documents/TMRelationViewer.js +69 -20
  53. package/lib/components/features/search/TMSearch.js +31 -18
  54. package/lib/components/features/search/TMSearchQueryEditor.js +5 -3
  55. package/lib/components/features/search/TMSearchQueryPanel.d.ts +2 -1
  56. package/lib/components/features/search/TMSearchQueryPanel.js +45 -9
  57. package/lib/components/features/search/TMSearchResult.d.ts +15 -0
  58. package/lib/components/features/search/TMSearchResult.js +509 -187
  59. package/lib/components/features/search/TMTreeSelector.js +18 -5
  60. package/lib/components/features/workflow/TMWorkflowPopup.d.ts +10 -0
  61. package/lib/components/features/workflow/TMWorkflowPopup.js +8 -4
  62. package/lib/components/layout/panelManager/TMPanelManagerContainer.d.ts +1 -0
  63. package/lib/components/layout/panelManager/TMPanelManagerContainer.js +24 -14
  64. package/lib/components/layout/panelManager/TMPanelManagerContext.js +68 -93
  65. package/lib/components/layout/panelManager/TMPanelWrapper.js +2 -2
  66. package/lib/components/layout/panelManager/types.d.ts +3 -0
  67. package/lib/components/layout/panelManager/utils.js +13 -11
  68. package/lib/components/viewers/TMMidViewer.js +4 -1
  69. package/lib/components/viewers/TMTidViewer.js +25 -11
  70. package/lib/devextreme-license.d.ts +1 -0
  71. package/lib/devextreme-license.js +3 -0
  72. package/lib/helper/SDKUI_Globals.d.ts +109 -0
  73. package/lib/helper/SDKUI_Globals.js +150 -0
  74. package/lib/helper/SDKUI_Localizator.d.ts +34 -0
  75. package/lib/helper/SDKUI_Localizator.js +340 -0
  76. package/lib/helper/TMIcons.d.ts +1 -0
  77. package/lib/helper/TMIcons.js +3 -0
  78. package/lib/helper/dataGridSearchHelper.d.ts +28 -0
  79. package/lib/helper/dataGridSearchHelper.js +51 -0
  80. package/lib/helper/dcmtsHelper.d.ts +20 -0
  81. package/lib/helper/dcmtsHelper.js +58 -1
  82. package/lib/helper/helpers.d.ts +20 -0
  83. package/lib/helper/helpers.js +55 -1
  84. package/lib/helper/index.d.ts +1 -0
  85. package/lib/helper/index.js +1 -0
  86. package/lib/helper/queryHelper.d.ts +2 -0
  87. package/lib/helper/queryHelper.js +3 -1
  88. package/lib/hooks/tmDistinctValuesGridHelper.d.ts +16 -0
  89. package/lib/hooks/tmDistinctValuesGridHelper.js +31 -0
  90. package/lib/hooks/useArchiveListForm.d.ts +39 -0
  91. package/lib/hooks/useArchiveListForm.js +46 -0
  92. package/lib/hooks/useCaseFlowApprove.d.ts +56 -0
  93. package/lib/hooks/useCaseFlowApprove.js +157 -0
  94. package/lib/hooks/useDataUserIdItem.d.ts +1 -0
  95. package/lib/hooks/useDataUserIdItem.js +1 -0
  96. package/lib/hooks/useDocumentOperations.d.ts +4 -0
  97. package/lib/hooks/useDocumentOperations.js +41 -7
  98. package/lib/hooks/useMultiMasterDetailDcmts.d.ts +113 -0
  99. package/lib/hooks/useMultiMasterDetailDcmts.js +760 -0
  100. package/lib/hooks/useRelatedDocuments.js +2 -26
  101. package/lib/hooks/useShowDossiersV5.d.ts +12 -0
  102. package/lib/hooks/useShowDossiersV5.js +37 -0
  103. package/lib/hooks/useTMDistinctValuesMetadataDisplay.d.ts +18 -0
  104. package/lib/hooks/useTMDistinctValuesMetadataDisplay.js +103 -0
  105. package/lib/hooks/useTMDistinctValuesQuickSearch.d.ts +49 -0
  106. package/lib/hooks/useTMDistinctValuesQuickSearch.js +307 -0
  107. package/lib/hooks/useTMDistinctValuesSelection.d.ts +42 -0
  108. package/lib/hooks/useTMDistinctValuesSelection.js +103 -0
  109. package/lib/hooks/useTMDistinctValuesSource.d.ts +29 -0
  110. package/lib/hooks/useTMDistinctValuesSource.js +105 -0
  111. package/lib/index.d.ts +2 -0
  112. package/lib/index.js +5 -2
  113. package/lib/services/caseflow/CaseFlowService.d.ts +236 -0
  114. package/lib/services/caseflow/CaseFlowService.js +693 -0
  115. package/lib/services/caseflow/CaseFlowTemplateCacheService.d.ts +28 -0
  116. package/lib/services/caseflow/CaseFlowTemplateCacheService.js +121 -0
  117. package/lib/services/caseflow/caseFlowNameCache.d.ts +32 -0
  118. package/lib/services/caseflow/caseFlowNameCache.js +116 -0
  119. package/lib/services/caseflow/caseFlowWorkItemUtils.d.ts +61 -0
  120. package/lib/services/caseflow/caseFlowWorkItemUtils.js +113 -0
  121. package/lib/services/index.d.ts +4 -0
  122. package/lib/services/index.js +4 -0
  123. package/lib/services/platform_services.d.ts +1 -1
  124. 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
- return (_jsx("div", { style: elementStyle, children: showTooltip ? _jsx(TMTooltip, { hideAfterDelay: true, content: renderTooltip(), children: renderedButton() }) : renderedButton() }));
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
- setCustomContextMenuRowKey(focusedRowKey);
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
- const id = focusedRowEnabled ? focusedRowKey : rowID;
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
- 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' : ''}`,
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 }), 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, { target: ".dx-data-row", items: processCustomContextMenuItems(customContextMenuItems, customContextMenuRowKey), externalControl: {
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
- setFilteredFileItems(filtered);
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 { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
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
- }, [width, height]);
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
- setInitialWidth(e.width.toString());
95
- setInitialHeight(e.height.toString());
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 ? (_jsx(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 } : undefined, width: expandable && isFullScreen ? '95%' : initialWidth, height: expandable && isFullScreen ? '95%' : initialHeight, title: title, visible: showPopup, onShown: handleShown, onResizeStart: handleResizeStart, onResizeEnd: handleResizeEnd, onHiding: onHiding, toolbarItems: showHeader && expandable ? [
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;
@@ -10,6 +10,7 @@ export interface ITMPanelProps {
10
10
  children?: React.ReactNode;
11
11
  showHeader?: boolean;
12
12
  title?: React.ReactNode;
13
+ enableTitleMarquee?: boolean;
13
14
  displayedItemsCount?: number;
14
15
  totalItems?: number;
15
16
  toolbar?: any;