@topconsultnpm/sdkui-react 6.22.0-dev2.10 → 6.22.0-dev2.12

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.
@@ -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;
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useCallback, useRef, useState, forwardRef, useImperativeHandle } from 'react';
3
- import styled from 'styled-components';
3
+ import styled, { css, keyframes } from 'styled-components';
4
4
  import { getAppModuleGradient, IconArrowLeft, IconClearButton, IconWindowMaximize, IconWindowMinimize, isPositiveNumber, SDKUI_Localizator } from '../../helper';
5
5
  import TMButton from './TMButton';
6
6
  import { Gutters } from '../../utils/theme';
@@ -96,6 +96,77 @@ const StyledPanelContent = styled.div `
96
96
  outline: none;
97
97
  }
98
98
  `;
99
+ // Allineata al TABLET_BREAKPOINT (768) di TMDeviceProvider
100
+ const MOBILE_MEDIA_QUERY = '(max-width: 767.98px)';
101
+ // L'eccedenza da scorrere è "larghezza viewport - larghezza testo", ma nessuna delle due
102
+ // è nota al CSS. La si ottiene componendo due percentuali che si risolvono su box diversi:
103
+ // la track (larga quanto il viewport) va a +100% della PROPRIA larghezza, il testo a -100%
104
+ // della SUA. Le due animazioni hanno stessa durata ed easing, quindi lo scostamento netto
105
+ // 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); }
110
+ `;
111
+ const panelTitleTextMarquee = keyframes `
112
+ 0%, 15% { transform: translateX(0); }
113
+ 45%, 55% { transform: translateX(-100%); }
114
+ 85%, 100% { transform: translateX(0); }
115
+ `;
116
+ // 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;
125
+ `;
126
+ // 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
+ `}
142
+ `;
143
+ // 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
+ `}
169
+ `;
99
170
  /**
100
171
  * TMPanel - Pannello con supporto Controlled/Uncontrolled
101
172
  *
@@ -103,7 +174,7 @@ const StyledPanelContent = styled.div `
103
174
  * - Se NON passata → Uncontrolled: gestione interna dello stato attivo (plug-and-play)
104
175
  * - Se passata → Controlled: il parent gestisce lo stato tramite onActivate
105
176
  */
106
- const TMPanel = forwardRef(({ allowMaximize = true, color, backgroundColor, backgroundColorContainer, children, showHeader = true, title, totalItems, displayedItemsCount, toolbar, padding = '5px', isVisible = true, panelID = 'tmpanel', isActive, // Questa prop determinerà la modalità
177
+ 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à
107
178
  onActivate, onBack, onClose, onHeaderDoubleClick, onMaximize, onActiveChanged }, ref) => {
108
179
  const [isMaximized, setIsMaximized] = useState(false);
109
180
  // Detect device type (e.g., mobile or desktop)
@@ -157,6 +228,7 @@ onActivate, onBack, onClose, onHeaderDoubleClick, onMaximize, onActiveChanged },
157
228
  handleFocusUncontrolled();
158
229
  }
159
230
  };
231
+ const titleMarqueeKey = enableTitleMarquee && (typeof title === 'string' || typeof title === 'number') ? String(title) : undefined;
160
232
  // handler for external maximize management
161
233
  const handleMaximize = () => {
162
234
  setIsMaximized(prevState => {
@@ -188,24 +260,11 @@ onActivate, onBack, onClose, onHeaderDoubleClick, onMaximize, onActiveChanged },
188
260
  flexDirection: 'row',
189
261
  alignItems: 'center',
190
262
  flexShrink: 0
191
- }, children: _jsxs(StyledBackButton, { "$isActive": currentIsActive, onClick: () => onBack ? onBack() : null, title: SDKUI_Localizator.Back, children: [_jsx(IconArrowLeft, {}), !isMobile && _jsx("span", { children: SDKUI_Localizator.Back })] }) }), _jsx("div", { style: {
192
- flex: 1,
193
- display: 'flex',
194
- alignItems: 'center',
195
- justifyContent: 'flex-start',
196
- minWidth: 0,
197
- overflow: 'hidden',
198
- padding: '0 8px'
199
- }, children: _jsxs("div", { style: {
200
- whiteSpace: 'nowrap',
201
- overflow: 'hidden',
202
- textOverflow: 'ellipsis',
203
- margin: 0
204
- }, children: [title, isPositiveNumber(displayedItemsCount) && isPositiveNumber(totalItems)
205
- ? ` (${displayedItemsCount} / ${totalItems})`
206
- : isPositiveNumber(totalItems)
207
- ? ` (${totalItems})`
208
- : ''] }) }), _jsxs("div", { style: {
263
+ }, children: _jsxs(StyledBackButton, { "$isActive": currentIsActive, onClick: () => onBack ? onBack() : null, title: SDKUI_Localizator.Back, children: [_jsx(IconArrowLeft, {}), !isMobile && _jsx("span", { children: SDKUI_Localizator.Back })] }) }), _jsx(StyledPanelTitleViewport, { children: _jsx(StyledPanelTitleTrack, { "$marquee": enableTitleMarquee, children: _jsxs(StyledPanelTitle, { "$marquee": enableTitleMarquee, children: [title, isPositiveNumber(displayedItemsCount) && isPositiveNumber(totalItems)
264
+ ? ` (${displayedItemsCount} / ${totalItems})`
265
+ : isPositiveNumber(totalItems)
266
+ ? ` (${totalItems})`
267
+ : ''] }) }, titleMarqueeKey) }), _jsxs("div", { style: {
209
268
  display: 'flex',
210
269
  flexDirection: 'row',
211
270
  alignItems: 'center',
@@ -77,6 +77,11 @@ const TMTreeView = ({ dataSource = [], focusedItem, selectedItems = [], allowMul
77
77
  const getVisibleItems = useCallback((node) => {
78
78
  if (!node.items)
79
79
  return [];
80
+ // Con la virtualizzazione react-window renderizza già solo le righe visibili:
81
+ // la paginazione nasconderebbe gli items oltre itemsPerPage senza renderizzare
82
+ // il paginatore, presente solo nel render standard (renderTree).
83
+ if (enableVirtualization)
84
+ return node.items;
80
85
  const totalItems = node.items.length;
81
86
  // Se non c'è paginazione attiva o gli items sono pochi, mostra tutti
82
87
  if (totalItems <= itemsPerPage || !showLoadMoreButton) {
@@ -87,7 +92,7 @@ const TMTreeView = ({ dataSource = [], focusedItem, selectedItems = [], allowMul
87
92
  const startIndex = currentPage * itemsPerPage;
88
93
  const endIndex = startIndex + itemsPerPage;
89
94
  return node.items.slice(startIndex, endIndex);
90
- }, [itemsPerPage, showLoadMoreButton]);
95
+ }, [itemsPerPage, showLoadMoreButton, enableVirtualization]);
91
96
  // Misura le dimensioni del container per react-window
92
97
  useEffect(() => {
93
98
  if (!enableVirtualization || !containerRef.current)
@@ -7,7 +7,7 @@ import { DcmtOperationTypes, FormModes } from '../../../ts';
7
7
  import { StyledToolbarCardContainer, StyledFormButtonsContainer } from '../../base/Styled';
8
8
  import { useDeviceType, DeviceType } from '../../base/TMDeviceProvider';
9
9
  import { TMSplitterLayout } from '../../base/TMLayout';
10
- import { TMExceptionBoxManager } from '../../base/TMPopUp';
10
+ import { TMExceptionBoxManager, TMMessageBoxManager, ButtonNames } from '../../base/TMPopUp';
11
11
  import TMShowAllOrMaxItemsButton from '../../base/TMShowAllOrMaxItemsButton';
12
12
  import TMSpinner from '../../base/TMSpinner';
13
13
  import { TMLayoutWaitingContainer } from '../../base/TMWaitPanel';
@@ -114,10 +114,24 @@ const TMBatchUpdateForm = ({ inputDcmts, TID, DID, isExpertMode = SDKUI_Globals.
114
114
  const totalItems = metadataValues.filter(o => o.mid && o.mid > 100).length;
115
115
  const metadataValuesSource = metadataValues.filter(o => o.mid && o.mid > 100).slice(0, showAll ? metadataValues.filter(o => o.mid && o.mid > 100).length : listMaxItems);
116
116
  const metadataValuesSourceOrig = metadataValuesOrig.filter(o => o.mid && o.mid > 100).slice(0, showAll ? metadataValuesOrig.filter(o => o.mid && o.mid > 100).length : listMaxItems);
117
+ /**
118
+ * Chiusura del form: con modifiche pendenti chiede conferma prima di scartarle.
119
+ * Il TMModal è montato con askClosingConfirm, quindi non si auto-nasconde: la chiusura
120
+ * effettiva avviene solo quando onBack smonta il componente.
121
+ */
122
+ const handleClose = () => {
123
+ if (!isModified) {
124
+ onBack?.();
125
+ return;
126
+ }
127
+ TMMessageBoxManager.show({
128
+ title: SDKUI_Localizator.Cancel, message: SDKUI_Localizator.ConfirmOnCancel, buttons: [ButtonNames.YES, ButtonNames.NO],
129
+ onButtonClick: (e) => { if (e === ButtonNames.YES)
130
+ onBack?.(); }
131
+ });
132
+ };
117
133
  const renderContent = () => {
118
- return (_jsx(TMLayoutWaitingContainer, { showWaitPanel: showWaitPanel, showWaitPanelPrimary: showPrimary, waitPanelTitle: waitPanelTitle, waitPanelTextPrimary: waitPanelTextPrimary, waitPanelValuePrimary: waitPanelValuePrimary, waitPanelMaxValuePrimary: waitPanelMaxValuePrimary, isCancelable: true, abortController: abortController, children: _jsxs(TMSplitterLayout, { direction: 'horizontal', overflow: 'visible', separatorSize: Gutters.getGutters(), separatorColor: 'transparent', separatorActiveColor: 'transparent', showSeparator: showFormulaEditor, start: showFormulaEditor ? ['50%', '50%'] : ['100%', '0%'], min: ['0', '0'], children: [_jsx(TMPanel, { title: isModal ? 'Modifica i metadati' : `${SDKUI_Localizator.BatchUpdate} (${inputDcmts.length} documenti selezionati)`, onBack: deviceType === DeviceType.MOBILE ? onBack : undefined,
119
- // onClose={deviceType !== DeviceType.MOBILE ? onBack : undefined}
120
- allowMaximize: !isModal, showHeader: !isModal, children: _jsxs(StyledToolbarCardContainer, { children: [_jsx(TMMetadataValues, { TID: TID, isExpertMode: isExpertMode, showCheckBoxes: ShowCheckBoxesMode.Always, showNullValueCheckBoxes: true, metadataValues: metadataValuesSource, metadataValuesOrig: metadataValuesSourceOrig, validationItems: validationItems, isOpenDistinctValues: showDistinctValuesPanel, selectedMID: focusedMetadataValue?.mid, onFocusedItemChanged: (item) => { setFocusedMetadataValue(item); }, onValueChanged: (newItems) => {
134
+ return (_jsx(TMLayoutWaitingContainer, { showWaitPanel: showWaitPanel, showWaitPanelPrimary: showPrimary, waitPanelTitle: waitPanelTitle, waitPanelTextPrimary: waitPanelTextPrimary, waitPanelValuePrimary: waitPanelValuePrimary, waitPanelMaxValuePrimary: waitPanelMaxValuePrimary, isCancelable: true, abortController: abortController, children: _jsxs(TMSplitterLayout, { direction: 'horizontal', overflow: 'visible', separatorSize: Gutters.getGutters(), separatorColor: 'transparent', separatorActiveColor: 'transparent', showSeparator: showFormulaEditor, start: showFormulaEditor ? ['50%', '50%'] : ['100%', '0%'], min: ['0', '0'], children: [_jsx(TMPanel, { title: isModal ? 'Modifica i metadati' : `${SDKUI_Localizator.BatchUpdate} (${inputDcmts.length} documenti selezionati)`, onBack: deviceType === DeviceType.MOBILE ? handleClose : undefined, allowMaximize: !isModal, showHeader: !isModal, children: _jsxs(StyledToolbarCardContainer, { children: [_jsx(TMMetadataValues, { TID: TID, isExpertMode: isExpertMode, showCheckBoxes: ShowCheckBoxesMode.Always, showNullValueCheckBoxes: true, metadataValues: metadataValuesSource, metadataValuesOrig: metadataValuesSourceOrig, validationItems: validationItems, isOpenDistinctValues: showDistinctValuesPanel, selectedMID: focusedMetadataValue?.mid, onFocusedItemChanged: (item) => { setFocusedMetadataValue(item); }, onValueChanged: (newItems) => {
121
135
  setMetadataValues((prevItems) => prevItems.map((item) => {
122
136
  const newItem = newItems.find((newItem) => newItem.tid === item.tid && newItem.mid === item.mid);
123
137
  return newItem ? { ...item, ...newItem } : item;
@@ -153,7 +167,7 @@ const TMBatchUpdateForm = ({ inputDcmts, TID, DID, isExpertMode = SDKUI_Globals.
153
167
  } })] }) })] }) }));
154
168
  };
155
169
  return (_jsx(_Fragment, { children: (isModal && onBack)
156
- ? _jsx(TMModal, { title: titleModal, onClose: onBack, width: widthModal ?? '100%', height: heightModal ?? '100%', hidePopup: false, askClosingConfirm: true, children: _jsx("div", { style: { width: "100%", height: "100%", display: 'block', padding: "4px" }, children: renderContent() }) })
170
+ ? _jsx(TMModal, { title: titleModal, onClose: handleClose, width: widthModal ?? '100%', height: heightModal ?? '100%', hidePopup: false, askClosingConfirm: true, children: _jsx("div", { style: { width: "100%", height: "100%", display: 'block', padding: "4px" }, children: renderContent() }) })
157
171
  : renderContent() }));
158
172
  };
159
173
  export default TMBatchUpdateForm;
@@ -37,6 +37,7 @@ interface ITMSearchResultProps {
37
37
  openInOffice?: (selectedDcmtsOrFocused: Array<DcmtInfo>) => Promise<void>;
38
38
  onWFOperationCompleted?: () => Promise<void>;
39
39
  onSelectedTIDChanged?: (TID: number) => void;
40
+ onSelectedTIDCleared?: () => void;
40
41
  onRefreshSearchAsyncDatagrid?: () => Promise<void>;
41
42
  onRefreshAfterAddDcmtToFavs?: () => void;
42
43
  onFileOpened?: (blob: File | undefined) => void;
@@ -57,7 +57,7 @@ groupId, searchResults = [], context = SearchResultContext.METADATA_SEARCH, titl
57
57
  // Boolean flags to enable/disable features
58
58
  autoFocusFirstRow = true, formAutoOpen, isVisible = true, allowRelations = true, openDcmtFormAsModal = false, showSearchResultSidebar = true, showDcmtFormSidebar = true, showSelector = false, isClosable = false, allowFloatingBar = true, showToolbarHeader = true, showBackButton = true, disableAccordionIfSingleCategory = false, editPdfForm = false, openS4TViewer = false, showTodoDcmtForm = false, showToppyDraggableHelpCenter = true, toppyHelpCenterUsePortal = false, showNoDcmtFoundMessage = true, enablePinIcons = true,
59
59
  // Callbacks (optional)
60
- openInOffice, onRefreshAfterAddDcmtToFavs, onRefreshSearchAsyncDatagrid, onSelectedTIDChanged, onWFOperationCompleted, onClose, onFileOpened, onTaskCreateRequest, openWGsCopyMoveForm, openCommentFormCallback, openAddDocumentForm, onOpenS4TViewerRequest, onOpenPdfEditorRequest, openFileUploaderPdfEditor, passToArchiveCallback, onReferenceClick,
60
+ openInOffice, onRefreshAfterAddDcmtToFavs, onRefreshSearchAsyncDatagrid, onSelectedTIDChanged, onSelectedTIDCleared, onWFOperationCompleted, onClose, onFileOpened, onTaskCreateRequest, openWGsCopyMoveForm, openCommentFormCallback, openAddDocumentForm, onOpenS4TViewerRequest, onOpenPdfEditorRequest, openFileUploaderPdfEditor, passToArchiveCallback, onReferenceClick,
61
61
  // Tasks
62
62
  allTasks = [], getAllTasks, deleteTaskByIdsCallback, addTaskCallback, editTaskCallback,
63
63
  // Navigation
@@ -69,13 +69,19 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
69
69
  // Quando l'utente seleziona manualmente, il ref viene settato a false e il focus automatico
70
70
  // torna al comportamento normale.
71
71
  const isFirstAutoFocusRef = useRef(true);
72
+ // Mostra il pannello lista. È una ref perché il TMPanelManagerProvider sta dentro al JSX di
73
+ // questo componente, quindi qui il context non si può usare: la riempie ShowMainPanelBridge.
74
+ const showMainPanelRef = useRef(null);
72
75
  // ID state to force remount of components
73
76
  const [id, setID] = useState('');
74
77
  // Document type descriptor of the currently document
75
78
  const [fromDTD, setFromDTD] = useState();
76
79
  const [currentDcmt, setCurrentDcmt] = useState();
77
- // State to specify if the details form is open or not
78
- const [splitterSize, setSplitterSize] = useState(['100%', '0']);
80
+ // Passo corrente. Su mobile decide anche quale pannello è a schermo (vedi isSteppedLayout).
81
+ // 'auto' -> l'utente non ha ancora scelto: si può selezionare il primo tipo documento
82
+ // 'selector' -> è tornato indietro col back: non selezionare niente al posto suo
83
+ // 'results' -> è sulla griglia del tipo scelto
84
+ const [navStep, setNavStep] = useState('auto');
79
85
  // State to control the selected search result
80
86
  const [selectedSearchResult, setSelectedSearchResult] = useState();
81
87
  // State to control the search results to show in the datagrid
@@ -377,9 +383,20 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
377
383
  openTaskFormHandler,
378
384
  },
379
385
  });
380
- const { isOpenDcmtForm, openFormHandler, dcmtFormLayoutMode, onDcmtFormOpenChange, showSearchTMDatagrid, showExportForm, isOpenBatchUpdate, isModifiedBatchUpdate, updateBatchUpdateForm, closeDcmtFormHandler, handleSignApprove, checkoutInfo: { showCicoWaitPanel, cicoWaitPanelTitle, showCicoPrimaryProgress, cicoPrimaryProgressText, cicoPrimaryProgressValue, cicoPrimaryProgressMax, }, dcmtOperations: { abortController, showWaitPanel, showPrimary, waitPanelTitle, waitPanelTextPrimary, waitPanelValuePrimary, waitPanelMaxValuePrimary, showSecondary, waitPanelTextSecondary, waitPanelValueSecondary, waitPanelMaxValueSecondary, downloadDcmtsAsync, runOperationAsync, }, relatedDocumentsInfo: { isOpenDetails, isOpenMaster, checkRelatedDcmtsArchiveCapability, checkManyToManyCapability, }, toppyOperations: { showApprovePopup, showRejectPopup, showReAssignPopup, showMoreInfoPopup, updateShowApprovePopup, updateShowRejectPopup, updateShowReAssignPopup, updateShowMoreInfoPopup } } = features;
386
+ const { isOpenDcmtForm, openFormHandler, dcmtFormLayoutMode, onDcmtFormOpenChange, showSearchTMDatagrid, showDcmtTypesSelection, showExportForm, updateBatchUpdateForm, closeDcmtFormHandler, handleSignApprove, checkoutInfo: { showCicoWaitPanel, cicoWaitPanelTitle, showCicoPrimaryProgress, cicoPrimaryProgressText, cicoPrimaryProgressValue, cicoPrimaryProgressMax, }, dcmtOperations: { abortController, showWaitPanel, showPrimary, waitPanelTitle, waitPanelTextPrimary, waitPanelValuePrimary, waitPanelMaxValuePrimary, showSecondary, waitPanelTextSecondary, waitPanelValueSecondary, waitPanelMaxValueSecondary, downloadDcmtsAsync, runOperationAsync, }, relatedDocumentsInfo: { isOpenDetails, isOpenMaster, checkRelatedDcmtsArchiveCapability, checkManyToManyCapability, }, toppyOperations: { showApprovePopup, showRejectPopup, showReAssignPopup, showMoreInfoPopup, updateShowApprovePopup, updateShowRejectPopup, updateShowReAssignPopup, updateShowMoreInfoPopup } } = features;
381
387
  const deviceType = useDeviceType();
382
388
  const isMobile = deviceType === DeviceType.MOBILE;
389
+ const hasSingleDcmtType = currentSearchResults.length === 1;
390
+ // Su mobile si vede un pannello per volta; su desktop selettore e griglia sono affiancati
391
+ const isSteppedLayout = showSelector && isMobile;
392
+ // Un solo tipo documento su desktop: non c'è niente da scegliere, quindi il selettore sparisce
393
+ // (larghezza 0, nessun separatore) e lo spazio va tutto alla griglia. Il menu "Altro" permette
394
+ // di riaprirlo (vedi canToggleDcmtTypesSelection nell'hook).
395
+ const isSelectorCollapsed = showSelector && !isSteppedLayout && hasSingleDcmtType && !showDcmtTypesSelection;
396
+ // Senza selettore lo spazio va tutto alla griglia; altrimenti a step su mobile, affiancati su desktop
397
+ const splitterSizes = (!showSelector || isSelectorCollapsed) ? ['0%', '100%']
398
+ : isSteppedLayout ? (navStep === 'results' ? ['0', '100%'] : ['100%', '0'])
399
+ : ['30%', '70%'];
383
400
  const selectedDocs = getSelectedDcmtsOrFocused(selectedItems, focusedItem);
384
401
  // Disable the "Sign/Approve" button if:
385
402
  // 1. No document or multiple documents are selected, OR
@@ -400,8 +417,14 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
400
417
  setSelectedSearchResult(undefined);
401
418
  return;
402
419
  }
420
+ // Un solo tipo documento: selezionalo e vai ai risultati, a meno che l'utente sia tornato indietro
403
421
  if (searchResults.length === 1) {
422
+ if (navStep === 'selector') {
423
+ setSelectedSearchResult(undefined);
424
+ return;
425
+ }
404
426
  setSelectedSearchResult(searchResults[0]);
427
+ setNavStep('results');
405
428
  return;
406
429
  }
407
430
  // Se il TID precedentemente selezionato esiste ancora nei nuovi risultati, mantienilo.
@@ -415,6 +438,11 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
415
438
  return;
416
439
  }
417
440
  }
441
+ // L'utente è sul selettore
442
+ if (isMobile && navStep !== 'results') {
443
+ setSelectedSearchResult(undefined);
444
+ return;
445
+ }
418
446
  // Altrimenti seleziona il primo risultato ordinato
419
447
  setSelectedSearchResult(orderByName(searchResults)[0]);
420
448
  }, [searchResults]);
@@ -533,47 +561,75 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
533
561
  let counters = (showSelector && disableAccordionIfSingleCategory && searchResults.length > 1) ? getSearchResultCountersSingleCategory(searchResults) : "";
534
562
  if (title)
535
563
  return title + counters;
536
- let titleHeader = 'Ricerca per metadati';
564
+ // Nome del tipo documento selezionato, accodato al titolo
565
+ const selectedDcmtTypeName = selectedSearchResult?.fromName ?? (searchResults?.length === 1 ? searchResults[0]?.fromName : undefined);
566
+ const withSelectedDcmtTypeName = (baseTitle) => selectedDcmtTypeName ? `${baseTitle} - ${selectedDcmtTypeName}` : baseTitle;
567
+ let titleHeader = withSelectedDcmtTypeName('Ricerca per metadati');
537
568
  switch (context) {
538
569
  case SearchResultContext.MASTER_DETAIL:
539
570
  case SearchResultContext.METADATA_SEARCH:
540
571
  titleHeader = `${searchResults?.length > 1 ? selectedSearchResult?.fromName ?? 'Ricerca per metadati' : searchResults[0]?.fromName ?? 'Ricerca per metadati'}`;
541
572
  break;
542
573
  case SearchResultContext.FAVORITES_AND_RECENTS:
543
- titleHeader = SDKUI_Localizator.FavoritesAndRecentDcmts;
574
+ titleHeader = withSelectedDcmtTypeName(SDKUI_Localizator.FavoritesAndRecentDcmts);
544
575
  break;
545
576
  case SearchResultContext.WORKFLOW_APPROVE:
546
- titleHeader = SDKUI_Localizator.WorkflowApproval;
577
+ titleHeader = withSelectedDcmtTypeName(SDKUI_Localizator.WorkflowApproval);
547
578
  break;
548
579
  case SearchResultContext.FREE_SEARCH:
549
- titleHeader = `Risultato della ricerca full text`;
580
+ titleHeader = withSelectedDcmtTypeName('Risultato della ricerca full text');
550
581
  break;
551
582
  }
552
583
  return titleHeader + counters;
553
584
  };
554
- const onBack = () => {
555
- if (isOpenBatchUpdate && isModifiedBatchUpdate) {
556
- TMMessageBoxManager.show({
557
- title: SDKUI_Localizator.Cancel, message: SDKUI_Localizator.ConfirmOnCancel, buttons: [ButtonNames.YES, ButtonNames.NO],
558
- onButtonClick: (e) => {
559
- if (e !== ButtonNames.YES)
560
- return;
561
- setSplitterSize(['100%', '0']);
562
- onClose?.();
563
- }
564
- });
565
- }
566
- else if (isOpenDcmtForm) {
567
- onDcmtFormOpenChange(false, LayoutModes.Update);
568
- }
569
- else {
570
- if (isMobile && currentSearchResults.length > 1) {
571
- setSelectedSearchResult(undefined);
572
- }
573
- setSplitterSize(['100%', '0']);
574
- onClose?.();
585
+ // Dai risultati si risale al selettore dei tipi documento
586
+ const canGoBackToSelector = isSteppedLayout && navStep === 'results' && selectedSearchResult !== undefined;
587
+ /**
588
+ * Back di tutti i pannelli (e X di chiusura se isClosable). Su mobile risale un livello alla volta:
589
+ * pannello secondario -> lista dei risultati -> selettore dei tipi documento -> uscita.
590
+ * Su desktop i pannelli sono affiancati, quindi resta solo l'uscita.
591
+ *
592
+ * @param fromSecondaryPanel - true se il back arriva da un pannello secondario
593
+ */
594
+ const onBack = useCallback((fromSecondaryPanel = false) => {
595
+ // Non è previsto nessun modo di uscire: non fare niente
596
+ if (!showBackButton && !isClosable)
597
+ return;
598
+ // Torna alla lista senza toccare gli stati: l'utente deve ritrovare la sua selezione
599
+ if (fromSecondaryPanel) {
600
+ showMainPanelRef.current?.();
601
+ return;
575
602
  }
576
- };
603
+ // Sia il ritorno al selettore sia l'uscita ripartono da una lista pulita
604
+ setSelectedSearchResult(undefined);
605
+ setFocusedItem(undefined);
606
+ setSelectedItems([]);
607
+ setSelectedRowKeysFromDataGrid([]);
608
+ setCurrentDcmt(undefined);
609
+ setCurrentMetadataValues([]);
610
+ setShowIndexingInfo(false);
611
+ isFirstAutoFocusRef.current = true;
612
+ // Il chiamante può tenere allineato il suo TID selezionato (es. selectedSearchResultTID)
613
+ onSelectedTIDCleared?.();
614
+ // [2] l'utente ha chiesto la lista: resta lì finché non sceglie lui, niente auto-selezione.
615
+ // [1] si esce: si riparte da capo, quindi l'auto-selezione torna permessa.
616
+ setNavStep(canGoBackToSelector ? 'selector' : 'auto');
617
+ // [2] C'è ancora un livello sopra: restiamo dentro, sul selettore
618
+ if (canGoBackToSelector)
619
+ return;
620
+ // [1] Si esce e si avvisa il chiamante
621
+ onClose?.();
622
+ }, [canGoBackToSelector, showBackButton, isClosable, onClose, onSelectedTIDCleared]);
623
+ /**
624
+ * Back del pannello lista. `undefined` = freccia non renderizzata, quindi la mostriamo solo se
625
+ * ha dove tornare: uno step intermedio, o un onClose per uscire. Con `isClosable` basta la X.
626
+ */
627
+ const backHandler = (showBackButton && !isClosable && (canGoBackToSelector || onClose !== undefined)) ? onBack : undefined;
628
+ /**
629
+ * Back dei pannelli secondari: la stessa onBack marcata come livello [3]. Solo su mobile:
630
+ * su desktop stanno affiancati alla lista e non c'è niente da cui tornare.
631
+ */
632
+ const backHandlerSecondary = useMemo(() => (isMobile && backHandler) ? () => backHandler(true) : undefined, [isMobile, backHandler]);
577
633
  const refreshDataGridAfterRemoveAsync = async () => {
578
634
  let index = selectedSearchResult?.dtdResult?.columns?.findIndex(col => col.caption === 'DID');
579
635
  let selectedRows = [];
@@ -605,21 +661,6 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
605
661
  return newResults;
606
662
  });
607
663
  };
608
- /**
609
- * Restituisce l'handler onBack per i pannelli, evitando duplicazione di codice.
610
- * @param isMainPanel - Se true, è il pannello principale dei risultati di ricerca
611
- */
612
- const getOnBackHandler = (isMainPanel = false) => {
613
- // Se showBackButton è false, non mostrare mai il back
614
- if (showBackButton === false)
615
- return undefined;
616
- // Mobile: mostra back su tutti i pannelli (se showBackButton è definito e non è closable)
617
- if (isMobile) {
618
- return (showBackButton !== undefined && !isClosable) ? onBack : undefined;
619
- }
620
- // Desktop: mostra back solo sul pannello principale dei risultati (se showBackButton è definito e non è closable)
621
- return (showBackButton !== undefined && isMainPanel && !isClosable) ? onBack : undefined;
622
- };
623
664
  const removeDcmtFromFavsOrRecents = async () => {
624
665
  switch (selectedSearchResult?.category) {
625
666
  case "Favorites":
@@ -636,9 +677,9 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
636
677
  };
637
678
  const onSearchResultSelectionChanged = (e) => {
638
679
  setSelectedSearchResult(e);
639
- if (deviceType === DeviceType.MOBILE) {
640
- setSplitterSize(['0', '100%']);
641
- }
680
+ // Scelto il tipo documento si passa ai risultati (a livello di layout è ignorato se i due
681
+ // step sono affiancati, ma vale comunque come "l'utente non è più fermo sul selettore")
682
+ setNavStep('results');
642
683
  onSelectedTIDChanged?.(e.fromTID);
643
684
  };
644
685
  const showToppyForApprove = useMemo(() => {
@@ -666,8 +707,8 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
666
707
  const tmSearchResult = useMemo(() => (!searchResults || searchResults.length <= 0)
667
708
  ? _jsxs("div", { style: { display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', height: '100%', width: '100%' }, children: [_jsx(IconBoard, { fontSize: 96 }), showNoDcmtFoundMessage && _jsx("div", { style: { fontSize: "15px", marginTop: "10px" }, children: SDKUI_Localizator.NoDcmtFound }), openAddDocumentForm && _jsx("div", { style: { marginTop: "10px" }, children: _jsx(TMButton, { fontSize: "15px", icon: _jsx("i", { className: 'dx-icon-share' }), caption: SDKUI_Localizator.Share, onClick: openAddDocumentForm }) })] })
668
709
  :
669
- _jsxs(_Fragment, { children: [_jsxs(TMLayoutItem, { height: '100%', children: [_jsxs("div", { ref: floatingBarContainerRef, style: { position: 'relative', height: '100%', width: '100%' }, children: [_jsxs(TMSplitterLayout, { direction: 'horizontal', overflow: 'visible', separatorSize: Gutters.getGutters(), separatorActiveColor: 'transparent', separatorColor: 'transparent', min: ['0', '0'], showSeparator: showSelector && deviceType !== DeviceType.MOBILE, start: showSelector ? deviceType !== DeviceType.MOBILE ? ['30%', '70%'] : splitterSize : ['0%', '100%'], children: [showSelector ?
670
- _jsx(TMLayoutItem, { children: _jsx(TMSearchResultSelector, { searchResults: currentSearchResults, disableAccordionIfSingleCategory: disableAccordionIfSingleCategory, selectedTID: selectedSearchResultTID, selectedSearchResult: selectedSearchResult, autoSelectFirst: !isMobile || currentSearchResults.length === 1, onSelectionChanged: onSearchResultSelectionChanged }) })
710
+ _jsxs(_Fragment, { children: [_jsxs(TMLayoutItem, { height: '100%', children: [_jsxs("div", { ref: floatingBarContainerRef, style: { position: 'relative', height: '100%', width: '100%' }, children: [_jsxs(TMSplitterLayout, { direction: 'horizontal', overflow: 'visible', separatorSize: Gutters.getGutters(), separatorActiveColor: 'transparent', separatorColor: 'transparent', min: ['0', '0'], showSeparator: showSelector && !isSteppedLayout && !isSelectorCollapsed, start: splitterSizes, children: [showSelector ?
711
+ _jsx(TMLayoutItem, { children: _jsx(TMSearchResultSelector, { searchResults: currentSearchResults, disableAccordionIfSingleCategory: disableAccordionIfSingleCategory, selectedTID: selectedSearchResultTID, selectedSearchResult: selectedSearchResult, autoSelectFirst: !isMobile || hasSingleDcmtType, onSelectionChanged: onSearchResultSelectionChanged }) })
671
712
  :
672
713
  _jsx(_Fragment, {}), _jsx(TMLayoutItem, { children: _jsx(TMSearchResultGrid, { openInOffice: openInOffice, fromDTD: fromDTD, operationItems: operationItems, allUsers: allUsers, inputFocusedItem: focusedItem, inputSelectedItems: selectedItems, showExportForm: showExportForm, onFocusedItemChanged: handleFocusedItemChangedFromGrid, onDownloadDcmtsAsync: async (inputDcmts, downloadType, downloadMode, _y, confirmAttachments) => await downloadDcmtsAsync({ inputDcmts, downloadType, downloadMode, onFileDownloaded: onFileOpened, confirmAttachments }), lastUpdateSearchTime: lastUpdateSearchTime, searchResult: searchResults.length > 1 ? selectedSearchResult : searchResults[0], onSelectionChanged: (items) => { setSelectedItems(items); }, onDblClick: () => openFormHandler(LayoutModes.Update), showSearchTMDatagrid: showSearchTMDatagrid, onVisibleItemChanged: setVisibleItems, updateDataColumnsFromDataGrid: updateDataColumnsFromDataGrid, updateDataSourceFromDataGrid: updateDataSourceFromDataGrid, updateSelectedRowKeysFromDataGrid: updateSelectedRowKeysFromDataGrid, disableAutoFocus: autoFocusFirstRow === false && isFirstAutoFocusRef.current, dcmtsReturned: dcmtsReturned, dcmtsFound: dcmtsFound }) })] }), renderFloatingBar] }), _jsx(TMToppyDraggableHelpCenter, { usePortal: toppyHelpCenterUsePortal, isVisible: isToppyHelpCenterVisible, content: _jsx("div", { style: { display: 'flex', flexDirection: 'column', gap: '10px' }, children: _jsx(WorkFlowOperationButtons, { dtd: fromDTD, deviceType: deviceType, onApprove: () => {
673
714
  updateShowApprovePopup(true);
@@ -689,7 +730,6 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
689
730
  showApprovePopup,
690
731
  showRejectPopup,
691
732
  showReAssignPopup,
692
- isOpenBatchUpdate,
693
733
  isOpenDetails,
694
734
  isOpenMaster,
695
735
  isOpenDcmtForm,
@@ -700,6 +740,10 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
700
740
  openS4TViewer,
701
741
  showBackButton,
702
742
  isMobile,
743
+ navStep,
744
+ isSteppedLayout,
745
+ isSelectorCollapsed,
746
+ currentSearchResults,
703
747
  currentMetadataValues,
704
748
  onRefreshSearchAsyncDatagrid,
705
749
  handleNavigateToWGs,
@@ -709,7 +753,7 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
709
753
  ]);
710
754
  const tmBlog = useMemo(() => _jsx(TMDcmtBlog, { tid: focusedItem?.TID, did: focusedItem?.DID, fetchBlogDataTrigger: refreshBlogTrigger, allTasks: allTasks, getAllTasks: getAllTasks, deleteTaskByIdsCallback: deleteTaskByIdsCallback, addTaskCallback: addTaskCallback, editTaskCallback: editTaskCallback, handleNavigateToWGs: handleNavigateToWGs, handleNavigateToDossiers: handleNavigateToDossiers }), [focusedItem, allTasks, refreshBlogTrigger, handleNavigateToWGs, handleNavigateToDossiers, onRefreshBlogDatagrid]);
711
755
  const tmSysMetadata = useMemo(() => _jsx(TMMetadataValues, { layoutMode: LayoutModes.Update, openChooserBySingleClick: true, TID: focusedItem?.TID, isReadOnly: true, deviceType: deviceType, metadataValues: currentMetadataValues.filter(o => (o.mid != undefined && o.mid <= 100)), metadataValuesOrig: currentMetadataValues.filter(o => (o.mid != undefined && o.mid <= 100)), validationItems: [] }), [focusedItem, currentMetadataValues, deviceType]);
712
- const tmDcmtPreview = useMemo(() => _jsx(TMDcmtPreviewWrapper, { refreshPreviewTrigger: refreshPreviewTrigger, currentDcmt: currentDcmt, onBack: getOnBackHandler() }), [currentDcmt, refreshPreviewTrigger]);
756
+ const tmDcmtPreview = useMemo(() => _jsx(TMDcmtPreviewWrapper, { refreshPreviewTrigger: refreshPreviewTrigger, currentDcmt: currentDcmt, onBack: backHandlerSecondary }), [currentDcmt, refreshPreviewTrigger, backHandlerSecondary]);
713
757
  // Auto-fetch indexing info when drawer is open and focusedItem changes
714
758
  useEffect(() => {
715
759
  if (!focusedItem || !showIndexingInfo)
@@ -788,9 +832,10 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
788
832
  component: tmSearchResult,
789
833
  panelContainer: {
790
834
  title: getTitleHeader(),
835
+ enableTitleMarquee: true,
791
836
  showHeader: showToolbarHeader,
792
837
  allowMaximize: !isMobile,
793
- onBack: getOnBackHandler(true),
838
+ onBack: backHandler,
794
839
  onClose: isClosable ? onBack : undefined,
795
840
  toolbar: searchResutlToolbar
796
841
  },
@@ -805,7 +850,7 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
805
850
  panelContainer: {
806
851
  title: SDKUI_Localizator.BlogCase,
807
852
  allowMaximize: !isMobile,
808
- onBack: getOnBackHandler(),
853
+ onBack: backHandlerSecondary,
809
854
  }
810
855
  },
811
856
  toolbarOptions: { icon: _jsx(IconBoard, { fontSize: 24 }), visible: getSearchToolbarVisibility(SDK_Globals.tmSession?.SessionDescr?.appModuleID ?? AppModules.SURFER).tmBlog, orderNumber: 2, isActive: allInitialPanelVisibility['tmBlog'] }
@@ -818,7 +863,7 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
818
863
  panelContainer: {
819
864
  title: SDKUI_Localizator.MetadataSystem,
820
865
  allowMaximize: !isMobile,
821
- onBack: getOnBackHandler(),
866
+ onBack: backHandlerSecondary,
822
867
  }
823
868
  },
824
869
  toolbarOptions: { icon: _jsx(IconDcmtTypeSys, { fontSize: 24 }), visible: getSearchToolbarVisibility(SDK_Globals.tmSession?.SessionDescr?.appModuleID ?? AppModules.SURFER).tmSysMetadata, orderNumber: 3, isActive: allInitialPanelVisibility['tmSysMetadata'] }
@@ -832,7 +877,7 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
832
877
  panelContainer: {
833
878
  title: SDKUI_Localizator.ResultDetails,
834
879
  allowMaximize: !isMobile,
835
- onBack: getOnBackHandler(),
880
+ onBack: backHandlerSecondary,
836
881
  }
837
882
  },
838
883
  toolbarOptions: { icon: _jsx(IconPlatform, { fontSize: 20 }), visible: getSearchToolbarVisibility(SDK_Globals.tmSession?.SessionDescr?.appModuleID ?? AppModules.SURFER).tmFullTextSearch, orderNumber: 4, isActive: allInitialPanelVisibility['tmFullTextSearch'] }
@@ -846,7 +891,7 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
846
891
  },
847
892
  toolbarOptions: { icon: _jsx(IconShow, { fontSize: 24 }), visible: getSearchToolbarVisibility(SDK_Globals.tmSession?.SessionDescr?.appModuleID ?? AppModules.SURFER).tmDcmtPreview, orderNumber: context === SearchResultContext.FREE_SEARCH ? 5 : 4, isActive: allInitialPanelVisibility['tmDcmtPreview'] }
848
893
  }
849
- ], [tmSearchResult, tmBlog, tmSysMetadata, tmDcmtPreview, tmFullTextSearch, showToolbarHeader, context, isMobile]);
894
+ ], [tmSearchResult, tmBlog, tmSysMetadata, tmDcmtPreview, tmFullTextSearch, showToolbarHeader, context, isMobile, backHandler, backHandlerSecondary, isClosable, onBack]);
850
895
  return (_jsxs(StyledMultiViewPanel, { "$isVisible": isVisible, children: [_jsx(StyledMultiViewPanel, { "$isVisible": !isOpenDcmtForm && !isOpenDetails && !isOpenMaster, style: {
851
896
  display: 'flex',
852
897
  flexDirection: isMobile ? 'column' : 'row',
@@ -855,9 +900,9 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
855
900
  width: '100%',
856
901
  height: '100%',
857
902
  }, children: _jsx(TMLayoutWaitingContainer, { direction: 'vertical', showWaitPanel: showWaitPanel, showWaitPanelPrimary: showPrimary, showWaitPanelSecondary: showSecondary, waitPanelTitle: waitPanelTitle, waitPanelTextPrimary: waitPanelTextPrimary, waitPanelValuePrimary: waitPanelValuePrimary, waitPanelMaxValuePrimary: waitPanelMaxValuePrimary, waitPanelTextSecondary: waitPanelTextSecondary, waitPanelValueSecondary: waitPanelValueSecondary, waitPanelMaxValueSecondary: waitPanelMaxValueSecondary, isCancelable: true, abortController: abortController, children: _jsx(TMLayoutWaitingContainer, { direction: 'vertical', showWaitPanel: showCicoWaitPanel, showWaitPanelPrimary: showCicoPrimaryProgress, waitPanelTitle: cicoWaitPanelTitle, waitPanelTextPrimary: cicoPrimaryProgressText, waitPanelValuePrimary: cicoPrimaryProgressValue, waitPanelMaxValuePrimary: cicoPrimaryProgressMax, isCancelable: true, abortController: abortControllerLocal, children: (groupId && groupId.length > 0) ?
858
- _jsxs(_Fragment, { children: [_jsx(PanelDisabledStateHandler, { isBoardDisabled: isBoardDisabled }), _jsx(TMPanelManagerContainer, { panels: initialPanels, direction: "horizontal", parentId: groupId, showToolbar: showSearchResultSidebar })] })
903
+ _jsxs(_Fragment, { children: [_jsx(PanelDisabledStateHandler, { isBoardDisabled: isBoardDisabled }), _jsx(ShowMainPanelBridge, { showMainPanelRef: showMainPanelRef }), _jsx(TMPanelManagerContainer, { panels: initialPanels, direction: "horizontal", parentId: groupId, showToolbar: showSearchResultSidebar })] })
859
904
  :
860
- _jsxs(TMPanelManagerProvider, { panels: initialPanels, initialVisibility: allInitialPanelVisibility, defaultDimensions: initialPanelDimensions, initialDimensions: initialPanelDimensions, initialMobilePanelId: 'tmSearchResult', children: [_jsx(PanelDisabledStateHandler, { isBoardDisabled: isBoardDisabled }), _jsx(TMPanelManagerContainer, { panels: initialPanels, direction: "horizontal", parentId: groupId, showToolbar: showSearchResultSidebar })] }) }) }) }), renderDcmtOperations] }));
905
+ _jsxs(TMPanelManagerProvider, { panels: initialPanels, initialVisibility: allInitialPanelVisibility, defaultDimensions: initialPanelDimensions, initialDimensions: initialPanelDimensions, initialMobilePanelId: 'tmSearchResult', children: [_jsx(PanelDisabledStateHandler, { isBoardDisabled: isBoardDisabled }), _jsx(ShowMainPanelBridge, { showMainPanelRef: showMainPanelRef }), _jsx(TMPanelManagerContainer, { panels: initialPanels, direction: "horizontal", parentId: groupId, showToolbar: showSearchResultSidebar })] }) }) }) }), renderDcmtOperations] }));
861
906
  };
862
907
  export default TMSearchResult;
863
908
  const TMSearchResultGrid = ({ openInOffice, fromDTD, operationItems, allUsers, inputFocusedItem, allowMultipleSelection = true, showExportForm = false, onFocusedItemChanged, onDownloadDcmtsAsync, onVisibleItemChanged, inputSelectedItems = [], lastUpdateSearchTime, searchResult, onSelectionChanged, onDblClick, showSearchTMDatagrid, updateDataColumnsFromDataGrid, updateDataSourceFromDataGrid, updateSelectedRowKeysFromDataGrid, disableAutoFocus = false, dcmtsReturned, dcmtsFound }) => {
@@ -1319,14 +1364,29 @@ const TMSearchResultSelector = ({ searchResults = [], disableAccordionIfSingleCa
1319
1364
  }
1320
1365
  }
1321
1366
  }, [sortedCategories.length, autoSelectFirst]);
1367
+ // La preselezione esterna va applicata una volta per valore: riapplicarla a ogni cambio di
1368
+ // searchResults faceva sì che un refresh annullasse la navigazione dell'utente.
1369
+ const appliedSelectedTIDRef = useRef(undefined);
1322
1370
  useEffect(() => {
1323
- if (selectedTID !== undefined) {
1324
- const found = searchResults.find(r => Number(r.fromTID) === selectedTID);
1325
- handleSelect(found);
1371
+ // Richiesta ritirata: dimentichiamo il valore applicato, così se il chiamante richiede di
1372
+ // nuovo lo stesso TID la preselezione riparte
1373
+ if (selectedTID === undefined) {
1374
+ appliedSelectedTIDRef.current = undefined;
1375
+ return;
1326
1376
  }
1377
+ if (appliedSelectedTIDRef.current === selectedTID)
1378
+ return;
1379
+ const found = searchResults.find(r => Number(r.fromTID) === selectedTID);
1380
+ if (!found)
1381
+ return; // risultati non ancora arrivati: riproviamo al prossimo giro
1382
+ appliedSelectedTIDRef.current = selectedTID;
1383
+ handleSelect(found);
1327
1384
  }, [selectedTID, searchResults]);
1328
- const handleSelect = useCallback((result) => {
1329
- if (selectedResult !== result && result !== undefined) {
1385
+ // force = true per i click dell'utente: la selezione va notificata anche se il tipo documento è già quello selezionato
1386
+ const handleSelect = useCallback((result, force = false) => {
1387
+ if (result === undefined)
1388
+ return;
1389
+ if (selectedResult !== result || force) {
1330
1390
  setSelectedResult(result);
1331
1391
  onSelectionChanged?.(result);
1332
1392
  }
@@ -1371,14 +1431,24 @@ const TMSearchResultSelector = ({ searchResults = [], disableAccordionIfSingleCa
1371
1431
  renderItem: (result, isSelected) => (_jsxs("div", { style: { width: '100%', padding: '5px', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }, children: [_jsx(TMTidViewer, { tid: result.fromTID, did: Number(result.dtdResult?.rows?.[0]?.[1]), showIcon: true }), _jsx("div", { style: { padding: 3, display: 'flex', alignItems: 'center', justifyContent: 'center', backgroundColor: 'white', color: 'gray', borderRadius: 3 }, children: result.dcmtsReturned })] })),
1372
1432
  itemHeight: 40
1373
1433
  })), [sortedCategories, groupedResults, searchResults]);
1374
- const renderCategoryItems = (category) => (_jsx("div", { style: { padding: '5px', height: '100%', overflow: 'auto' }, children: groupedResults[category].map((result, index) => (_jsx(MemoizedStyledItemTemplate, { "$isSelected": selectedResult === result, onClick: () => handleSelect(result), children: renderItemTemplate(result) }, index))) }));
1434
+ const renderCategoryItems = (category) => (_jsx("div", { style: { padding: '5px', height: '100%', overflow: 'auto' }, children: groupedResults[category].map((result, index) => (_jsx(MemoizedStyledItemTemplate, { "$isSelected": selectedResult === result, onClick: () => handleSelect(result, true), children: renderItemTemplate(result) }, index))) }));
1375
1435
  if (disableAccordionIfSingleCategory && sortedCategories.length === 1) {
1376
1436
  const category = sortedCategories[0];
1377
1437
  return renderCategoryItems(category);
1378
1438
  }
1379
- return (_jsx(TMAccordionNew, { groups: accordionGroups, selectedItem: selectedResult, onSelectedItemChange: (result) => handleSelect(result) }));
1439
+ return (_jsx(TMAccordionNew, { groups: accordionGroups, selectedItem: selectedResult, onSelectedItemChange: (result) => handleSelect(result, true) }));
1380
1440
  };
1381
1441
  //#endregion TMSearchResultSelector
1442
+ /** Senza UI: vive dentro il provider e passa a onBack, via ref, l'azione "mostra il pannello lista". */
1443
+ const ShowMainPanelBridge = ({ showMainPanelRef }) => {
1444
+ const { setPanelVisibilityById } = useTMPanelManagerContext();
1445
+ useEffect(() => {
1446
+ // Su mobile mostrare un pannello nasconde gli altri; su desktop la lista è già visibile
1447
+ showMainPanelRef.current = () => setPanelVisibilityById('tmSearchResult', true);
1448
+ return () => { showMainPanelRef.current = null; };
1449
+ }, [setPanelVisibilityById, showMainPanelRef]);
1450
+ return null;
1451
+ };
1382
1452
  const PanelDisabledStateHandler = ({ isBoardDisabled }) => {
1383
1453
  const { setPanelVisibilityById, setToolbarButtonDisabled } = useTMPanelManagerContext();
1384
1454
  useEffect(() => {
@@ -405,6 +405,7 @@ export declare class SDKUI_Localizator {
405
405
  static get Grids(): string;
406
406
  static get Hide_CompleteName(): "Vollständigen Namen ausblenden" | "Hide full name" | "Ocultar nombre completo" | "Masquer le nom complet" | "Ocultar nome completo" | "Nascondi nome completo";
407
407
  static get HideAll(): "Alle ausblenden" | "Hide all" | "Ocultar todo" | "Masquer tout" | "Ocultar tudo" | "Nascondi tutti";
408
+ static get HideDcmtTypes(): string;
408
409
  static get HideDetailsWithZeroDocs(): string;
409
410
  static get HideFloatingBar(): string;
410
411
  static get HideFilters(): string;
@@ -764,6 +765,7 @@ export declare class SDKUI_Localizator {
764
765
  static get Shortcuts(): "Tastenkombinationen" | "Shortcuts" | "Atajos" | "Raccourcis" | "Atalhos" | "Scorciatoie";
765
766
  static get ShowAll(): "Alle anzeigen" | "Show all" | "Mostrar todo" | "Tout afficher" | "Mostrar tudo" | "Mostra tutti";
766
767
  static get ShowColumnSelection(): string;
768
+ static get ShowDcmtTypes(): string;
767
769
  static get ShowFloatingBar(): string;
768
770
  static get ShowLess(): "Weniger anzeigen" | "Show less" | "Mostrar menos" | "Afficher moins" | "Mostra meno";
769
771
  static get Show_CompleteName(): "Vollständigen Namen anzeigen" | "View full name" | "Mostrar nombre completo" | "Afficher le nom complet" | "Mostrar nome completo" | "Visualizza nome completo";
@@ -4018,6 +4018,16 @@ export class SDKUI_Localizator {
4018
4018
  default: return "Nascondi tutti";
4019
4019
  }
4020
4020
  }
4021
+ static get HideDcmtTypes() {
4022
+ switch (this._cultureID) {
4023
+ case CultureIDs.De_DE: return "Dokumenttypen ausblenden";
4024
+ case CultureIDs.En_US: return "Hide document types";
4025
+ case CultureIDs.Es_ES: return "Ocultar tipos de documento";
4026
+ case CultureIDs.Fr_FR: return "Masquer les types de document";
4027
+ case CultureIDs.Pt_PT: return "Ocultar tipos de documento";
4028
+ default: return "Nascondi tipi documento";
4029
+ }
4030
+ }
4021
4031
  static get HideDetailsWithZeroDocs() {
4022
4032
  switch (this._cultureID) {
4023
4033
  case CultureIDs.De_DE: return "Details mit 0 Dokumenten ausblenden";
@@ -7620,6 +7630,16 @@ export class SDKUI_Localizator {
7620
7630
  default: return "Mostra selezione colonne";
7621
7631
  }
7622
7632
  }
7633
+ static get ShowDcmtTypes() {
7634
+ switch (this._cultureID) {
7635
+ case CultureIDs.De_DE: return "Dokumenttypen anzeigen";
7636
+ case CultureIDs.En_US: return "Show document types";
7637
+ case CultureIDs.Es_ES: return "Mostrar tipos de documento";
7638
+ case CultureIDs.Fr_FR: return "Afficher les types de document";
7639
+ case CultureIDs.Pt_PT: return "Mostrar tipos de documento";
7640
+ default: return "Mostra tipi documento";
7641
+ }
7642
+ }
7623
7643
  static get ShowFloatingBar() {
7624
7644
  switch (this._cultureID) {
7625
7645
  case CultureIDs.De_DE: return "Floating-Leiste anzeigen";
@@ -118,6 +118,7 @@ export interface UseDocumentOperationsResult {
118
118
  closeDcmtFormHandler: () => void;
119
119
  handleSignApprove: () => void;
120
120
  showSearchTMDatagrid: boolean;
121
+ showDcmtTypesSelection: boolean;
121
122
  showExportForm: boolean;
122
123
  checkoutInfo: UseCheckInOutOperationsReturn;
123
124
  relatedDocumentsInfo: UseRelatedDocumentsReturn;
@@ -152,6 +152,7 @@ export const useDocumentOperations = (props) => {
152
152
  const [currentCustomButton, setCurrentCustomButton] = useState();
153
153
  // State to control the visibility of the details form
154
154
  const [showSearchTMDatagrid, setShowSearchTMDatagrid] = useState(false);
155
+ const [showDcmtTypesSelection, setShowDcmtTypesSelection] = useState(false);
155
156
  const [secondaryMasterDcmts, setSecondaryMasterDcmts] = useState([]);
156
157
  const [isOpenSharedArchive, setIsOpenSharedArchive] = useState(false);
157
158
  const [sharedDcmtFile, setSharedDcmtFile] = useState(undefined);
@@ -1054,6 +1055,7 @@ export const useDocumentOperations = (props) => {
1054
1055
  ]
1055
1056
  };
1056
1057
  };
1058
+ const canToggleDcmtTypesSelection = !isMobile && context === SearchResultContext.WORKFLOW_APPROVE;
1057
1059
  const otherOperationsMenuItem = () => {
1058
1060
  return {
1059
1061
  id: 'oth',
@@ -1085,7 +1087,15 @@ export const useDocumentOperations = (props) => {
1085
1087
  disabled: false,
1086
1088
  onClick: () => setShowFloatingBar(prev => !prev)
1087
1089
  }]
1088
- : [])
1090
+ : []),
1091
+ {
1092
+ id: 'oth-dcmt-types',
1093
+ icon: showDcmtTypesSelection ? _jsx(IconHide, {}) : _jsx(IconShow, {}),
1094
+ name: showDcmtTypesSelection ? SDKUI_Localizator.HideDcmtTypes : SDKUI_Localizator.ShowDcmtTypes,
1095
+ visible: canToggleDcmtTypesSelection,
1096
+ disabled: false,
1097
+ onClick: () => setShowDcmtTypesSelection(prev => !prev)
1098
+ }
1089
1099
  ]
1090
1100
  };
1091
1101
  };
@@ -1454,6 +1464,7 @@ export const useDocumentOperations = (props) => {
1454
1464
  dcmtFormLayoutMode,
1455
1465
  onDcmtFormOpenChange,
1456
1466
  showSearchTMDatagrid,
1467
+ showDcmtTypesSelection,
1457
1468
  showExportForm,
1458
1469
  isOpenBatchUpdate,
1459
1470
  isModifiedBatchUpdate,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@topconsultnpm/sdkui-react",
3
- "version": "6.22.0-dev2.10",
3
+ "version": "6.22.0-dev2.12",
4
4
  "description": "",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1",
@@ -39,7 +39,7 @@
39
39
  "lib"
40
40
  ],
41
41
  "dependencies": {
42
- "@topconsultnpm/sdk-ts": "6.22.0-dev2.4",
42
+ "@topconsultnpm/sdk-ts": "6.22.0-dev2.5",
43
43
  "@zip.js/zip.js": "2.8.26",
44
44
  "buffer": "^6.0.3",
45
45
  "devextreme": "^25.2.6",