@topconsultnpm/sdkui-react 6.19.0-dev2.16 → 6.19.0-dev2.18

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 (36) hide show
  1. package/lib/components/NewComponents/ContextMenu/TMContextMenu.d.ts +4 -0
  2. package/lib/components/NewComponents/ContextMenu/TMContextMenu.js +187 -0
  3. package/lib/components/NewComponents/ContextMenu/hooks.d.ts +11 -0
  4. package/lib/components/NewComponents/ContextMenu/hooks.js +48 -0
  5. package/lib/components/NewComponents/ContextMenu/index.d.ts +2 -0
  6. package/lib/components/NewComponents/ContextMenu/index.js +1 -0
  7. package/lib/components/NewComponents/ContextMenu/styles.d.ts +27 -0
  8. package/lib/components/NewComponents/ContextMenu/styles.js +308 -0
  9. package/lib/components/NewComponents/ContextMenu/types.d.ts +26 -0
  10. package/lib/components/NewComponents/ContextMenu/types.js +1 -0
  11. package/lib/components/NewComponents/FloatingMenuBar/TMFloatingMenuBar.d.ts +4 -0
  12. package/lib/components/NewComponents/FloatingMenuBar/TMFloatingMenuBar.js +370 -0
  13. package/lib/components/NewComponents/FloatingMenuBar/index.d.ts +2 -0
  14. package/lib/components/NewComponents/FloatingMenuBar/index.js +2 -0
  15. package/lib/components/NewComponents/FloatingMenuBar/styles.d.ts +38 -0
  16. package/lib/components/NewComponents/FloatingMenuBar/styles.js +267 -0
  17. package/lib/components/NewComponents/FloatingMenuBar/types.d.ts +30 -0
  18. package/lib/components/NewComponents/FloatingMenuBar/types.js +1 -0
  19. package/lib/components/NewComponents/Notification/Notification.d.ts +4 -0
  20. package/lib/components/NewComponents/Notification/Notification.js +60 -0
  21. package/lib/components/NewComponents/Notification/NotificationContainer.d.ts +8 -0
  22. package/lib/components/NewComponents/Notification/NotificationContainer.js +33 -0
  23. package/lib/components/NewComponents/Notification/index.d.ts +2 -0
  24. package/lib/components/NewComponents/Notification/index.js +2 -0
  25. package/lib/components/NewComponents/Notification/styles.d.ts +21 -0
  26. package/lib/components/NewComponents/Notification/styles.js +180 -0
  27. package/lib/components/NewComponents/Notification/types.d.ts +18 -0
  28. package/lib/components/NewComponents/Notification/types.js +1 -0
  29. package/lib/components/base/TMCustomButton.js +2 -2
  30. package/lib/components/base/TMDataGridExportForm.js +9 -3
  31. package/lib/components/features/documents/TMDcmtForm.js +2 -1
  32. package/lib/components/features/documents/TMRelationViewer.js +55 -22
  33. package/lib/components/features/search/TMSearchResult.js +3 -3
  34. package/lib/helper/TMUtils.d.ts +1 -4
  35. package/lib/helper/TMUtils.js +13 -9
  36. package/package.json +1 -1
@@ -0,0 +1,30 @@
1
+ import type { TMContextMenuItemProps } from '../ContextMenu';
2
+ export interface TMFloatingMenuItem {
3
+ id: string;
4
+ name: string;
5
+ icon: React.ReactNode;
6
+ onClick: () => void;
7
+ disabled?: boolean;
8
+ isPinned?: boolean;
9
+ originalMenuItem?: TMContextMenuItemProps;
10
+ }
11
+ export interface TMFloatingMenuBarProps {
12
+ containerRef: React.RefObject<HTMLElement | null>;
13
+ contextMenuItems?: TMContextMenuItemProps[];
14
+ storageKey?: string;
15
+ isConstrained?: boolean;
16
+ defaultPosition?: Position;
17
+ maxItems?: number;
18
+ }
19
+ export interface Position {
20
+ x: number;
21
+ y: number;
22
+ }
23
+ export interface TMFloatingMenuBarState {
24
+ position: Position;
25
+ isDragging: boolean;
26
+ isConfigMode: boolean;
27
+ orientation: 'horizontal' | 'vertical';
28
+ items: TMFloatingMenuItem[];
29
+ draggedItemIndex: number | null;
30
+ }
@@ -0,0 +1,4 @@
1
+ import React from 'react';
2
+ import type { NotificationProps } from './types';
3
+ declare const Notification: React.FC<NotificationProps>;
4
+ export default Notification;
@@ -0,0 +1,60 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useEffect, useRef } from 'react';
3
+ import * as S from './styles';
4
+ const Notification = ({ title, message, mode = 'info', position = 'top-right', duration = 3000, closable = false, stopOnMouseEnter = true, hasProgress = true, onClose, }) => {
5
+ const [state, setState] = useState({
6
+ visible: true,
7
+ isPaused: false,
8
+ progress: 100,
9
+ });
10
+ const timeoutRef = useRef(undefined);
11
+ const remainingTimeRef = useRef(duration);
12
+ const pauseTimeRef = useRef(0);
13
+ const closeNotification = () => {
14
+ setState(prev => ({ ...prev, visible: false }));
15
+ setTimeout(() => {
16
+ onClose?.();
17
+ }, 300);
18
+ };
19
+ useEffect(() => {
20
+ // Set up auto-close timer
21
+ timeoutRef.current = setTimeout(() => {
22
+ closeNotification();
23
+ }, duration);
24
+ return () => {
25
+ if (timeoutRef.current) {
26
+ clearTimeout(timeoutRef.current);
27
+ }
28
+ };
29
+ }, [duration]);
30
+ const handleMouseEnter = () => {
31
+ if (!stopOnMouseEnter)
32
+ return;
33
+ // Pause the timer
34
+ if (timeoutRef.current) {
35
+ clearTimeout(timeoutRef.current);
36
+ pauseTimeRef.current = Date.now();
37
+ }
38
+ setState(prev => ({ ...prev, isPaused: true }));
39
+ };
40
+ const handleMouseLeave = () => {
41
+ if (!stopOnMouseEnter)
42
+ return;
43
+ // Resume the timer with remaining time
44
+ const pauseDuration = Date.now() - pauseTimeRef.current;
45
+ remainingTimeRef.current = Math.max(0, remainingTimeRef.current - pauseDuration);
46
+ timeoutRef.current = setTimeout(() => {
47
+ closeNotification();
48
+ }, remainingTimeRef.current);
49
+ setState(prev => ({ ...prev, isPaused: false }));
50
+ };
51
+ const handleClose = (e) => {
52
+ e.stopPropagation();
53
+ if (timeoutRef.current) {
54
+ clearTimeout(timeoutRef.current);
55
+ }
56
+ closeNotification();
57
+ };
58
+ return (_jsxs(S.NotificationContainer, { "$position": position, "$mode": mode, "$visible": state.visible, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, role: "alert", "aria-live": "assertive", children: [_jsxs(S.NotificationContent, { children: [_jsx(S.NotificationTitle, { children: title }), _jsx(S.NotificationMessage, { children: message })] }), closable && (_jsx(S.CloseButton, { onClick: handleClose, "aria-label": "Close notification", children: "\u00D7" })), hasProgress && (_jsx(S.ProgressBar, { "$duration": duration, "$mode": mode, "$isPaused": state.isPaused }))] }));
59
+ };
60
+ export default Notification;
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ import type { NotificationPosition } from './types';
3
+ interface NotificationContainerProps {
4
+ position: NotificationPosition;
5
+ children: React.ReactNode;
6
+ }
7
+ declare const NotificationContainer: React.FC<NotificationContainerProps>;
8
+ export default NotificationContainer;
@@ -0,0 +1,33 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import styled from 'styled-components';
3
+ const Container = styled.div `
4
+ position: fixed;
5
+ z-index: 10002;
6
+ display: flex;
7
+ flex-direction: column;
8
+ gap: 12px;
9
+ pointer-events: none;
10
+
11
+ ${props => {
12
+ const isTop = props.$position.startsWith('top');
13
+ const isBottom = props.$position.startsWith('bottom');
14
+ const isLeft = props.$position.endsWith('left');
15
+ const isRight = props.$position.endsWith('right');
16
+ const isCenter = props.$position.endsWith('center');
17
+ return `
18
+ ${isTop ? 'top: 24px;' : ''}
19
+ ${isBottom ? 'bottom: 24px;' : ''}
20
+ ${isLeft ? 'left: 24px;' : ''}
21
+ ${isRight ? 'right: 24px;' : ''}
22
+ ${isCenter ? 'left: 50%; transform: translateX(-50%);' : ''}
23
+ `;
24
+ }}
25
+
26
+ & > * {
27
+ pointer-events: auto;
28
+ }
29
+ `;
30
+ const NotificationContainer = ({ position, children }) => {
31
+ return _jsx(Container, { "$position": position, children: children });
32
+ };
33
+ export default NotificationContainer;
@@ -0,0 +1,2 @@
1
+ export { default } from './Notification';
2
+ export * from './types';
@@ -0,0 +1,2 @@
1
+ export { default } from './Notification';
2
+ export * from './types';
@@ -0,0 +1,21 @@
1
+ import type { NotificationMode, NotificationPosition } from './types';
2
+ export declare const NotificationContainer: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components/dist/types").Substitute<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, {
3
+ $position: NotificationPosition;
4
+ $mode: NotificationMode;
5
+ $visible: boolean;
6
+ }>> & string;
7
+ export declare const NotificationContent: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>> & string;
8
+ export declare const NotificationTitle: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>> & string;
9
+ export declare const NotificationMessage: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>> & string;
10
+ export declare const CloseButton: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, never>> & string;
11
+ export declare const ProgressBar: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components/dist/types").Substitute<import("styled-components/dist/types").Substitute<import("styled-components/dist/types").Substitute<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & {
12
+ ref?: ((instance: HTMLDivElement | null) => void | import("react").DO_NOT_USE_OR_YOU_WILL_BE_FIRED_CALLBACK_REF_RETURN_VALUES[keyof import("react").DO_NOT_USE_OR_YOU_WILL_BE_FIRED_CALLBACK_REF_RETURN_VALUES]) | import("react").RefObject<HTMLDivElement> | null | undefined;
13
+ }>, {
14
+ $duration: number;
15
+ $mode: NotificationMode;
16
+ $isPaused: boolean;
17
+ }>, {
18
+ $duration: number;
19
+ $mode: NotificationMode;
20
+ $isPaused: boolean;
21
+ }>> & string;
@@ -0,0 +1,180 @@
1
+ import styled, { keyframes } from 'styled-components';
2
+ const slideInFromTop = keyframes `
3
+ from {
4
+ opacity: 0;
5
+ transform: translateY(-100%);
6
+ }
7
+ to {
8
+ opacity: 1;
9
+ transform: translateY(0);
10
+ }
11
+ `;
12
+ const slideInFromBottom = keyframes `
13
+ from {
14
+ opacity: 0;
15
+ transform: translateY(100%);
16
+ }
17
+ to {
18
+ opacity: 1;
19
+ transform: translateY(0);
20
+ }
21
+ `;
22
+ const slideOut = keyframes `
23
+ from {
24
+ opacity: 1;
25
+ transform: scale(1);
26
+ }
27
+ to {
28
+ opacity: 0;
29
+ transform: scale(0.9);
30
+ }
31
+ `;
32
+ const getModeColors = (mode) => {
33
+ const colors = {
34
+ success: {
35
+ bg: '#10b981',
36
+ bgDark: '#059669',
37
+ border: '#34d399',
38
+ text: '#ffffff',
39
+ },
40
+ error: {
41
+ bg: '#ef4444',
42
+ bgDark: '#dc2626',
43
+ border: '#f87171',
44
+ text: '#ffffff',
45
+ },
46
+ warning: {
47
+ bg: '#f59e0b',
48
+ bgDark: '#d97706',
49
+ border: '#fbbf24',
50
+ text: '#ffffff',
51
+ },
52
+ info: {
53
+ bg: '#3b82f6',
54
+ bgDark: '#2563eb',
55
+ border: '#60a5fa',
56
+ text: '#ffffff',
57
+ },
58
+ };
59
+ return colors[mode];
60
+ };
61
+ export const NotificationContainer = styled.div `
62
+ position: relative;
63
+ z-index: 1;
64
+ min-width: 320px;
65
+ max-width: 420px;
66
+ background: ${props => getModeColors(props.$mode).bg};
67
+ border-radius: 12px;
68
+ box-shadow: 0 12px 40px rgba(0, 0, 0, 0.2),
69
+ 0 4px 12px rgba(0, 0, 0, 0.15);
70
+ padding: 16px 20px;
71
+ animation: ${props => {
72
+ if (!props.$visible)
73
+ return slideOut;
74
+ return props.$position.startsWith('top') ? slideInFromTop : slideInFromBottom;
75
+ }} 0.3s cubic-bezier(0.4, 0, 0.2, 1) forwards;
76
+ backdrop-filter: blur(10px);
77
+ border: 2px solid ${props => getModeColors(props.$mode).border};
78
+ color: ${props => getModeColors(props.$mode).text};
79
+ overflow: hidden;
80
+
81
+ [data-theme='dark'] & {
82
+ background: ${props => getModeColors(props.$mode).bgDark};
83
+ box-shadow: 0 12px 40px rgba(0, 0, 0, 0.4),
84
+ 0 4px 12px rgba(0, 0, 0, 0.3);
85
+ }
86
+
87
+ @media (max-width: 768px) {
88
+ min-width: 280px;
89
+ max-width: calc(100vw - 48px);
90
+ }
91
+ `;
92
+ export const NotificationContent = styled.div `
93
+ display: flex;
94
+ flex-direction: column;
95
+ gap: 6px;
96
+ padding-right: 24px;
97
+ `;
98
+ export const NotificationTitle = styled.div `
99
+ font-size: 16px;
100
+ font-weight: 600;
101
+ line-height: 1.4;
102
+ letter-spacing: -0.01em;
103
+ `;
104
+ export const NotificationMessage = styled.div `
105
+ font-size: 14px;
106
+ font-weight: 400;
107
+ line-height: 1.5;
108
+ opacity: 0.95;
109
+ `;
110
+ export const CloseButton = styled.button `
111
+ position: absolute;
112
+ top: 12px;
113
+ right: 12px;
114
+ background: transparent;
115
+ border: none;
116
+ color: inherit;
117
+ cursor: pointer;
118
+ width: 24px;
119
+ height: 24px;
120
+ display: flex;
121
+ align-items: center;
122
+ justify-content: center;
123
+ border-radius: 6px;
124
+ transition: all 0.15s ease;
125
+ font-size: 18px;
126
+ line-height: 1;
127
+ padding: 0;
128
+ opacity: 0.8;
129
+
130
+ &:hover {
131
+ opacity: 1;
132
+ background: rgba(255, 255, 255, 0.2);
133
+ transform: scale(1.1);
134
+ }
135
+
136
+ &:active {
137
+ transform: scale(0.95);
138
+ }
139
+
140
+ &:focus {
141
+ outline: 2px solid rgba(255, 255, 255, 0.5);
142
+ outline-offset: 2px;
143
+ }
144
+ `;
145
+ export const ProgressBar = styled.div.attrs(props => ({
146
+ style: {
147
+ animationDuration: `${props.$duration}ms`,
148
+ },
149
+ })) `
150
+ position: absolute;
151
+ bottom: 0;
152
+ left: 0;
153
+ height: 4px;
154
+ width: 100%;
155
+ background: ${props => getModeColors(props.$mode).border};
156
+ border-radius: 0 0 0 10px;
157
+ box-shadow: 0 0 8px ${props => getModeColors(props.$mode).border};
158
+ transform-origin: left;
159
+ animation: progress-shrink linear forwards;
160
+ animation-play-state: ${props => props.$isPaused ? 'paused' : 'running'};
161
+
162
+ @keyframes progress-shrink {
163
+ from {
164
+ transform: scaleX(1);
165
+ }
166
+ to {
167
+ transform: scaleX(0);
168
+ }
169
+ }
170
+
171
+ &::after {
172
+ content: '';
173
+ position: absolute;
174
+ top: 0;
175
+ right: 0;
176
+ width: 20px;
177
+ height: 100%;
178
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.4));
179
+ }
180
+ `;
@@ -0,0 +1,18 @@
1
+ export type NotificationMode = 'warning' | 'info' | 'error' | 'success';
2
+ export type NotificationPosition = 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right';
3
+ export interface NotificationProps {
4
+ title: string;
5
+ message: string;
6
+ mode?: NotificationMode;
7
+ position?: NotificationPosition;
8
+ duration?: number;
9
+ closable?: boolean;
10
+ stopOnMouseEnter?: boolean;
11
+ hasProgress?: boolean;
12
+ onClose?: () => void;
13
+ }
14
+ export interface NotificationState {
15
+ visible: boolean;
16
+ isPaused: boolean;
17
+ progress: number;
18
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -8,6 +8,7 @@ const IframeContainer = styled.div `
8
8
  display: flex;
9
9
  height: 100%;
10
10
  flex-direction: column;
11
+ padding-left: 15px;
11
12
  `;
12
13
  const StyledIframe = styled.iframe `
13
14
  border: none;
@@ -36,7 +37,6 @@ const TMCustomButton = (props) => {
36
37
  const mergedAttributes = { ...attributes, selectedItems: selectedItems };
37
38
  iframeRef.current.contentWindow.postMessage({
38
39
  "options": mergedAttributes,
39
- "selectedItems": selectedItems,
40
40
  "session": SDK_Globals.tmSession
41
41
  }, getTargetOrigin(scriptUrl));
42
42
  }
@@ -58,6 +58,6 @@ const TMCustomButton = (props) => {
58
58
  onClose?.();
59
59
  }
60
60
  }, [isModal, scriptUrl, onClose]);
61
- return isModal ? (_jsx(TMModal, { title: button.title, width: '60%', height: '60%', resizable: true, onClose: onClose, children: _jsxs(IframeContainer, { children: [error && _jsx("div", { children: "Si \u00E8 verificato un errore nel caricamento del contenuto." }), !error && _jsx(StyledIframe, { ref: iframeRef, loading: 'lazy', onLoad: handleLoad, onError: handleError, src: scriptUrl })] }) })) : null;
61
+ return isModal ? (_jsx(TMModal, { title: button.title, width: '60%', height: '70%', resizable: true, onClose: onClose, children: _jsxs(IframeContainer, { children: [error && _jsx("div", { children: "Si \u00E8 verificato un errore nel caricamento del contenuto." }), !error && _jsx(StyledIframe, { ref: iframeRef, loading: 'lazy', onLoad: handleLoad, onError: handleError, src: scriptUrl })] }) })) : null;
62
62
  };
63
63
  export default TMCustomButton;
@@ -64,7 +64,7 @@ const TMDataGridExportForm = (props) => {
64
64
  // Retrieve columns from the search result, or use an empty array if not available
65
65
  const columns = searchResult?.dtdResult?.columns ?? [];
66
66
  // If exportDescriptionsForDataLists is true, build a map of values to labels for the columns; otherwise use an empty Map
67
- const { valueToNameMap, captions } = exportDescriptionsForDataLists ? await buildValueToLabelMapFromDataColumns(columns) : { valueToNameMap: new Map(), captions: new Set() };
67
+ const valueToNameMap = exportDescriptionsForDataLists ? await buildValueToLabelMapFromDataColumns(columns) : new Map();
68
68
  // Create a Set from selectedRowKeys for efficient lookup
69
69
  const selectedSet = new Set(selectedRowKeys);
70
70
  // If exporting only selected rows, filter the dataSource accordingly; otherwise use the full dataSource
@@ -82,8 +82,14 @@ const TMDataGridExportForm = (props) => {
82
82
  const getValue = (col, value) => {
83
83
  // Replace raw value with corresponding label from the map if applicable
84
84
  let result = value;
85
- if (exportDescriptionsForDataLists && col.caption && captions.has(col.caption.toString())) {
86
- result = valueToNameMap.get(value) ?? value;
85
+ if (exportDescriptionsForDataLists && col.dataField && valueToNameMap.size > 0) {
86
+ const mapForField = valueToNameMap.get(col.dataField);
87
+ if (mapForField) {
88
+ result = mapForField.get(value) ?? value;
89
+ }
90
+ else {
91
+ result = value;
92
+ }
87
93
  }
88
94
  // If the column is a datetime type, attempt to format it as a locale date string
89
95
  if (col.dataType === 'datetime' && result) {
@@ -8,7 +8,7 @@ import { DownloadTypes, FormModes } from '../../../ts';
8
8
  import { DeviceType, useDeviceType } from '../../base/TMDeviceProvider';
9
9
  import { useDcmtOperations } from '../../../hooks/useDcmtOperations';
10
10
  import { getWorkItemSetIDAsync, handleArchiveVisibility, searchResultToMetadataValues } from '../../../helper/queryHelper';
11
- import { genUniqueId, IconShow, SDKUI_Localizator, updateMruTids, IconBoard, IconDcmtTypeSys, IconDetailDcmts, svgToString, IconDownload, calcIsModified, IconMenuVertical, Globalization, getListMaxItems, getSystemMetadata, IconBoxArchiveIn, IconClear, IconUndo, SDKUI_Globals, IconPreview, isTaskMoreInfo, IconWorkflow, IconSearch, deepCompare, IconCheck, IconActivity } from '../../../helper';
11
+ import { genUniqueId, IconShow, SDKUI_Localizator, updateMruTids, IconBoard, IconDcmtTypeSys, IconDetailDcmts, svgToString, IconDownload, calcIsModified, IconMenuVertical, Globalization, getListMaxItems, getSystemMetadata, IconBoxArchiveIn, IconClear, IconUndo, SDKUI_Globals, IconPreview, isTaskMoreInfo, IconWorkflow, IconSearch, deepCompare, IconCheck, IconActivity, TMImageLibrary } from '../../../helper';
12
12
  import { hasDetailRelations, hasMasterRelations, isXMLFileExt } from '../../../helper/dcmtsHelper';
13
13
  import { Gutters, TMColors } from '../../../utils/theme';
14
14
  import { StyledFormButtonsContainer, StyledLoadingContainer, StyledModalContainer, StyledReferenceButton, StyledSpinner, StyledToolbarCardContainer } from '../../base/Styled';
@@ -480,6 +480,7 @@ const TMDcmtForm = ({ allTasks = [], getAllTasks, deleteTaskByIdsCallback, addTa
480
480
  const customButtonsItems = customButtonsLayout.customButtons
481
481
  .filter((customButton) => customButton.isForUpdate && customButton.isForUpdate > 0)
482
482
  .map((customButton) => ({
483
+ icon: svgToString(TMImageLibrary({ imageID: customButton.glyphID, showPath: true })),
483
484
  text: customButton.title || 'Bottone personalizzato',
484
485
  onClick: () => setCustomButton(customButton)
485
486
  }));
@@ -142,12 +142,16 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
142
142
  const [treeData, setTreeData] = useState([]);
143
143
  const [showZeroDcmts, setShowZeroDcmts] = useState(initialShowZeroDcmts);
144
144
  const [staticItemsState, setStaticItemsState] = useState([]);
145
- // Wait Panel State (only used if allowWaitPanel is true)
146
145
  const [showWaitPanel, setShowWaitPanel] = useState(false);
147
146
  const [waitPanelTextPrimary, setWaitPanelTextPrimary] = useState('');
148
147
  const [waitPanelValuePrimary, setWaitPanelValuePrimary] = useState(0);
149
148
  const [waitPanelMaxValuePrimary, setWaitPanelMaxValuePrimary] = useState(0);
150
149
  const [abortController] = useState(new AbortController());
150
+ const [showExpansionWaitPanel, setShowExpansionWaitPanel] = useState(false);
151
+ const [expansionWaitPanelText, setExpansionWaitPanelText] = useState('');
152
+ const [expansionWaitPanelValue, setExpansionWaitPanelValue] = useState(0);
153
+ const [expansionWaitPanelMaxValue, setExpansionWaitPanelMaxValue] = useState(0);
154
+ const [expansionAbortController, setExpansionAbortController] = useState(undefined);
151
155
  // Ref to track last loaded input to prevent unnecessary reloads
152
156
  const lastLoadedInputRef = React.useRef('');
153
157
  // Ref to track if user has manually expanded/collapsed static items
@@ -560,30 +564,55 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
560
564
  // If container is already loaded, return items
561
565
  if (node.isLoaded)
562
566
  return node.items;
563
- // Load sub-documents for each document in the container
564
- const newItems = [];
565
- for (const dcmt of node.items ?? []) {
566
- const item = { ...dcmt }; // Create a new reference
567
- if (item.tid && item.did && !item.isLoaded) {
568
- // Nella modalità originale (invertMasterNavigation=false),
569
- // i documenti detail non devono caricare i master come figli
570
- if (isForMaster && !invertMasterNavigation) {
571
- // Carica i detail dei detail (navigazione naturale detail→detail)
572
- const loadedItems = await getDetailDcmtsAsync(item.tid, item.did, 1);
573
- item.items = updateHiddenProperty(loadedItems);
567
+ const newAbortController = new AbortController();
568
+ setExpansionAbortController(newAbortController);
569
+ const itemsToLoad = node.items?.length ?? 0;
570
+ setShowExpansionWaitPanel(true);
571
+ setExpansionWaitPanelMaxValue(itemsToLoad);
572
+ setExpansionWaitPanelValue(0);
573
+ setExpansionWaitPanelText(`Caricamento documenti correlati...`);
574
+ try {
575
+ const newItems = [];
576
+ let processedCount = 0;
577
+ for (const dcmt of node.items ?? []) {
578
+ if (newAbortController.signal.aborted) {
579
+ console.log('Folder expansion aborted by user');
580
+ return node.items;
574
581
  }
575
- else {
576
- // Modalità standard o invertita
577
- const loadedItems = isForMaster
578
- ? await getMasterDcmtsAsync(item.tid, item.did, 1)
579
- : await getDetailDcmtsAsync(item.tid, item.did, 1);
580
- item.items = updateHiddenProperty(loadedItems);
582
+ const item = { ...dcmt };
583
+ if (item.tid && item.did && !item.isLoaded) {
584
+ // Update progress
585
+ processedCount++;
586
+ setExpansionWaitPanelValue(processedCount);
587
+ setExpansionWaitPanelText(`Caricamento ${processedCount} di ${itemsToLoad}...`);
588
+ // Nella modalità originale (invertMasterNavigation=false),
589
+ // i documenti detail non devono caricare i master come figli
590
+ if (isForMaster && !invertMasterNavigation) {
591
+ // Carica i detail dei detail (navigazione naturale detail→detail)
592
+ const loadedItems = await getDetailDcmtsAsync(item.tid, item.did, 1);
593
+ item.items = updateHiddenProperty(loadedItems);
594
+ }
595
+ else {
596
+ // Modalità standard o invertita
597
+ const loadedItems = isForMaster
598
+ ? await getMasterDcmtsAsync(item.tid, item.did, 1)
599
+ : await getDetailDcmtsAsync(item.tid, item.did, 1);
600
+ item.items = updateHiddenProperty(loadedItems);
601
+ }
602
+ item.isLoaded = true;
581
603
  }
582
- item.isLoaded = true;
604
+ newItems.push(item);
583
605
  }
584
- newItems.push(item);
606
+ return newItems;
607
+ }
608
+ catch (error) {
609
+ console.error('Error loading folder contents:', error);
610
+ return node.items;
611
+ }
612
+ finally {
613
+ setShowExpansionWaitPanel(false);
614
+ setExpansionAbortController(undefined);
585
615
  }
586
- return newItems;
587
616
  }, [isForMaster, invertMasterNavigation, getDetailDcmtsAsync, getMasterDcmtsAsync, updateHiddenProperty]);
588
617
  /**
589
618
  * Default item renderer with metadata display (adapted from TMMasterDetailDcmts.tsx)
@@ -715,6 +744,10 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
715
744
  if (mergedTreeData.length === 0) {
716
745
  return _jsx("div", { style: { padding: '20px', textAlign: 'center', color: '#666' }, children: "Nessuna relazione disponibile." });
717
746
  }
718
- return (_jsx(TMTreeView, { dataSource: mergedTreeData, itemRender: finalItemRender, calculateItemsForNode: calculateItemsForNode, onDataChanged: handleDataChanged, focusedItem: focusedItem, onFocusedItemChanged: handleFocusedItemChanged, allowMultipleSelection: allowMultipleSelection, selectedItems: selectedItems, onSelectionChanged: handleSelectedItemsChanged }));
747
+ return (_jsxs(_Fragment, { children: [_jsx(TMTreeView, { dataSource: mergedTreeData, itemRender: finalItemRender, calculateItemsForNode: calculateItemsForNode, onDataChanged: handleDataChanged, focusedItem: focusedItem, onFocusedItemChanged: handleFocusedItemChanged, allowMultipleSelection: allowMultipleSelection, selectedItems: selectedItems, onSelectionChanged: handleSelectedItemsChanged }), showExpansionWaitPanel && (_jsx(TMWaitPanel, { title: isForMaster ? 'Caricamento documenti master' : 'Caricamento documenti dettaglio', showPrimary: true, textPrimary: expansionWaitPanelText, valuePrimary: expansionWaitPanelValue, maxValuePrimary: expansionWaitPanelMaxValue, isCancelable: true, abortController: expansionAbortController, onAbortClick: (abortController) => {
748
+ setTimeout(() => {
749
+ abortController?.abort();
750
+ }, 100);
751
+ } }))] }));
719
752
  };
720
753
  export default TMRelationViewer;
@@ -3,7 +3,7 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react';
3
3
  import { SDK_Globals, DataColumnTypes, MetadataDataDomains, DataListViewModes, MetadataFormats, LayoutModes, TemplateTIDs, DcmtTypeListCacheService, AccessLevels, SystemMIDsAsNumber, RetrieveFileOptions, DcmtOpers, GeneralRetrieveFormats, AccessLevelsEx, ResultTypes, LayoutCacheService } from '@topconsultnpm/sdk-ts';
4
4
  import styled from 'styled-components';
5
5
  import { getCommandsMenuItems, getSelectedDcmtsOrFocused } from './TMSearchResultsMenuItems';
6
- import { genUniqueId, IconShow, IconBoard, IconDcmtTypeSys, IconDetailDcmts, SDKUI_Localizator, IconDelete, IconRefresh, IconMenuVertical, IconDownload, deepCompare, getDataColumnName, searchResultDescriptorToSimpleArray, searchResultToMetadataValues, IconSearchCheck, TMCommandsContextMenu, getExceptionMessage, IconCheck, svgToString } from '../../../helper';
6
+ import { genUniqueId, IconShow, IconBoard, IconDcmtTypeSys, IconDetailDcmts, SDKUI_Localizator, IconDelete, IconRefresh, IconMenuVertical, IconDownload, deepCompare, getDataColumnName, searchResultDescriptorToSimpleArray, searchResultToMetadataValues, IconSearchCheck, TMCommandsContextMenu, getExceptionMessage, IconCheck, svgToString, TMImageLibrary } from '../../../helper';
7
7
  import { useDcmtOperations } from '../../../hooks/useDcmtOperations';
8
8
  import { useInputAttachmentsDialog, useInputCvtFormatDialog } from '../../../hooks/useInputDialog';
9
9
  import { useRelatedDocuments } from '../../../hooks/useRelatedDocuments';
@@ -309,7 +309,7 @@ const TMSearchResult = ({ allTasks = [], getAllTasks, deleteTaskByIdsCallback, a
309
309
  const customButtonMenuItems = () => {
310
310
  const customButtonsItems = customButtonsLayout?.customButtons?.filter((customButton) => customButton.isForSearchResult && customButton.isForSearchResult > 0)
311
311
  .map((customButton) => ({
312
- icon: svgToString(_jsx(IconCheck, {})),
312
+ icon: svgToString(TMImageLibrary({ imageID: customButton.glyphID, showPath: true })),
313
313
  text: customButton.title || 'Bottone personalizzato',
314
314
  onClick: () => setCustomButton(customButton)
315
315
  }));
@@ -595,7 +595,7 @@ const TMSearchResult = ({ allTasks = [], getAllTasks, deleteTaskByIdsCallback, a
595
595
  setShowReAssignPopup(true);
596
596
  }, onMoreInfo: () => {
597
597
  setShowMoreInfoPopup(true);
598
- }, approveDisable: selectedDocs.length === 0, signApproveDisable: disableSignApproveDisable, rejectDisable: selectedDocs.length === 0, reassignDisable: selectedDocs.length === 0, infoDisable: selectedDocs.length !== 1, dtd: fromDTD }) }) })] }), _jsx(ConfirmFormatDialog, {}), _jsx(ConfirmAttachmentsDialog, {}), customButton && _jsx(TMCustomButton, { button: customButton, formData: currentMetadataValues, selectedItems: [], onClose: () => setCustomButton(undefined) }), showRelatedDcmtsChooser &&
598
+ }, approveDisable: selectedDocs.length === 0, signApproveDisable: disableSignApproveDisable, rejectDisable: selectedDocs.length === 0, reassignDisable: selectedDocs.length === 0, infoDisable: selectedDocs.length !== 1, dtd: fromDTD }) }) })] }), _jsx(ConfirmFormatDialog, {}), _jsx(ConfirmAttachmentsDialog, {}), customButton && _jsx(TMCustomButton, { button: customButton, formData: currentMetadataValues, selectedItems: selectedItems, onClose: () => setCustomButton(undefined) }), showRelatedDcmtsChooser &&
599
599
  _jsx(TMChooserForm, { dataSource: relatedDcmtsChooserDataSource, onChoose: async (selectedRelation) => {
600
600
  try {
601
601
  setShowRelatedDcmtsChooser(false);
@@ -6,10 +6,7 @@ export interface RowData {
6
6
  [key: string]: string | number | null;
7
7
  }
8
8
  export declare const associateColumnsToRows: (columns: Array<DataColumnDescriptor> | undefined, rows: Array<Array<string>> | undefined) => Array<RowData>;
9
- export declare const buildValueToLabelMapFromDataColumns: (columns: Array<DataColumnDescriptor>) => Promise<{
10
- valueToNameMap: Map<string, string>;
11
- captions: Set<string>;
12
- }>;
9
+ export declare const buildValueToLabelMapFromDataColumns: (columns: Array<DataColumnDescriptor>) => Promise<Map<string, Map<string, string>>>;
13
10
  export declare const getAvatarColor: (name: string) => string;
14
11
  export declare const extractInitialsFromName: (name: string) => string;
15
12
  export declare const SIGN4_TOP_WIDGET_ID = "60003";
@@ -119,23 +119,27 @@ export const associateColumnsToRows = (columns, rows) => {
119
119
  };
120
120
  export const buildValueToLabelMapFromDataColumns = async (columns) => {
121
121
  const valueToNameMap = new Map();
122
- const captions = new Set();
123
- for (const col of columns) {
124
- const dataDomain = MetadataDataDomains[(col.extendedProperties?.["DataDomain"] ?? "None")];
122
+ const dataListColumns = columns.filter(col => {
123
+ const dataDomainRaw = col.extendedProperties?.["DataDomain"];
124
+ const dataDomain = MetadataDataDomains[(dataDomainRaw ?? "None")];
125
+ return dataDomain === MetadataDataDomains.DataList;
126
+ });
127
+ for (const col of dataListColumns) {
128
+ const tid = Number(col.extendedProperties?.["TID"]);
129
+ const mid = Number(col.extendedProperties?.["MID"]);
125
130
  const dataListID = Number(col.extendedProperties?.["DataListID"]);
126
- if (dataDomain === MetadataDataDomains.DataList) {
127
- if (col.caption) {
128
- captions.add(col.caption);
129
- }
131
+ if (tid && mid && dataListID) {
132
+ const tid_mid = `${tid}_${mid}`;
133
+ valueToNameMap.set(tid_mid, new Map());
130
134
  const dl = await DataListCacheService.GetAsync(dataListID);
131
135
  dl?.items?.forEach((item) => {
132
136
  if (item?.name !== undefined && item?.value !== undefined) {
133
- valueToNameMap.set(item.value, item.name);
137
+ valueToNameMap.get(tid_mid)?.set(item.value, item.name);
134
138
  }
135
139
  });
136
140
  }
137
141
  }
138
- return { valueToNameMap, captions };
142
+ return valueToNameMap;
139
143
  };
140
144
  export const getAvatarColor = (name) => {
141
145
  const colors = [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@topconsultnpm/sdkui-react",
3
- "version": "6.19.0-dev2.16",
3
+ "version": "6.19.0-dev2.18",
4
4
  "description": "",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1",