@topconsultnpm/sdkui-react 6.22.0-dev2.47 → 6.22.0-dev2.49

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.
@@ -18,6 +18,10 @@ export interface ITMPanelProps {
18
18
  isVisible?: boolean;
19
19
  panelID?: string;
20
20
  isActive?: boolean;
21
+ /** Se fornita, in testata compare il bottone di ricarica: finché la Promise è pendente il pannello mostra il loader */
22
+ onRefresh?: () => void | Promise<void>;
23
+ /** Mostra il loader sul contenuto anche per caricamenti non avviati dal bottone di ricarica */
24
+ isRefreshing?: boolean;
21
25
  onActivate?: (panelID: string) => void;
22
26
  onActiveChanged?: (isActive: boolean) => void;
23
27
  onBack?: () => void;
@@ -1,100 +1,102 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useCallback, useRef, useState, forwardRef, useImperativeHandle } from 'react';
3
3
  import styled, { css, keyframes } from 'styled-components';
4
- import { getAppModuleGradient, IconArrowLeft, IconClearButton, IconWindowMaximize, IconWindowMinimize, isPositiveNumber, SDKUI_Localizator } from '../../helper';
4
+ import { getAppModuleGradient, IconArrowLeft, IconClearButton, IconRefresh, IconWindowMaximize, IconWindowMinimize, isPositiveNumber, SDKUI_Localizator } from '../../helper';
5
5
  import TMButton from './TMButton';
6
- import { Gutters } from '../../utils/theme';
7
- const StyledBackButton = styled.button `
8
- display: flex;
9
- align-items: center;
10
- justify-content: center;
11
- gap: 4px;
12
- padding: 4px 10px;
13
- border: 1px solid ${({ $isActive }) => $isActive ? 'rgba(37, 89, 165, 0.3)' : 'rgba(255, 255, 255, 0.4)'};
14
- border-radius: 6px;
15
- cursor: pointer;
16
- font-weight: 500;
17
- font-size: 0.85rem;
18
- flex-shrink: 0;
19
- transition: all 0.15s ease;
20
- background-color: ${({ $isActive }) => $isActive ? '#FFFFFF' : '#2559A5'};
21
- box-shadow: none;
22
-
23
- &&, && * {
24
- color: ${({ $isActive }) => $isActive ? '#2559A5' : '#FFFFFF'} !important;
25
- }
26
-
27
- svg {
28
- font-size: 14px;
29
- }
6
+ import { LoadIndicator } from 'devextreme-react/load-indicator';
7
+ import { TMExceptionBoxManager } from './TMPopUp';
8
+ import { Gutters, TMColors } from '../../utils/theme';
9
+ const StyledBackButton = styled.button `
10
+ display: flex;
11
+ align-items: center;
12
+ justify-content: center;
13
+ gap: 4px;
14
+ padding: 4px 10px;
15
+ border: 1px solid ${({ $isActive }) => $isActive ? 'rgba(37, 89, 165, 0.3)' : 'rgba(255, 255, 255, 0.4)'};
16
+ border-radius: 6px;
17
+ cursor: pointer;
18
+ font-weight: 500;
19
+ font-size: 0.85rem;
20
+ flex-shrink: 0;
21
+ transition: all 0.15s ease;
22
+ background-color: ${({ $isActive }) => $isActive ? '#FFFFFF' : '#2559A5'};
23
+ box-shadow: none;
24
+
25
+ &&, && * {
26
+ color: ${({ $isActive }) => $isActive ? '#2559A5' : '#FFFFFF'} !important;
27
+ }
28
+
29
+ svg {
30
+ font-size: 14px;
31
+ }
30
32
  `;
31
33
  import { AppModules, SDK_Globals } from '@topconsultnpm/sdk-ts';
32
34
  import { DeviceType, useDeviceType } from './TMDeviceProvider';
33
- const StyledPanelContainer = styled.div `
34
- width: 100%;
35
- height: 100%;
36
- font-size: 1rem;
37
- box-shadow: 3px 3px 5px #D3D3D3BF;
38
- border-radius: 10px;
39
- opacity: 1;
40
- background: #FFFFFF;
41
- min-height: 0;
42
- overflow: hidden;
43
- position: ${({ $isMaximized }) => $isMaximized ? 'fixed' : 'relative'};
44
- top: ${({ $isMaximized }) => $isMaximized ? '50px' : 'auto'};
45
- left: ${({ $isMaximized }) => $isMaximized ? '50px' : 'auto'};
46
- width: ${({ $isMaximized }) => $isMaximized ? `calc(100vw - 50px - (${Gutters.getGutters()}px * 2))` : '100%'};
47
- height: ${({ $isMaximized }) => $isMaximized ? `calc(100vh - 50px - (${Gutters.getGutters()}px * 2))` : '100%'};
48
- z-index: ${({ $isMaximized }) => $isMaximized ? 2000 : 'auto'};
49
- margin: ${({ $isMaximized }) => $isMaximized ? `${Gutters.getGutters()}px` : '0'};
50
- outline: none;
51
- /* transition: all 0.2s; */
35
+ const StyledPanelContainer = styled.div `
36
+ width: 100%;
37
+ height: 100%;
38
+ font-size: 1rem;
39
+ box-shadow: 3px 3px 5px #D3D3D3BF;
40
+ border-radius: 10px;
41
+ opacity: 1;
42
+ background: #FFFFFF;
43
+ min-height: 0;
44
+ overflow: hidden;
45
+ position: ${({ $isMaximized }) => $isMaximized ? 'fixed' : 'relative'};
46
+ top: ${({ $isMaximized }) => $isMaximized ? '50px' : 'auto'};
47
+ left: ${({ $isMaximized }) => $isMaximized ? '50px' : 'auto'};
48
+ width: ${({ $isMaximized }) => $isMaximized ? `calc(100vw - 50px - (${Gutters.getGutters()}px * 2))` : '100%'};
49
+ height: ${({ $isMaximized }) => $isMaximized ? `calc(100vh - 50px - (${Gutters.getGutters()}px * 2))` : '100%'};
50
+ z-index: ${({ $isMaximized }) => $isMaximized ? 2000 : 'auto'};
51
+ margin: ${({ $isMaximized }) => $isMaximized ? `${Gutters.getGutters()}px` : '0'};
52
+ outline: none;
53
+ /* transition: all 0.2s; */
52
54
  `;
53
- const StyledPanelHeader = styled.div `
54
- width:100% ;
55
- display: flex;
56
- flex-direction: row;
57
- align-items: center;
58
- justify-content: flex-start;
59
- padding: 4px 10px;
55
+ const StyledPanelHeader = styled.div `
56
+ width:100% ;
57
+ display: flex;
58
+ flex-direction: row;
59
+ align-items: center;
60
+ justify-content: flex-start;
61
+ padding: 4px 10px;
60
62
  background: ${({ $isActive, $backgroundColor, $activeGradient }) => $isActive
61
63
  ? ($activeGradient ??
62
64
  'linear-gradient(270deg, #46B5A2 16%, #3BAABC 34%, #3BAABC 34%, #3681AD 54%, #3368A5 72%, #2F549D 88%, #304F99 100%)')
63
- : ($backgroundColor ?? '#F4F4F4')};
64
- box-shadow: 3px 3px 5px #D3D3D3BF;
65
- color: ${({ $isActive, $color }) => $isActive ? '#FFFFFF' : ($color ?? '#2559A5')};
66
- border-top-right-radius: 10px;
67
- border-top-left-radius: 10px;
68
- height: 40px;
69
- font-weight: 600;
70
- outline: none;
71
- &:focus {
72
- outline: none;
73
- }
74
- // Ensure all children inherit the header color
75
- &,
76
- * {
77
- color: ${({ $isActive, $color }) => $isActive ? '#FFFFFF' : ($color ?? '#2559A5')} !important;
78
- }
65
+ : ($backgroundColor ?? '#F4F4F4')};
66
+ box-shadow: 3px 3px 5px #D3D3D3BF;
67
+ color: ${({ $isActive, $color }) => $isActive ? '#FFFFFF' : ($color ?? '#2559A5')};
68
+ border-top-right-radius: 10px;
69
+ border-top-left-radius: 10px;
70
+ height: 40px;
71
+ font-weight: 600;
72
+ outline: none;
73
+ &:focus {
74
+ outline: none;
75
+ }
76
+ // Ensure all children inherit the header color
77
+ &,
78
+ * {
79
+ color: ${({ $isActive, $color }) => $isActive ? '#FFFFFF' : ($color ?? '#2559A5')} !important;
80
+ }
79
81
  `;
80
- const StyledPanelContent = styled.div `
81
- width: 100%;
82
- height: ${props => props.$height};
83
- min-height: 0;
84
- overflow: hidden;
85
- border-bottom-right-radius: 10px;
86
- border-bottom-left-radius: 10px;
87
- padding: ${props => props.$padding};
88
- display: flex;
89
- justify-content: space-between;
90
- position: relative;
91
- user-select: none;
92
- -webkit-touch-callout: none;
93
- -webkit-user-select: none;
94
- outline: none;
95
- &:focus {
96
- outline: none;
97
- }
82
+ const StyledPanelContent = styled.div `
83
+ width: 100%;
84
+ height: ${props => props.$height};
85
+ min-height: 0;
86
+ overflow: hidden;
87
+ border-bottom-right-radius: 10px;
88
+ border-bottom-left-radius: 10px;
89
+ padding: ${props => props.$padding};
90
+ display: flex;
91
+ justify-content: space-between;
92
+ position: relative;
93
+ user-select: none;
94
+ -webkit-touch-callout: none;
95
+ -webkit-user-select: none;
96
+ outline: none;
97
+ &:focus {
98
+ outline: none;
99
+ }
98
100
  `;
99
101
  // Allineata al TABLET_BREAKPOINT (768) di TMDeviceProvider
100
102
  const MOBILE_MEDIA_QUERY = '(max-width: 767.98px)';
@@ -103,69 +105,102 @@ const MOBILE_MEDIA_QUERY = '(max-width: 767.98px)';
103
105
  // la track (larga quanto il viewport) va a +100% della PROPRIA larghezza, il testo a -100%
104
106
  // della SUA. Le due animazioni hanno stessa durata ed easing, quindi lo scostamento netto
105
107
  // in ogni istante è esattamente e(t) * (viewport - testo).
106
- const panelTitleTrackMarquee = keyframes `
107
- 0%, 15% { transform: translateX(0); }
108
- 45%, 55% { transform: translateX(100%); }
109
- 85%, 100% { transform: translateX(0); }
108
+ const panelTitleTrackMarquee = keyframes `
109
+ 0%, 15% { transform: translateX(0); }
110
+ 45%, 55% { transform: translateX(100%); }
111
+ 85%, 100% { transform: translateX(0); }
110
112
  `;
111
- const panelTitleTextMarquee = keyframes `
112
- 0%, 15% { transform: translateX(0); }
113
- 45%, 55% { transform: translateX(-100%); }
114
- 85%, 100% { transform: translateX(0); }
113
+ const panelTitleTextMarquee = keyframes `
114
+ 0%, 15% { transform: translateX(0); }
115
+ 45%, 55% { transform: translateX(-100%); }
116
+ 85%, 100% { transform: translateX(0); }
115
117
  `;
116
118
  // Ritaglia il testo in eccesso: è l'unico box con overflow hidden, la track ci scorre dentro
117
- const StyledPanelTitleViewport = styled.div `
118
- flex: 1;
119
- display: flex;
120
- align-items: center;
121
- justify-content: flex-start;
122
- min-width: 0;
123
- overflow: hidden;
124
- padding: 0 8px;
119
+ const StyledPanelTitleViewport = styled.div `
120
+ flex: 1;
121
+ display: flex;
122
+ align-items: center;
123
+ justify-content: flex-start;
124
+ min-width: 0;
125
+ overflow: hidden;
126
+ padding: 0 8px;
125
127
  `;
126
128
  // Larga quanto il viewport: è il metro con cui il -100% del testo viene compensato.
127
- const StyledPanelTitleTrack = styled.div `
128
- flex: 1;
129
- min-width: 0;
130
- display: flex;
131
- align-items: center;
132
-
133
- ${({ $marquee }) => $marquee && css `
134
- @media ${MOBILE_MEDIA_QUERY} {
135
- animation: ${panelTitleTrackMarquee} 10s ease-in-out infinite;
136
-
137
- @media (prefers-reduced-motion: reduce) {
138
- animation: none;
139
- }
140
- }
141
- `}
129
+ const StyledPanelTitleTrack = styled.div `
130
+ flex: 1;
131
+ min-width: 0;
132
+ display: flex;
133
+ align-items: center;
134
+
135
+ ${({ $marquee }) => $marquee && css `
136
+ @media ${MOBILE_MEDIA_QUERY} {
137
+ animation: ${panelTitleTrackMarquee} 10s ease-in-out infinite;
138
+
139
+ @media (prefers-reduced-motion: reduce) {
140
+ animation: none;
141
+ }
142
+ }
143
+ `}
142
144
  `;
143
145
  // Senza $marquee resta il troncamento con i puntini di sempre
144
- const StyledPanelTitle = styled.div `
145
- margin: 0;
146
- white-space: nowrap;
147
- overflow: hidden;
148
- text-overflow: ellipsis;
149
-
150
- ${({ $marquee }) => $marquee && css `
151
- @media ${MOBILE_MEDIA_QUERY} {
152
- flex: 0 0 auto;
153
- width: max-content;
154
- min-width: 100%;
155
- max-width: none;
156
- overflow: visible;
157
- text-overflow: clip;
158
- animation: ${panelTitleTextMarquee} 10s ease-in-out infinite;
159
-
160
- @media (prefers-reduced-motion: reduce) {
161
- animation: none;
162
- width: auto;
163
- min-width: 0;
164
- overflow: hidden;
165
- text-overflow: ellipsis;
166
- }
167
- }
168
- `}
146
+ const StyledPanelTitle = styled.div `
147
+ margin: 0;
148
+ white-space: nowrap;
149
+ overflow: hidden;
150
+ text-overflow: ellipsis;
151
+
152
+ ${({ $marquee }) => $marquee && css `
153
+ @media ${MOBILE_MEDIA_QUERY} {
154
+ flex: 0 0 auto;
155
+ width: max-content;
156
+ min-width: 100%;
157
+ max-width: none;
158
+ overflow: visible;
159
+ text-overflow: clip;
160
+ animation: ${panelTitleTextMarquee} 10s ease-in-out infinite;
161
+
162
+ @media (prefers-reduced-motion: reduce) {
163
+ animation: none;
164
+ width: auto;
165
+ min-width: 0;
166
+ overflow: hidden;
167
+ text-overflow: ellipsis;
168
+ }
169
+ }
170
+ `}
171
+ `;
172
+ const refreshSpin = keyframes `
173
+ from { transform: rotate(0deg); }
174
+ to { transform: rotate(360deg); }
175
+ `;
176
+ // Fa girare l'icona per tutta la durata del ricaricamento
177
+ const StyledRefreshIcon = styled.span `
178
+ display: flex;
179
+ align-items: center;
180
+ justify-content: center;
181
+
182
+ ${({ $isSpinning }) => $isSpinning && css `
183
+ animation: ${refreshSpin} 1s linear infinite;
184
+
185
+ @media (prefers-reduced-motion: reduce) {
186
+ animation: none;
187
+ }
188
+ `}
189
+ `;
190
+ // Copre il solo contenuto del pannello (StyledPanelContent è position: relative con overflow hidden)
191
+ const StyledPanelLoaderOverlay = styled.div `
192
+ position: absolute;
193
+ inset: 0;
194
+ z-index: 10;
195
+ display: flex;
196
+ flex-direction: column;
197
+ align-items: center;
198
+ justify-content: center;
199
+ gap: 10px;
200
+ background: rgba(255, 255, 255, 0.65);
201
+ border-bottom-right-radius: 10px;
202
+ border-bottom-left-radius: 10px;
203
+ color: ${TMColors.primaryColor};
169
204
  `;
170
205
  /**
171
206
  * TMPanel - Pannello con supporto Controlled/Uncontrolled
@@ -175,8 +210,26 @@ const StyledPanelTitle = styled.div `
175
210
  * - Se passata → Controlled: il parent gestisce lo stato tramite onActivate
176
211
  */
177
212
  const TMPanel = forwardRef(({ allowMaximize = true, color, backgroundColor, backgroundColorContainer, children, showHeader = true, title, enableTitleMarquee = false, totalItems, displayedItemsCount, toolbar, padding = '5px', isVisible = true, panelID = 'tmpanel', isActive, // Questa prop determinerà la modalità
178
- onActivate, onBack, onClose, onHeaderDoubleClick, onMaximize, onActiveChanged }, ref) => {
213
+ onRefresh, isRefreshing, onActivate, onBack, onClose, onHeaderDoubleClick, onMaximize, onActiveChanged }, ref) => {
179
214
  const [isMaximized, setIsMaximized] = useState(false);
215
+ const [isRefreshingInternal, setIsRefreshingInternal] = useState(false);
216
+ // Il loader compare sia per il refresh dalla testata sia per i caricamenti segnalati dal contenuto
217
+ const isBusy = isRefreshing || isRefreshingInternal;
218
+ const handleRefresh = useCallback(async () => {
219
+ if (isRefreshingInternal)
220
+ return;
221
+ setIsRefreshingInternal(true);
222
+ // Il finally garantisce che loader e rotellina si fermino anche se il ricaricamento fallisce
223
+ try {
224
+ await onRefresh?.();
225
+ }
226
+ catch (ex) {
227
+ TMExceptionBoxManager.show({ exception: ex });
228
+ }
229
+ finally {
230
+ setIsRefreshingInternal(false);
231
+ }
232
+ }, [isRefreshingInternal, onRefresh]);
180
233
  // Detect device type (e.g., mobile or desktop)
181
234
  const deviceType = useDeviceType();
182
235
  const isMobile = deviceType === DeviceType.MOBILE;
@@ -270,8 +323,9 @@ onActivate, onBack, onClose, onHeaderDoubleClick, onMaximize, onActiveChanged },
270
323
  alignItems: 'center',
271
324
  gap: '5px',
272
325
  flexShrink: 0
273
- }, children: [toolbar, allowMaximize && _jsx(TMButton, { color: 'primaryOutline', caption: isMaximized ? SDKUI_Localizator.Minimize : SDKUI_Localizator.Maximize, icon: isMaximized
326
+ }, children: [toolbar, onRefresh && _jsx(TMButton, { color: 'primaryOutline', caption: SDKUI_Localizator.Refresh, disabled: isBusy, icon: _jsx(StyledRefreshIcon, { "$isSpinning": isBusy, children: _jsx(IconRefresh, {}) }), btnStyle: 'icon', onClick: handleRefresh }), allowMaximize && _jsx(TMButton, { color: 'primaryOutline', caption: isMaximized ? SDKUI_Localizator.Minimize : SDKUI_Localizator.Maximize, icon: isMaximized
274
327
  ? _jsx(IconWindowMinimize, { fontSize: 16 })
275
- : _jsx(IconWindowMaximize, { fontSize: 16 }), btnStyle: 'icon', onClick: handleMaximize }), onClose && _jsx(TMButton, { color: 'primaryOutline', caption: SDKUI_Localizator.Close, icon: _jsx(IconClearButton, {}), btnStyle: 'icon', onClick: () => { setIsMaximized(false); onClose?.(); } })] })] }) }), _jsx(StyledPanelContent, { "$height": showHeader ? "calc(100% - 40px)" : "100%", "$padding": padding, "$backgroundColor": backgroundColorContainer ?? `#FFFFFF`, children: children })] }));
328
+ : _jsx(IconWindowMaximize, { fontSize: 16 }), btnStyle: 'icon', onClick: handleMaximize }), onClose && _jsx(TMButton, { color: 'primaryOutline', caption: SDKUI_Localizator.Close, icon: _jsx(IconClearButton, {}), btnStyle: 'icon', onClick: () => { setIsMaximized(false); onClose?.(); } })] })] }) }), _jsxs(StyledPanelContent, { "$height": showHeader ? "calc(100% - 40px)" : "100%", "$padding": padding, "$backgroundColor": backgroundColorContainer ?? `#FFFFFF`, children: [children, isBusy &&
329
+ _jsxs(StyledPanelLoaderOverlay, { children: [_jsx(LoadIndicator, { height: 40, width: 40 }), _jsx("div", { children: SDKUI_Localizator.Loading })] })] })] }));
276
330
  });
277
331
  export default TMPanel;
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import React, { useEffect, useMemo, useState } from 'react';
2
+ import React, { useCallback, useEffect, useMemo, useState } from 'react';
3
3
  import Logo from '../../../assets/Toppy-generico.png';
4
4
  import { ArchiveConstraints, DcmtTypeListCacheService, LayoutModes, SDK_Localizator } from '@topconsultnpm/sdk-ts';
5
5
  import { IconTree, SDKUI_Globals, SDKUI_Localizator, IconRecentlyViewed, IconPreview, IconShow, IconBoard, IconDcmtTypeSys, removeMruTid, getMoreInfoTasksForDocument } from '../../../helper';
@@ -56,6 +56,13 @@ const TMArchive = ({ onDcmtTypeSelect = undefined, inputTID, inputFile = null, c
56
56
  previousTIDRef.current = currentTID;
57
57
  }, [currentTID, onDcmtTypeSelect, isSharedArchive]);
58
58
  const isMobile = deviceType === DeviceType.MOBILE;
59
+ // Scorciatoie: rilegge dal server l'elenco dei tipi documento, così le scorciatoie riflettono
60
+ // quelli aggiunti o rimossi. La nuova identità di mruTIDs fa ricaricare i descrittori a TMRecentsManager.
61
+ const refreshRecentsAsync = useCallback(async () => {
62
+ DcmtTypeListCacheService.RemoveAllWithoutMetadata();
63
+ await DcmtTypeListCacheService.GetAllWithoutMetadataAsync();
64
+ setMruTIDs([...SDKUI_Globals.userSettings.archivingSettings.mruTIDs]);
65
+ }, []);
59
66
  const tmTreeSelectorElement = useMemo(() => _jsx(TMTreeSelectorWrapper, { isMobile: isMobile, isSharedArchive: isSharedArchive, onSelectedTIDChanged: (tid) => {
60
67
  setCurrentTID(tid);
61
68
  if (tid && mruTIDs.includes(tid))
@@ -108,7 +115,14 @@ const TMArchive = ({ onDcmtTypeSelect = undefined, inputTID, inputFile = null, c
108
115
  {
109
116
  id: 'tmRecentsManager',
110
117
  name: SDKUI_Localizator.Shortcuts,
111
- contentOptions: { component: tmRecentsManagerElement, panelContainer: { title: SDKUI_Localizator.Shortcuts, totalItems: mruTIDs.length } },
118
+ contentOptions: {
119
+ component: tmRecentsManagerElement,
120
+ panelContainer: {
121
+ title: SDKUI_Localizator.Shortcuts,
122
+ totalItems: mruTIDs.length,
123
+ onRefresh: refreshRecentsAsync
124
+ }
125
+ },
112
126
  toolbarOptions: { icon: _jsx(IconRecentlyViewed, { fontSize: 24 }), visible: true, orderNumber: 2, isActive: allInitialPanelVisibility['tmRecentsManager'] }
113
127
  },
114
128
  {
@@ -139,7 +153,7 @@ const TMArchive = ({ onDcmtTypeSelect = undefined, inputTID, inputFile = null, c
139
153
  }
140
154
  ]
141
155
  },
142
- ], [tmTreeSelectorElement, tmRecentsManagerElement, tmFormElement, currentTID, mruTIDs, isSharedArchive]);
156
+ ], [tmTreeSelectorElement, tmRecentsManagerElement, tmFormElement, currentTID, mruTIDs, isSharedArchive, refreshRecentsAsync]);
143
157
  return (_jsx(TMPanelManagerProvider, { panels: initialPanels, initialVisibility: allInitialPanelVisibility, defaultDimensions: initialPanelDimensions, initialDimensions: initialPanelDimensions, initialMobilePanelId: 'tmRecentsManager', children: _jsx(TMPanelManagerContainer, { panels: initialPanels, direction: "horizontal", showToolbar: true }) }));
144
158
  };
145
159
  export default TMArchive;
@@ -863,6 +863,43 @@ const TMDcmtForm = ({ TID, DID, groupId, layoutMode = LayoutModes.Update, formMo
863
863
  const formToolbar = useMemo(() => _jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: '10px' }, children: [allowNavigation && canPrev != undefined && _jsx("p", { style: { textAlign: 'center', padding: '1px 4px', display: 'flex' }, children: `${itemIndex}/${count}` }), allowNavigation && canPrev != undefined && _jsx(TMSaveFormButtonPrevious, { btnStyle: 'icon', iconColor: 'white', isModified: isModified, formMode: formMode, canPrev: canPrev, onPrev: onPrev }), allowNavigation && canNext != undefined && _jsx(TMSaveFormButtonNext, { btnStyle: 'icon', iconColor: 'white', isModified: isModified, formMode: formMode, canNext: canNext, onNext: onNext }), layoutMode === LayoutModes.Update &&
864
864
  _jsx(ContextMenu, { items: operationItems, trigger: "left", children: _jsx(IconMenuVertical, { color: 'white', cursor: 'pointer' }) }), layoutMode === LayoutModes.Ark && !archiveList.enabled &&
865
865
  _jsx(TMTooltip, { content: SDKUI_Localizator.PassToSearch, position: 'bottom', children: _jsx(IconSearch, { style: { cursor: 'pointer' }, onClick: handlePassToSearch }) })] }), [allowNavigation, canPrev, canNext, itemIndex, count, isModified, formMode, onPrev, onNext, layoutMode, operationItems, handlePassToSearch, archiveList.enabled]);
866
+ // Ricarica del pannello metadati: invalida le cache del tipo documento e del layout, così la
867
+ // definizione dei metadati e la disposizione del form vengono rilette dal server, poi ricostruisce il form
868
+ const reloadFormMetadataAsync = useCallback(async () => {
869
+ if (!TID)
870
+ return;
871
+ // refreshCache invalida la voce in cache e rilegge il tipo documento con i metadati:
872
+ // la GetWithNotGrantedAsync della load successiva trova quella appena aggiornata
873
+ await DcmtTypeListCacheService.GetAsync(TID, true, undefined, false, true);
874
+ LayoutCacheService.Remove(TID, layoutMode);
875
+ if (layoutMode === LayoutModes.Update)
876
+ LayoutCacheService.Remove(TID, LayoutModes.None);
877
+ await loadDocumentFormDataAsync();
878
+ }, [TID, layoutMode, loadDocumentFormDataAsync]);
879
+ // Il ricaricamento ricostruisce il form: con valori inseriti e non salvati chiede prima conferma.
880
+ // In archiviazione la ricostruzione sgancia anche il file, quindi va confermata pure in quel caso.
881
+ const handleRefreshFormMetadataAsync = useCallback(async () => {
882
+ const hasPendingChanges = isModified || (layoutMode === LayoutModes.Ark && !!dcmtFile);
883
+ if (!hasPendingChanges) {
884
+ await reloadFormMetadataAsync();
885
+ return;
886
+ }
887
+ const isConfirmed = await new Promise(resolve => {
888
+ let isAnswered = false;
889
+ TMMessageBoxManager.show({
890
+ parentId: isModal ? "TMDcmtFormShowConfirmForClose-" + id : undefined,
891
+ title: SDKUI_Localizator.Refresh,
892
+ message: SDKUI_Localizator.ConfirmOnCancel,
893
+ buttons: [ButtonNames.YES, ButtonNames.NO],
894
+ onButtonClick: (e) => { isAnswered = true; resolve(e === ButtonNames.YES); },
895
+ // Chiusura dalla X: senza questa la Promise non si risolverebbe e il loader resterebbe acceso
896
+ onClose: () => { if (!isAnswered)
897
+ resolve(false); }
898
+ });
899
+ });
900
+ if (isConfirmed)
901
+ await reloadFormMetadataAsync();
902
+ }, [isModified, layoutMode, dcmtFile, isModal, id, reloadFormMetadataAsync]);
866
903
  const handleUndo = useCallback(() => {
867
904
  setFormData(structuredClone(formDataOrig));
868
905
  }, [formDataOrig]);
@@ -1347,6 +1384,7 @@ const TMDcmtForm = ({ TID, DID, groupId, layoutMode = LayoutModes.Update, formMo
1347
1384
  allowMaximize: !isMobile,
1348
1385
  onBack: getOnBackHandler(true),
1349
1386
  onClose: isClosable ? () => { } : undefined,
1387
+ onRefresh: handleRefreshFormMetadataAsync,
1350
1388
  toolbar: allowNavigation ? formToolbar : _jsx(_Fragment, {})
1351
1389
  },
1352
1390
  },
@@ -1436,7 +1474,7 @@ const TMDcmtForm = ({ TID, DID, groupId, layoutMode = LayoutModes.Update, formMo
1436
1474
  },
1437
1475
  ];
1438
1476
  return panels;
1439
- }, [fromDTD, showBackButton, tmDcmtForm, tmBlog, tmSysMetadata, tmDcmtPreview, tmWF, tmDcmtTasks, isPreviewDisabled, isSysMetadataDisabled, isBoardDisabled, isWFDisabled, inputFile, isClosable, allTasks, DID, TID, isReadOnlyOriginCallback, metadataDcmtOrigin?.fromTID]);
1477
+ }, [fromDTD, showBackButton, tmDcmtForm, tmBlog, tmSysMetadata, tmDcmtPreview, tmWF, tmDcmtTasks, isPreviewDisabled, isSysMetadataDisabled, isBoardDisabled, isWFDisabled, inputFile, isClosable, allTasks, DID, TID, isReadOnlyOriginCallback, metadataDcmtOrigin?.fromTID, handleRefreshFormMetadataAsync]);
1440
1478
  // Retrieves the current document form setting based on the normalized TID
1441
1479
  const getCurrentDcmtFormSetting = () => {
1442
1480
  const settings = SDKUI_Globals.userSettings.dcmtFormSettings;
@@ -5,7 +5,7 @@ import TMSavedQuerySelector from './TMSavedQuerySelector';
5
5
  import TMTreeSelector from './TMTreeSelector';
6
6
  import { TabPanel, Item } from 'devextreme-react/tab-panel';
7
7
  import TMSearchQueryPanel, { refreshLastSearch } from './TMSearchQueryPanel';
8
- import { getSysAllDcmtsSQD, IconFilter, IconRecentlyViewed, IconSavedQuery, IconTree, mergeSearchResultsAppend, removeMruTid, SDKUI_Globals, SDKUI_Localizator, updateMruTids } from '../../../helper';
8
+ import { getSysAllDcmtsSQD, SYS_ALL_DCMTS_SQD_ID, IconFilter, IconRecentlyViewed, IconSavedQuery, IconTree, mergeSearchResultsAppend, removeMruTid, SDKUI_Globals, SDKUI_Localizator, updateMruTids } from '../../../helper';
9
9
  import TMSearchResult from './TMSearchResult';
10
10
  import TMRecentsManager from '../../grids/TMRecentsManager';
11
11
  import { SearchResultContext } from '../../../ts';
@@ -152,6 +152,42 @@ const TMSearch = ({ allTasks = [], getAllTasks, deleteTaskByIdsCallback, addTask
152
152
  if (sqdToBeSet)
153
153
  await setSqdAsync?.(sqdToBeSet);
154
154
  }, []);
155
+ // --- REFRESH DEI PANNELLI ---
156
+ // Scorciatoie: rilegge dal server l'elenco dei tipi documento, così le scorciatoie riflettono
157
+ // quelli aggiunti o rimossi. La nuova identità di mruTIDs fa ricaricare i descrittori a TMRecentsManager.
158
+ const refreshRecentsAsync = useCallback(async () => {
159
+ DcmtTypeListCacheService.RemoveAllWithoutMetadata();
160
+ await DcmtTypeListCacheService.GetAllWithoutMetadataAsync();
161
+ setMruTIDs([...SDKUI_Globals.userSettings.searchSettings.mruTIDs]);
162
+ }, []);
163
+ // Ricerche rapide: invalida la cache delle saved query e ricarica l'elenco del tipo documento corrente
164
+ const refreshSavedQueriesAsync = useCallback(async () => {
165
+ // loadDataSQDsAsync riporta la selezione sulla prima tab: il refresh non deve spostare quella scelta dall'utente
166
+ const sqdMode = currentSQDMode;
167
+ await loadDataSQDsAsync(true, fromDTD?.id ?? currentTID);
168
+ setCurrentSQDMode(sqdMode);
169
+ }, [fromDTD?.id, currentTID, currentSQDMode]);
170
+ // Pannello dei metadati: rilegge dal server i metadati del tipo documento e la definizione della
171
+ // ricerca rapida corrente, con cui il pannello ricostruisce i filtri (i valori digitati vengono persi)
172
+ const refreshSearchQueryPanelAsync = useCallback(async () => {
173
+ const tid = fromDTD?.id ?? currentTID;
174
+ if (!tid)
175
+ return;
176
+ const dtd = await DcmtTypeListCacheService.GetAsync(tid, true, undefined, false, true);
177
+ setFromDTD(dtd);
178
+ // Passare da undefined forza la ricostruzione dei filtri anche quando la query selezionata non cambia
179
+ const sqdToRestore = currentSQD;
180
+ SavedQueryCacheService.Remove();
181
+ await setSQDAsync(undefined);
182
+ // La ricerca di sistema non è in cache: va ricostruita, così recepisce i metadati appena riletti
183
+ await setSQDAsync(sqdToRestore?.id === SYS_ALL_DCMTS_SQD_ID ? await getSysAllDcmtsSQD(tid, false) : sqdToRestore);
184
+ // mruTIDs è tra le dipendenze perché setSQDAsync lo legge
185
+ }, [fromDTD?.id, currentTID, currentSQD, mruTIDs]);
186
+ // Prima di ogni ricerca il pannello rilegge i metadati del tipo documento: si recepisce il descrittore
187
+ // aggiornato, così anche il resto della vista (titoli, badge, form dei metadati) lavora sulla lista corrente
188
+ const handleDcmtTypeReloaded = useCallback((dtd) => {
189
+ setFromDTD(dtd);
190
+ }, []);
155
191
  const onRefreshSearchAsyncDatagrid = async () => {
156
192
  try {
157
193
  if (lastQdSearched) {
@@ -208,10 +244,10 @@ const TMSearch = ({ allTasks = [], getAllTasks, deleteTaskByIdsCallback, addTask
208
244
  SDKUI_Globals.userSettings.searchSettings.mruTIDs = newMruTIDS;
209
245
  setMruTIDs(newMruTIDS);
210
246
  } }), [mruTIDs, currentMruTID, deviceType]);
211
- const tmSearchQueryPanelElement = useMemo(() => _jsx(TMSearchQueryPanelWrapper, { isSearchViewOnScreen: currentSearchView === TMSearchViews.Search, passToArchiveCallback: passToArchiveCallback, isExpertMode: isExpertMode, showBackToResultButton: searchResult.length > 0, fromDTD: fromDTD, SQD: currentSQD, inputMids: inputMids, maxDcmtsToBeReturned: maxDcmtsToBeReturned, onBackToResult: () => { setCurrentSearchView(TMSearchViews.Result); }, onSearchCompleted: handleSearchCompleted, onSqdSaved: async (newSqd) => {
247
+ const tmSearchQueryPanelElement = useMemo(() => _jsx(TMSearchQueryPanelWrapper, { isSearchViewOnScreen: currentSearchView === TMSearchViews.Search, passToArchiveCallback: passToArchiveCallback, isExpertMode: isExpertMode, showBackToResultButton: searchResult.length > 0, fromDTD: fromDTD, SQD: currentSQD, inputMids: inputMids, maxDcmtsToBeReturned: maxDcmtsToBeReturned, onBackToResult: () => { setCurrentSearchView(TMSearchViews.Result); }, onSearchCompleted: handleSearchCompleted, onRefreshRequested: refreshSearchQueryPanelAsync, onDcmtTypeReloaded: handleDcmtTypeReloaded, onSqdSaved: async (newSqd) => {
212
248
  await loadDataSQDsAsync(true, newSqd.masterTID);
213
249
  await setSQDAsync(newSqd);
214
- } }), [fromDTD, showSearchResults, setShowSearchResults, currentSQD, currentSearchView, isExpertMode, mruTIDs, searchResult, passToArchiveCallback, inputMids, maxDcmtsToBeReturned, handleSearchCompleted]);
250
+ } }), [fromDTD, showSearchResults, setShowSearchResults, currentSQD, currentSearchView, isExpertMode, mruTIDs, searchResult, passToArchiveCallback, inputMids, maxDcmtsToBeReturned, handleSearchCompleted, refreshSearchQueryPanelAsync, handleDcmtTypeReloaded]);
215
251
  const tmSavedQuerySelectorElement = useMemo(() => _jsxs(TabPanel, { width: "100%", height: "100%", showNavButtons: true, repaintChangesOnly: true, selectedIndex: currentSQDMode, onSelectedIndexChange: (index) => setCurrentSQDMode(index), children: [(currentTID || currentSQD) ? _jsx(Item, { title: fromDTD?.nameLoc, children: _jsx(TMSavedQuerySelectorWrapper, { allowShowSearch: false, items: filteredByTIDSQDs, selectedId: currentSQD?.id, onRefreshData: () => { loadDataSQDsAsync(true); }, onItemClick: (sqd) => {
216
252
  onSQDItemClick(sqd, setSQDAsync);
217
253
  }, onDeleted: (sqd) => onSQDDeleted(sqd, sqd.id == currentSQD?.id ? filteredByTIDSQDs.find(o => o.id == 1) : currentSQD, setSQDAsync), refreshFavoriteSavedQueries: refreshFavoriteSavedQueries }) }) : _jsx(_Fragment, {}), _jsx(Item, { title: SDKUI_Localizator.AllFemale, children: _jsx(TMSavedQuerySelectorWrapper, { allowShowSearch: true, items: allSQDs, manageDefault: false, onItemClick: (sqd) => {
@@ -263,7 +299,14 @@ const TMSearch = ({ allTasks = [], getAllTasks, deleteTaskByIdsCallback, addTask
263
299
  {
264
300
  id: 'TMRecentsManager',
265
301
  name: SDKUI_Localizator.Shortcuts,
266
- contentOptions: { component: tmRecentsManagerElement, panelContainer: { title: SDKUI_Localizator.Shortcuts, totalItems: mruTIDs.length } },
302
+ contentOptions: {
303
+ component: tmRecentsManagerElement,
304
+ panelContainer: {
305
+ title: SDKUI_Localizator.Shortcuts,
306
+ totalItems: mruTIDs.length,
307
+ onRefresh: refreshRecentsAsync
308
+ }
309
+ },
267
310
  toolbarOptions: { icon: _jsx(IconRecentlyViewed, { fontSize: 24 }), visible: true, orderNumber: 2, isActive: allInitialPanelVisibility['TMRecentsManager'] }
268
311
  },
269
312
  {
@@ -275,10 +318,16 @@ const TMSearch = ({ allTasks = [], getAllTasks, deleteTaskByIdsCallback, addTask
275
318
  {
276
319
  id: 'TMSavedQuerySelector',
277
320
  name: SDK_Localizator.SavedQueries,
278
- contentOptions: { component: tmSavedQuerySelectorElement, panelContainer: { title: SDK_Localizator.SavedQueries } },
321
+ contentOptions: {
322
+ component: tmSavedQuerySelectorElement,
323
+ panelContainer: {
324
+ title: SDK_Localizator.SavedQueries,
325
+ onRefresh: refreshSavedQueriesAsync
326
+ }
327
+ },
279
328
  toolbarOptions: { icon: _jsx(IconSavedQuery, { fontSize: 24 }), visible: true, orderNumber: 4, isActive: allInitialPanelVisibility['TMSavedQuerySelector'] }
280
329
  }
281
- ], [tmTreeSelectorElement, showSearchResults, tmRecentsManagerElement, tmSearchQueryPanelElement, tmSavedQuerySelectorElement, fromDTD, mruTIDs]);
330
+ ], [tmTreeSelectorElement, showSearchResults, tmRecentsManagerElement, tmSearchQueryPanelElement, tmSavedQuerySelectorElement, fromDTD, mruTIDs, refreshRecentsAsync, refreshSavedQueriesAsync]);
282
331
  return (_jsxs(_Fragment, { children: [showSearchResults ? _jsx(StyledMultiViewPanel, { "$isVisible": currentSearchView === TMSearchViews.Search, children: _jsx(TMPanelManagerWithPersistenceProvider, { panels: initialPanels, initialVisibility: allInitialPanelVisibility, defaultDimensions: initialPanelDimensions, initialDimensions: initialPanelDimensions, initialMobilePanelId: 'TMRecentsManager', isPersistenceEnabled: !isMobile ? hasSavedLayout() : false, persistPanelStates: !isMobile ? (state) => persistPanelStates(state) : undefined, persistedPanelStates: getPersistedPanelStates(), children: _jsx(TMPanelManagerContainer, { panels: initialPanels, direction: "horizontal", showToolbar: true, minPanelSizePx: !isMobile ? 250 : 150 }) }) }) : tmSearchQueryPanelElement, showSearchResults && _jsx(TMSearchResult, { isVisible: isVisible && currentSearchView === TMSearchViews.Result, context: SearchResultContext.METADATA_SEARCH, searchResults: searchResult, floatingActionConfig: floatingActionConfig, onRefreshAfterAddDcmtToFavs: onRefreshAfterAddDcmtToFavs, openInOffice: openInOffice, onRefreshSearchAsyncDatagrid: onRefreshSearchAsyncDatagrid, onClose: () => { onlyShowSearchQueryPanel ? setShowSearchResults(false) : setCurrentSearchView(TMSearchViews.Search); }, onFileOpened: onFileOpened, onTaskCreateRequest: onTaskCreateRequest, openWGsCopyMoveForm: openWGsCopyMoveForm, editPdfForm: editPdfForm, onOpenPdfEditorRequest: onOpenPdfEditorRequest, openS4TViewer: openS4TViewer, onOpenS4TViewerRequest: onOpenS4TViewerRequest, openFileUploaderPdfEditor: openFileUploaderPdfEditor, passToArchiveCallback: passToArchiveCallback, onSelectedTIDChanged: onCurrentTIDChangedCallback, showTodoDcmtForm: showTodoDcmtForm, showToppyDraggableHelpCenter: showToppyDraggableHelpCenter, toppyHelpCenterUsePortal: toppyHelpCenterUsePortal, onReferenceClick: onReferenceClick, allTasks: allTasks, getAllTasks: getAllTasks, deleteTaskByIdsCallback: deleteTaskByIdsCallback, addTaskCallback: addTaskCallback, editTaskCallback: editTaskCallback, handleNavigateToWGs: handleNavigateToWGs, handleNavigateToDossiers: handleNavigateToDossiers, inputDID: inputDID, formAutoOpen: formAutoOpen, fetchRemoteCertificates: fetchRemoteCertificates, graphometricManager: graphometricManager })] }));
283
332
  };
284
333
  export default TMSearch;
@@ -298,11 +347,11 @@ const TMRecentsManagerWrapper = ({ mruTIDs, currentMruTID, deviceType, onSelecte
298
347
  onDeletedTID?.(tid);
299
348
  } }));
300
349
  };
301
- const TMSearchQueryPanelWrapper = ({ isSearchViewOnScreen, fromDTD, SQD, isExpertMode, showBackToResultButton, onSearchCompleted, onSqdSaved, onBackToResult, passToArchiveCallback, inputMids, maxDcmtsToBeReturned }) => {
350
+ const TMSearchQueryPanelWrapper = ({ isSearchViewOnScreen, fromDTD, SQD, isExpertMode, showBackToResultButton, onSearchCompleted, onSqdSaved, onBackToResult, passToArchiveCallback, inputMids, maxDcmtsToBeReturned, onRefreshRequested, onDcmtTypeReloaded }) => {
302
351
  const { setPanelVisibilityById, toggleMaximize, countVisibleLeafPanels } = useTMPanelManagerContext();
303
352
  const deviceType = useDeviceType();
304
353
  const isMobile = deviceType === DeviceType.MOBILE;
305
- return (_jsx(TMSearchQueryPanel, { isSearchViewOnScreen: isSearchViewOnScreen, onClosePanel: (!isMobile && countVisibleLeafPanels() > 1) ? () => setPanelVisibilityById('TMSearchQueryPanel', false) : undefined, allowMaximize: !isMobile && countVisibleLeafPanels() > 1, onMaximizePanel: (!isMobile && countVisibleLeafPanels() > 1) ? () => toggleMaximize("TMSearchQueryPanel") : undefined, isExpertMode: isExpertMode, fromDTD: fromDTD, SQD: SQD, inputMids: inputMids, onSearchCompleted: onSearchCompleted, onSqdSaved: onSqdSaved, showBackToResultButton: showBackToResultButton, onBackToResult: onBackToResult, passToArchiveCallback: passToArchiveCallback, maxDcmtsToBeReturned: maxDcmtsToBeReturned }));
354
+ return (_jsx(TMSearchQueryPanel, { isSearchViewOnScreen: isSearchViewOnScreen, onClosePanel: (!isMobile && countVisibleLeafPanels() > 1) ? () => setPanelVisibilityById('TMSearchQueryPanel', false) : undefined, allowMaximize: !isMobile && countVisibleLeafPanels() > 1, onMaximizePanel: (!isMobile && countVisibleLeafPanels() > 1) ? () => toggleMaximize("TMSearchQueryPanel") : undefined, isExpertMode: isExpertMode, fromDTD: fromDTD, SQD: SQD, inputMids: inputMids, onSearchCompleted: onSearchCompleted, onRefreshRequested: onRefreshRequested, onDcmtTypeReloaded: onDcmtTypeReloaded, onSqdSaved: onSqdSaved, showBackToResultButton: showBackToResultButton, onBackToResult: onBackToResult, passToArchiveCallback: passToArchiveCallback, maxDcmtsToBeReturned: maxDcmtsToBeReturned }));
306
355
  };
307
356
  const TMSavedQuerySelectorWrapper = ({ items, selectedId, allowShowSearch, manageDefault, onRefreshData, onItemClick, onDeleted, refreshFavoriteSavedQueries }) => {
308
357
  const { setPanelVisibilityById } = useTMPanelManagerContext();
@@ -22,6 +22,9 @@ interface ITMSearchQueryPanelProps {
22
22
  value: string;
23
23
  }>, tid?: number) => void;
24
24
  maxDcmtsToBeReturned?: number;
25
+ onRefreshRequested?: () => Promise<void>;
26
+ /** Notifica il tipo documento riletto dal server prima di ogni ricerca, così il chiamante allinea il proprio descrittore */
27
+ onDcmtTypeReloaded?: (dtd: DcmtTypeDescriptor) => void;
25
28
  }
26
29
  declare const TMSearchQueryPanel: React.FunctionComponent<ITMSearchQueryPanelProps>;
27
30
  export default TMSearchQueryPanel;