@topconsultnpm/sdkui-react 6.22.0-dev2.9 → 6.22.0-t2

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 (214) hide show
  1. package/lib/components/NewComponents/ContextMenu/TMContextMenu.js +1 -1
  2. package/lib/components/NewComponents/ContextMenu/styles.d.ts +45 -43
  3. package/lib/components/NewComponents/FloatingMenuBar/TMFloatingMenuBar.js +1 -1
  4. package/lib/components/NewComponents/FloatingMenuBar/styles.d.ts +67 -60
  5. package/lib/components/base/Styled.d.ts +93 -77
  6. package/lib/components/base/TMCounterBar.d.ts +2 -2
  7. package/lib/components/base/TMCounterContainer.d.ts +2 -1
  8. package/lib/components/base/TMCustomButton.d.ts +1 -1
  9. package/lib/components/base/TMDataGrid.d.ts +2 -0
  10. package/lib/components/base/TMDataGrid.js +260 -8
  11. package/lib/components/base/TMDataGridExportForm.d.ts +2 -1
  12. package/lib/components/base/TMEditorBase.d.ts +2 -0
  13. package/lib/components/base/TMFileManager.d.ts +2 -1
  14. package/lib/components/base/TMFileManager.js +5 -3
  15. package/lib/components/base/TMFileManagerDataGridView.d.ts +2 -1
  16. package/lib/components/base/TMFileManagerDataGridView.js +11 -2
  17. package/lib/components/base/TMFileManagerThumbnailItems.d.ts +1 -1
  18. package/lib/components/base/TMFileManagerThumbnailItems.js +19 -9
  19. package/lib/components/base/TMFileManagerThumbnailsView.d.ts +1 -1
  20. package/lib/components/base/TMFileManagerUtils.d.ts +6 -6
  21. package/lib/components/base/TMFileManagerUtils.js +37 -1
  22. package/lib/components/base/TMLayout.d.ts +4 -4
  23. package/lib/components/base/TMList.d.ts +2 -1
  24. package/lib/components/base/TMListView.d.ts +1 -1
  25. package/lib/components/base/TMModal.d.ts +11 -0
  26. package/lib/components/base/TMModal.js +47 -13
  27. package/lib/components/base/TMPanel.d.ts +6 -0
  28. package/lib/components/base/TMPanel.js +138 -25
  29. package/lib/components/base/TMPopUp.js +3 -0
  30. package/lib/components/base/TMSpinner.d.ts +2 -1
  31. package/lib/components/base/TMTab.d.ts +4 -3
  32. package/lib/components/base/TMTooltip.d.ts +2 -1
  33. package/lib/components/base/TMTooltip.js +24 -8
  34. package/lib/components/base/TMTreeView.d.ts +14 -8
  35. package/lib/components/base/TMTreeView.js +108 -67
  36. package/lib/components/base/TMUserAvatar.d.ts +2 -1
  37. package/lib/components/base/TMVilViewer.d.ts +2 -1
  38. package/lib/components/base/TMWaitPanel.d.ts +3 -2
  39. package/lib/components/choosers/TMDcmtTypeChooser.js +4 -3
  40. package/lib/components/choosers/TMDistinctValues.d.ts +9 -7
  41. package/lib/components/choosers/TMDistinctValues.js +149 -142
  42. package/lib/components/choosers/TMDynDataListItemChooser.d.ts +1 -1
  43. package/lib/components/choosers/TMInvoiceRetrieveFormats.d.ts +2 -1
  44. package/lib/components/choosers/TMMetadataChooser.js +1 -1
  45. package/lib/components/choosers/TMOrderRetrieveFormats.d.ts +2 -1
  46. package/lib/components/choosers/TMQuickSearchInfo.d.ts +13 -0
  47. package/lib/components/choosers/TMQuickSearchInfo.js +279 -0
  48. package/lib/components/choosers/TMQuickSearchSettingsForm.d.ts +16 -0
  49. package/lib/components/choosers/TMQuickSearchSettingsForm.js +94 -0
  50. package/lib/components/choosers/TMRelationChooser.d.ts +3 -3
  51. package/lib/components/choosers/TMSelectedValuesSummary.d.ts +13 -0
  52. package/lib/components/choosers/TMSelectedValuesSummary.js +91 -0
  53. package/lib/components/choosers/TMUserChooser.d.ts +2 -2
  54. package/lib/components/editors/TMDropDown.d.ts +3 -0
  55. package/lib/components/editors/TMDropDown.js +197 -5
  56. package/lib/components/editors/TMEditorStyled.d.ts +20 -42
  57. package/lib/components/editors/TMFormulaEditor.d.ts +1 -1
  58. package/lib/components/editors/TMHtmlContentDisplay.d.ts +2 -1
  59. package/lib/components/editors/TMHtmlEditor.d.ts +2 -1
  60. package/lib/components/editors/TMTreeDropDown.d.ts +1 -1
  61. package/lib/components/features/archive/TMArchive.js +17 -3
  62. package/lib/components/features/assistant/TMToppyDraggableHelpCenter.d.ts +1 -1
  63. package/lib/components/features/blog/TMBlogCommentForm.d.ts +2 -1
  64. package/lib/components/features/documents/TMBatchUpdateForm.js +19 -5
  65. package/lib/components/features/documents/TMDcmtBlog.d.ts +3 -0
  66. package/lib/components/features/documents/TMDcmtBlog.js +4 -4
  67. package/lib/components/features/documents/TMDcmtForm.d.ts +7 -0
  68. package/lib/components/features/documents/TMDcmtForm.js +178 -51
  69. package/lib/components/features/documents/TMDcmtFormActionButtons.d.ts +12 -1
  70. package/lib/components/features/documents/TMDcmtFormActionButtons.js +77 -7
  71. package/lib/components/features/documents/TMDcmtIcon.d.ts +2 -1
  72. package/lib/components/features/documents/TMDcmtIcon.js +5 -3
  73. package/lib/components/features/documents/TMDcmtPreview.d.ts +8 -6
  74. package/lib/components/features/documents/TMDcmtPreview.js +10 -6
  75. package/lib/components/features/documents/TMDcmtTasks.d.ts +2 -1
  76. package/lib/components/features/documents/TMDragDropOverlay.d.ts +1 -1
  77. package/lib/components/features/documents/TMMasterDetailDcmts.js +95 -33
  78. package/lib/components/features/documents/TMMasterInfoFields.d.ts +20 -0
  79. package/lib/components/features/documents/TMMasterInfoFields.js +186 -0
  80. package/lib/components/features/documents/TMMergeToPdfForm.d.ts +3 -3
  81. package/lib/components/features/documents/TMMergeToPdfForm.js +30 -5
  82. package/lib/components/features/documents/TMMultiMasterDetailDcmtsForm.d.ts +46 -0
  83. package/lib/components/features/documents/TMMultiMasterDetailDcmtsForm.js +323 -0
  84. package/lib/components/features/documents/TMMultiMasterDetailDcmtsUtils.d.ts +370 -0
  85. package/lib/components/features/documents/TMMultiMasterDetailDcmtsUtils.js +283 -0
  86. package/lib/components/features/documents/TMRelationViewer.d.ts +14 -1
  87. package/lib/components/features/documents/TMRelationViewer.js +69 -20
  88. package/lib/components/features/search/TMDcmtCheckoutInfoForm.d.ts +2 -1
  89. package/lib/components/features/search/TMMetadataOutputForm.d.ts +2 -1
  90. package/lib/components/features/search/TMMetadataSorterForm.d.ts +2 -1
  91. package/lib/components/features/search/TMSavedQuerySelector.d.ts +2 -2
  92. package/lib/components/features/search/TMSearch.js +77 -11
  93. package/lib/components/features/search/TMSearchQueryEditor.js +5 -3
  94. package/lib/components/features/search/TMSearchQueryPanel.d.ts +13 -3
  95. package/lib/components/features/search/TMSearchQueryPanel.js +141 -67
  96. package/lib/components/features/search/TMSearchResult.d.ts +16 -0
  97. package/lib/components/features/search/TMSearchResult.js +429 -108
  98. package/lib/components/features/search/TMSearchResultFloatingActionButton.d.ts +1 -1
  99. package/lib/components/features/search/TMSignatureInfoContent.d.ts +2 -1
  100. package/lib/components/features/search/TMTreeSelector.js +109 -64
  101. package/lib/components/features/search/TMViewHistoryDcmt.d.ts +2 -1
  102. package/lib/components/features/tasks/TMTaskForm.d.ts +1 -1
  103. package/lib/components/features/tasks/TMTaskFormUtils.d.ts +10 -10
  104. package/lib/components/features/tasks/TMTasksAgenda.d.ts +1 -1
  105. package/lib/components/features/tasks/TMTasksCalendar.d.ts +1 -1
  106. package/lib/components/features/tasks/TMTasksHeader.d.ts +1 -1
  107. package/lib/components/features/tasks/TMTasksPanelContent.d.ts +1 -1
  108. package/lib/components/features/tasks/TMTasksUtilsView.d.ts +5 -5
  109. package/lib/components/features/tasks/TMTasksView.d.ts +1 -1
  110. package/lib/components/features/wg/TMWGsCopyMoveForm.d.ts +3 -2
  111. package/lib/components/features/workflow/TMWorkflowPopup.d.ts +18 -7
  112. package/lib/components/features/workflow/TMWorkflowPopup.js +8 -4
  113. package/lib/components/features/workflow/diagram/WorkitemRecipientsEditor.d.ts +3 -1
  114. package/lib/components/forms/Login/ChangePasswordInputs.d.ts +3 -1
  115. package/lib/components/forms/Login/Chooser.d.ts +2 -2
  116. package/lib/components/forms/TMChooserForm.d.ts +2 -1
  117. package/lib/components/forms/TMSaveForm.d.ts +4 -4
  118. package/lib/components/grids/TMBlogAttachments.d.ts +2 -1
  119. package/lib/components/grids/TMBlogAttachments.js +13 -2
  120. package/lib/components/grids/TMBlogHeader.d.ts +1 -1
  121. package/lib/components/grids/TMBlogHeader.js +5 -1
  122. package/lib/components/grids/TMBlogsPost.d.ts +3 -1
  123. package/lib/components/grids/TMBlogsPost.js +31 -4
  124. package/lib/components/grids/TMBlogsPostUtils.d.ts +10 -9
  125. package/lib/components/grids/TMBlogsPostUtils.js +14 -3
  126. package/lib/components/grids/TMValidationItemsList.d.ts +2 -1
  127. package/lib/components/layout/panelManager/TMPanelManagerContainer.d.ts +3 -1
  128. package/lib/components/layout/panelManager/TMPanelManagerContainer.js +24 -14
  129. package/lib/components/layout/panelManager/TMPanelManagerContext.d.ts +2 -2
  130. package/lib/components/layout/panelManager/TMPanelManagerContext.js +68 -93
  131. package/lib/components/layout/panelManager/TMPanelManagerToolbar.d.ts +5 -6
  132. package/lib/components/layout/panelManager/TMPanelManagerWithPersistenceProvider.d.ts +2 -2
  133. package/lib/components/layout/panelManager/TMPanelWrapper.d.ts +2 -2
  134. package/lib/components/layout/panelManager/TMPanelWrapper.js +2 -2
  135. package/lib/components/layout/panelManager/types.d.ts +3 -0
  136. package/lib/components/layout/panelManager/utils.js +13 -11
  137. package/lib/components/pages/TMPage.d.ts +1 -1
  138. package/lib/components/query/TMQueryEditor.d.ts +14 -10
  139. package/lib/components/settings/SettingsAppearance.d.ts +2 -1
  140. package/lib/components/sidebar/TMAboutApp.d.ts +2 -1
  141. package/lib/components/sidebar/TMCommandsPanel.d.ts +4 -4
  142. package/lib/components/sidebar/TMHeader.d.ts +3 -3
  143. package/lib/components/sidebar/TMHeader.js +30 -20
  144. package/lib/components/viewers/TMDataListItemViewer.d.ts +3 -2
  145. package/lib/components/viewers/TMDataUserIdItemViewer.d.ts +3 -2
  146. package/lib/components/viewers/TMMidViewer.d.ts +2 -2
  147. package/lib/components/viewers/TMMidViewer.js +4 -1
  148. package/lib/components/viewers/TMTidViewer.d.ts +2 -2
  149. package/lib/components/viewers/TMTidViewer.js +86 -25
  150. package/lib/css/tm-sdkui.css +1 -1
  151. package/lib/devextreme-license.d.ts +1 -0
  152. package/lib/devextreme-license.js +3 -0
  153. package/lib/helper/SDKUI_Globals.d.ts +110 -0
  154. package/lib/helper/SDKUI_Globals.js +151 -0
  155. package/lib/helper/SDKUI_Localizator.d.ts +44 -0
  156. package/lib/helper/SDKUI_Localizator.js +440 -0
  157. package/lib/helper/TMAppUpdatedModal.d.ts +2 -0
  158. package/lib/helper/TMAppUpdatedModal.js +134 -0
  159. package/lib/helper/TMCommandsContextMenu.d.ts +1 -1
  160. package/lib/helper/TMIcons.d.ts +280 -279
  161. package/lib/helper/TMIcons.js +3 -0
  162. package/lib/helper/TMPdfViewer.d.ts +2 -1
  163. package/lib/helper/TMToppyMessage.d.ts +2 -2
  164. package/lib/helper/TMUtils.d.ts +23 -4
  165. package/lib/helper/TMUtils.js +24 -2
  166. package/lib/helper/dataGridSearchHelper.d.ts +28 -0
  167. package/lib/helper/dataGridSearchHelper.js +51 -0
  168. package/lib/helper/dcmtsHelper.d.ts +20 -0
  169. package/lib/helper/dcmtsHelper.js +58 -1
  170. package/lib/helper/helpers.d.ts +12 -1
  171. package/lib/helper/helpers.js +25 -1
  172. package/lib/helper/index.d.ts +2 -0
  173. package/lib/helper/index.js +2 -0
  174. package/lib/helper/queryHelper.d.ts +6 -1
  175. package/lib/helper/queryHelper.js +8 -3
  176. package/lib/hooks/tmDistinctValuesGridHelper.d.ts +16 -0
  177. package/lib/hooks/tmDistinctValuesGridHelper.js +31 -0
  178. package/lib/hooks/useArchiveListForm.d.ts +39 -0
  179. package/lib/hooks/useArchiveListForm.js +46 -0
  180. package/lib/hooks/useCaseFlowApprove.d.ts +56 -0
  181. package/lib/hooks/useCaseFlowApprove.js +157 -0
  182. package/lib/hooks/useDataUserIdItem.d.ts +1 -0
  183. package/lib/hooks/useDataUserIdItem.js +1 -0
  184. package/lib/hooks/useDocumentOperations.d.ts +4 -0
  185. package/lib/hooks/useDocumentOperations.js +52 -10
  186. package/lib/hooks/useInputDialog.d.ts +2 -1
  187. package/lib/hooks/useMultiMasterDetailDcmts.d.ts +113 -0
  188. package/lib/hooks/useMultiMasterDetailDcmts.js +760 -0
  189. package/lib/hooks/useRelatedDocuments.d.ts +1 -0
  190. package/lib/hooks/useRelatedDocuments.js +22 -27
  191. package/lib/hooks/useShowDossiersV5.d.ts +12 -0
  192. package/lib/hooks/useShowDossiersV5.js +37 -0
  193. package/lib/hooks/useTMDistinctValuesMetadataDisplay.d.ts +18 -0
  194. package/lib/hooks/useTMDistinctValuesMetadataDisplay.js +103 -0
  195. package/lib/hooks/useTMDistinctValuesQuickSearch.d.ts +49 -0
  196. package/lib/hooks/useTMDistinctValuesQuickSearch.js +307 -0
  197. package/lib/hooks/useTMDistinctValuesSelection.d.ts +42 -0
  198. package/lib/hooks/useTMDistinctValuesSelection.js +103 -0
  199. package/lib/hooks/useTMDistinctValuesSource.d.ts +29 -0
  200. package/lib/hooks/useTMDistinctValuesSource.js +105 -0
  201. package/lib/index.d.ts +2 -0
  202. package/lib/index.js +5 -2
  203. package/lib/services/caseflow/CaseFlowService.d.ts +236 -0
  204. package/lib/services/caseflow/CaseFlowService.js +693 -0
  205. package/lib/services/caseflow/CaseFlowTemplateCacheService.d.ts +28 -0
  206. package/lib/services/caseflow/CaseFlowTemplateCacheService.js +121 -0
  207. package/lib/services/caseflow/caseFlowNameCache.d.ts +32 -0
  208. package/lib/services/caseflow/caseFlowNameCache.js +116 -0
  209. package/lib/services/caseflow/caseFlowWorkItemUtils.d.ts +61 -0
  210. package/lib/services/caseflow/caseFlowWorkItemUtils.js +113 -0
  211. package/lib/services/index.d.ts +4 -0
  212. package/lib/services/index.js +4 -0
  213. package/lib/services/platform_services.d.ts +4 -3
  214. package/package.json +9 -5
@@ -1,13 +1,13 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
+ import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react';
3
3
  import styled from 'styled-components';
4
4
  import { LoadIndicator } from 'devextreme-react';
5
5
  import { AppModules, DataColumnTypes, DataListViewModes, DcmtTypeListCacheService, LayoutCacheService, LayoutModes, MetadataDataDomains, SDK_Globals, SystemMIDsAsNumber, UserListCacheService, } from '@topconsultnpm/sdk-ts';
6
- import { deepCompare, formatDateTimeByMetadataFormat, formatNumberByMetadataFormat, generateUniqueColumnKeys, genUniqueId, getColumnFormatInfo, getSearchToolbarVisibility, IconAll, IconBoard, IconDcmtTypeSys, IconDelete, IconMenuVertical, IconPlatform, IconRefresh, IconSearchCheck, IconShow, isApprovalWorkflowView, isSign4TopEnabled, searchResultDescriptorToSimpleArray, searchResultToMetadataValues, SDKUI_Globals, SDKUI_Localizator } from '../../../helper';
6
+ import { customizeSearchByDisplayText, deepCompare, formatDateTimeByMetadataFormat, formatNumberByMetadataFormat, generateUniqueColumnKeys, genUniqueId, getColumnFormatInfo, getDateDisplayText, getNumberDisplayText, getSearchToolbarVisibility, IconAll, IconBoard, IconDcmtTypeSys, IconDelete, IconDuplicate, IconEdit, IconMenuVertical, IconPlatform, IconRefresh, IconSearchCheck, IconShow, isApprovalWorkflowView, isSign4TopEnabled, searchResultDescriptorToSimpleArray, searchResultToMetadataValues, SDKUI_Globals, SDKUI_Localizator, IconCloseOutline, getSearchResultPanelLayout, getSearchResultPanelLayoutSignature, hasSearchResultPanelLayout, saveSearchResultPanelLayout, subscribeSearchResultPanelLayout } from '../../../helper';
7
7
  import { CounterItemKey } from '../../base/TMCounterContainer';
8
8
  import { getDcmtCicoStatus } from '../../../helper/checkinCheckoutManager';
9
9
  import { DcmtOperationTypes, SearchResultContext, } from '../../../ts';
10
- import { Gutters } from '../../../utils/theme';
10
+ import { Gutters, TMColors } from '../../../utils/theme';
11
11
  import { useDataListItem } from '../../../hooks/useDataListItem';
12
12
  import { useDataUserIdItem } from '../../../hooks/useDataUserIdItem';
13
13
  import { getSelectedDcmtsOrFocused, useDocumentOperations } from '../../../hooks/useDocumentOperations';
@@ -23,7 +23,8 @@ import { StyledMultiViewPanel } from '../../base/Styled';
23
23
  import { TMLayoutWaitingContainer } from '../../base/TMWaitPanel';
24
24
  import TMMetadataValues from '../../editors/TMMetadataValues';
25
25
  import TMTidViewer from '../../viewers/TMTidViewer';
26
- import { TMPanelManagerProvider, useTMPanelManagerContext } from '../../layout/panelManager/TMPanelManagerContext';
26
+ import { useTMPanelManagerContext } from '../../layout/panelManager/TMPanelManagerContext';
27
+ import { TMPanelManagerWithPersistenceProvider } from '../../layout/panelManager/TMPanelManagerWithPersistenceProvider';
27
28
  import TMPanelManagerContainer from '../../layout/panelManager/TMPanelManagerContainer';
28
29
  import TMContextMenu from '../../NewComponents/ContextMenu/TMContextMenu';
29
30
  import TMToppyDraggableHelpCenter from '../assistant/TMToppyDraggableHelpCenter';
@@ -51,13 +52,25 @@ const orderByName = (array) => {
51
52
  return 1;
52
53
  } return 0; });
53
54
  };
55
+ //#endregion Helper Methods
56
+ /**
57
+ * Contesti che ricordano il layout dei pannelli, condividendone uno solo.
58
+ * Gli altri sono esclusi di proposito: FREE_SEARCH ha un pannello in più, quindi il suo layout non vale
59
+ * per gli altri, e i restanti mostrano il risultato dentro contenitori con esigenze di spazio loro.
60
+ */
61
+ const PERSISTED_LAYOUT_CONTEXTS = [
62
+ SearchResultContext.METADATA_SEARCH,
63
+ SearchResultContext.WORKFLOW_APPROVE,
64
+ ];
54
65
  const TMSearchResult = ({
55
66
  // Data
56
67
  groupId, searchResults = [], context = SearchResultContext.METADATA_SEARCH, title, selectedSearchResultTID, floatingActionConfig, workingGroupContext = undefined, inputDID,
57
68
  // Boolean flags to enable/disable features
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,
69
+ autoFocusFirstRow = true, formAutoOpen, isVisible = true, allowRelations = true, openDcmtFormAsModal = false, showSearchResultSidebar = true, showDcmtFormSidebar = true, showSelector = false, isClosable = false, allowFloatingBar = true, showToolbarHeader = true, showBackButton = true, backCaption, showToolbarCloseButton = false, disableAccordionIfSingleCategory = false, editPdfForm = false, openS4TViewer = false, showTodoDcmtForm = false, showToppyDraggableHelpCenter = true, toppyHelpCenterUsePortal = false, showNoDcmtFoundMessage = true, enablePinIcons = true, showCounterConfig = true, showActionsColumn = false, compactPager = false, onRowDuplicateRequest,
70
+ // Counters
71
+ counterConfig,
59
72
  // Callbacks (optional)
60
- openInOffice, onRefreshAfterAddDcmtToFavs, onRefreshSearchAsyncDatagrid, onSelectedTIDChanged, onWFOperationCompleted, onClose, onFileOpened, onTaskCreateRequest, openWGsCopyMoveForm, openCommentFormCallback, openAddDocumentForm, onOpenS4TViewerRequest, onOpenPdfEditorRequest, openFileUploaderPdfEditor, passToArchiveCallback, onReferenceClick,
73
+ openInOffice, onRefreshAfterAddDcmtToFavs, onRefreshSearchAsyncDatagrid, onSelectedTIDChanged, onSelectedTIDCleared, onWFOperationCompleted, onClose, onFileOpened, onTaskCreateRequest, openWGsCopyMoveForm, openCommentFormCallback, openAddDocumentForm, onOpenS4TViewerRequest, onOpenPdfEditorRequest, openFileUploaderPdfEditor, passToArchiveCallback, onReferenceClick,
61
74
  // Tasks
62
75
  allTasks = [], getAllTasks, deleteTaskByIdsCallback, addTaskCallback, editTaskCallback,
63
76
  // Navigation
@@ -69,13 +82,19 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
69
82
  // Quando l'utente seleziona manualmente, il ref viene settato a false e il focus automatico
70
83
  // torna al comportamento normale.
71
84
  const isFirstAutoFocusRef = useRef(true);
85
+ // Mostra il pannello lista. È una ref perché il TMPanelManagerProvider sta dentro al JSX di
86
+ // questo componente, quindi qui il context non si può usare: la riempie ShowMainPanelBridge.
87
+ const showMainPanelRef = useRef(null);
72
88
  // ID state to force remount of components
73
89
  const [id, setID] = useState('');
74
90
  // Document type descriptor of the currently document
75
91
  const [fromDTD, setFromDTD] = useState();
76
92
  const [currentDcmt, setCurrentDcmt] = useState();
77
- // State to specify if the details form is open or not
78
- const [splitterSize, setSplitterSize] = useState(['100%', '0']);
93
+ // Passo corrente. Su mobile decide anche quale pannello è a schermo (vedi isSteppedLayout).
94
+ // 'auto' -> l'utente non ha ancora scelto: si può selezionare il primo tipo documento
95
+ // 'selector' -> è tornato indietro col back: non selezionare niente al posto suo
96
+ // 'results' -> è sulla griglia del tipo scelto
97
+ const [navStep, setNavStep] = useState('auto');
79
98
  // State to control the selected search result
80
99
  const [selectedSearchResult, setSelectedSearchResult] = useState();
81
100
  // State to control the search results to show in the datagrid
@@ -303,7 +322,7 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
303
322
  createTaskFromDocumentOrWorkItem();
304
323
  }
305
324
  };
306
- const { operationItems, renderFloatingBar, renderDcmtOperations, features, } = useDocumentOperations({
325
+ const { operationItems, selectedDcmtInfos, renderFloatingBar, renderDcmtOperations, features, } = useDocumentOperations({
307
326
  context: context,
308
327
  documentData: {
309
328
  dtd: fromDTD,
@@ -377,9 +396,69 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
377
396
  openTaskFormHandler,
378
397
  },
379
398
  });
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;
399
+ const { isOpenDcmtForm, isDcmtFormModal, 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;
400
+ // ----- Azioni della colonna di riga (showActionsColumn) -----------------------------------------
401
+ // Le voci di operationItems agiscono sui documenti selezionati o, in mancanza, su quello a fuoco:
402
+ // non accettano una riga come argomento. Il click sull'icona quindi azzera la selezione e porta il
403
+ // fuoco sulla riga cliccata, poi attende. L'attesa non è sul fuoco ma su selectedDcmtInfos, che
404
+ // l'hook ricalcola in un effect (un render più tardi): lanciare l'operazione prima significherebbe
405
+ // eseguirla sulla riga precedente.
406
+ const pendingRowActionRef = useRef(undefined);
407
+ // La callback è letta da una ref: dipendere dal prop renderebbe nuova requestRowAction, e con essa
408
+ // la colonna delle azioni, a ogni render del padre (vedi actionsColumn in TMSearchResultGrid)
409
+ const onRowDuplicateRequestRef = useRef(onRowDuplicateRequest);
410
+ onRowDuplicateRequestRef.current = onRowDuplicateRequest;
411
+ const requestRowAction = useCallback((operationId, rowData) => {
412
+ if (!showActionsColumn)
413
+ return;
414
+ if (rowData?.TID === undefined || rowData?.DID === undefined) {
415
+ ShowAlert({ message: SDKUI_Localizator.InvalidDcmt, mode: "warning", duration: 3000 });
416
+ return;
417
+ }
418
+ // Duplica ridefinita dal chiamante: agisce sulla riga cliccata, senza toccare fuoco e selezione
419
+ if (operationId === 'dup' && onRowDuplicateRequestRef.current) {
420
+ onRowDuplicateRequestRef.current(rowData);
421
+ return;
422
+ }
423
+ pendingRowActionRef.current = { operationId, TID: rowData.TID, DID: rowData.DID };
424
+ isFirstAutoFocusRef.current = false;
425
+ setSelectedItems([]);
426
+ setFocusedItem(rowData);
427
+ }, [showActionsColumn]);
428
+ // La richiesta viene scartata se il documento sparisce dai risultati (es. refresh dopo
429
+ // un'eliminazione): senza questo resterebbe appesa e partirebbe al prossimo cambio di fuoco.
430
+ useEffect(() => { pendingRowActionRef.current = undefined; }, [selectedSearchResult, lastUpdateSearchTime]);
431
+ useEffect(() => {
432
+ if (!showActionsColumn)
433
+ return;
434
+ const pending = pendingRowActionRef.current;
435
+ if (!pending)
436
+ return;
437
+ // L'hook non ha ancora recepito la riga richiesta: si riproverà al prossimo aggiornamento
438
+ if (selectedDcmtInfos.length !== 1)
439
+ return;
440
+ if (selectedDcmtInfos[0].TID !== pending.TID || selectedDcmtInfos[0].DID !== pending.DID)
441
+ return;
442
+ pendingRowActionRef.current = undefined;
443
+ const operation = findOperationItemById(operationItems, pending.operationId);
444
+ // Ora che il fuoco è sulla riga, `disabled` riflette i permessi per quel documento
445
+ if (!operation || operation.disabled || operation.visible === false)
446
+ return;
447
+ operation.onClick?.();
448
+ }, [showActionsColumn, selectedDcmtInfos, operationItems]);
381
449
  const deviceType = useDeviceType();
382
450
  const isMobile = deviceType === DeviceType.MOBILE;
451
+ const hasSingleDcmtType = currentSearchResults.length === 1;
452
+ // Su mobile si vede un pannello per volta; su desktop selettore e griglia sono affiancati
453
+ const isSteppedLayout = showSelector && isMobile;
454
+ // Un solo tipo documento su desktop: non c'è niente da scegliere, quindi il selettore sparisce
455
+ // (larghezza 0, nessun separatore) e lo spazio va tutto alla griglia. Il menu "Altro" permette
456
+ // di riaprirlo (vedi canToggleDcmtTypesSelection nell'hook).
457
+ const isSelectorCollapsed = showSelector && !isSteppedLayout && hasSingleDcmtType && !showDcmtTypesSelection;
458
+ // Senza selettore lo spazio va tutto alla griglia; altrimenti a step su mobile, affiancati su desktop
459
+ const splitterSizes = (!showSelector || isSelectorCollapsed) ? ['0%', '100%']
460
+ : isSteppedLayout ? (navStep === 'results' ? ['0', '100%'] : ['100%', '0'])
461
+ : ['30%', '70%'];
383
462
  const selectedDocs = getSelectedDcmtsOrFocused(selectedItems, focusedItem);
384
463
  // Disable the "Sign/Approve" button if:
385
464
  // 1. No document or multiple documents are selected, OR
@@ -400,8 +479,14 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
400
479
  setSelectedSearchResult(undefined);
401
480
  return;
402
481
  }
482
+ // Un solo tipo documento: selezionalo e vai ai risultati, a meno che l'utente sia tornato indietro
403
483
  if (searchResults.length === 1) {
484
+ if (navStep === 'selector') {
485
+ setSelectedSearchResult(undefined);
486
+ return;
487
+ }
404
488
  setSelectedSearchResult(searchResults[0]);
489
+ setNavStep('results');
405
490
  return;
406
491
  }
407
492
  // Se il TID precedentemente selezionato esiste ancora nei nuovi risultati, mantienilo.
@@ -415,6 +500,11 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
415
500
  return;
416
501
  }
417
502
  }
503
+ // L'utente è sul selettore
504
+ if (isMobile && navStep !== 'results') {
505
+ setSelectedSearchResult(undefined);
506
+ return;
507
+ }
418
508
  // Altrimenti seleziona il primo risultato ordinato
419
509
  setSelectedSearchResult(orderByName(searchResults)[0]);
420
510
  }, [searchResults]);
@@ -471,7 +561,9 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
471
561
  return;
472
562
  if (focusedItem?.DID != inputDID)
473
563
  return;
474
- openFormHandler(LayoutModes.Update);
564
+ if (focusedItem?.TID === undefined)
565
+ return;
566
+ onDcmtFormOpenChange(true, LayoutModes.Update);
475
567
  }, [inputDID, formAutoOpen, focusedItem]);
476
568
  // Quando openS4TViewer è true, apri automaticamente il viewer S4T
477
569
  useEffect(() => {
@@ -531,47 +623,75 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
531
623
  let counters = (showSelector && disableAccordionIfSingleCategory && searchResults.length > 1) ? getSearchResultCountersSingleCategory(searchResults) : "";
532
624
  if (title)
533
625
  return title + counters;
534
- let titleHeader = 'Ricerca per metadati';
626
+ // Nome del tipo documento selezionato, accodato al titolo
627
+ const selectedDcmtTypeName = selectedSearchResult?.fromName ?? (searchResults?.length === 1 ? searchResults[0]?.fromName : undefined);
628
+ const withSelectedDcmtTypeName = (baseTitle) => selectedDcmtTypeName ? `${baseTitle} - ${selectedDcmtTypeName}` : baseTitle;
629
+ let titleHeader = withSelectedDcmtTypeName('Ricerca per metadati');
535
630
  switch (context) {
536
631
  case SearchResultContext.MASTER_DETAIL:
537
632
  case SearchResultContext.METADATA_SEARCH:
538
633
  titleHeader = `${searchResults?.length > 1 ? selectedSearchResult?.fromName ?? 'Ricerca per metadati' : searchResults[0]?.fromName ?? 'Ricerca per metadati'}`;
539
634
  break;
540
635
  case SearchResultContext.FAVORITES_AND_RECENTS:
541
- titleHeader = SDKUI_Localizator.FavoritesAndRecentDcmts;
636
+ titleHeader = withSelectedDcmtTypeName(SDKUI_Localizator.FavoritesAndRecentDcmts);
542
637
  break;
543
638
  case SearchResultContext.WORKFLOW_APPROVE:
544
- titleHeader = SDKUI_Localizator.WorkflowApproval;
639
+ titleHeader = withSelectedDcmtTypeName(SDKUI_Localizator.WorkflowApproval);
545
640
  break;
546
641
  case SearchResultContext.FREE_SEARCH:
547
- titleHeader = `Risultato della ricerca full text`;
642
+ titleHeader = withSelectedDcmtTypeName('Risultato della ricerca full text');
548
643
  break;
549
644
  }
550
645
  return titleHeader + counters;
551
646
  };
552
- const onBack = () => {
553
- if (isOpenBatchUpdate && isModifiedBatchUpdate) {
554
- TMMessageBoxManager.show({
555
- title: SDKUI_Localizator.Cancel, message: SDKUI_Localizator.ConfirmOnCancel, buttons: [ButtonNames.YES, ButtonNames.NO],
556
- onButtonClick: (e) => {
557
- if (e !== ButtonNames.YES)
558
- return;
559
- setSplitterSize(['100%', '0']);
560
- onClose?.();
561
- }
562
- });
563
- }
564
- else if (isOpenDcmtForm) {
565
- onDcmtFormOpenChange(false, LayoutModes.Update);
566
- }
567
- else {
568
- if (isMobile && currentSearchResults.length > 1) {
569
- setSelectedSearchResult(undefined);
570
- }
571
- setSplitterSize(['100%', '0']);
572
- onClose?.();
647
+ // Dai risultati si risale al selettore dei tipi documento
648
+ const canGoBackToSelector = isSteppedLayout && navStep === 'results' && selectedSearchResult !== undefined;
649
+ /**
650
+ * Back di tutti i pannelli (e X di chiusura se isClosable). Su mobile risale un livello alla volta:
651
+ * pannello secondario -> lista dei risultati -> selettore dei tipi documento -> uscita.
652
+ * Su desktop i pannelli sono affiancati, quindi resta solo l'uscita.
653
+ *
654
+ * @param fromSecondaryPanel - true se il back arriva da un pannello secondario
655
+ */
656
+ const onBack = useCallback((fromSecondaryPanel = false) => {
657
+ // Non è previsto nessun modo di uscire: non fare niente
658
+ if (!showBackButton && !isClosable)
659
+ return;
660
+ // Torna alla lista senza toccare gli stati: l'utente deve ritrovare la sua selezione
661
+ if (fromSecondaryPanel) {
662
+ showMainPanelRef.current?.();
663
+ return;
573
664
  }
574
- };
665
+ // Sia il ritorno al selettore sia l'uscita ripartono da una lista pulita
666
+ setSelectedSearchResult(undefined);
667
+ setFocusedItem(undefined);
668
+ setSelectedItems([]);
669
+ setSelectedRowKeysFromDataGrid([]);
670
+ setCurrentDcmt(undefined);
671
+ setCurrentMetadataValues([]);
672
+ setShowIndexingInfo(false);
673
+ isFirstAutoFocusRef.current = true;
674
+ // Il chiamante può tenere allineato il suo TID selezionato (es. selectedSearchResultTID)
675
+ onSelectedTIDCleared?.();
676
+ // [2] l'utente ha chiesto la lista: resta lì finché non sceglie lui, niente auto-selezione.
677
+ // [1] si esce: si riparte da capo, quindi l'auto-selezione torna permessa.
678
+ setNavStep(canGoBackToSelector ? 'selector' : 'auto');
679
+ // [2] C'è ancora un livello sopra: restiamo dentro, sul selettore
680
+ if (canGoBackToSelector)
681
+ return;
682
+ // [1] Si esce e si avvisa il chiamante
683
+ onClose?.();
684
+ }, [canGoBackToSelector, showBackButton, isClosable, onClose, onSelectedTIDCleared]);
685
+ /**
686
+ * Back del pannello lista. `undefined` = freccia non renderizzata, quindi la mostriamo solo se
687
+ * ha dove tornare: uno step intermedio, o un onClose per uscire. Con `isClosable` basta la X.
688
+ */
689
+ const backHandler = (showBackButton && !isClosable && (canGoBackToSelector || onClose !== undefined)) ? onBack : undefined;
690
+ /**
691
+ * Back dei pannelli secondari: la stessa onBack marcata come livello [3]. Solo su mobile:
692
+ * su desktop stanno affiancati alla lista e non c'è niente da cui tornare.
693
+ */
694
+ const backHandlerSecondary = useMemo(() => (isMobile && backHandler) ? () => backHandler(true) : undefined, [isMobile, backHandler]);
575
695
  const refreshDataGridAfterRemoveAsync = async () => {
576
696
  let index = selectedSearchResult?.dtdResult?.columns?.findIndex(col => col.caption === 'DID');
577
697
  let selectedRows = [];
@@ -603,21 +723,6 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
603
723
  return newResults;
604
724
  });
605
725
  };
606
- /**
607
- * Restituisce l'handler onBack per i pannelli, evitando duplicazione di codice.
608
- * @param isMainPanel - Se true, è il pannello principale dei risultati di ricerca
609
- */
610
- const getOnBackHandler = (isMainPanel = false) => {
611
- // Se showBackButton è false, non mostrare mai il back
612
- if (showBackButton === false)
613
- return undefined;
614
- // Mobile: mostra back su tutti i pannelli (se showBackButton è definito e non è closable)
615
- if (isMobile) {
616
- return (showBackButton !== undefined && !isClosable) ? onBack : undefined;
617
- }
618
- // Desktop: mostra back solo sul pannello principale dei risultati (se showBackButton è definito e non è closable)
619
- return (showBackButton !== undefined && isMainPanel && !isClosable) ? onBack : undefined;
620
- };
621
726
  const removeDcmtFromFavsOrRecents = async () => {
622
727
  switch (selectedSearchResult?.category) {
623
728
  case "Favorites":
@@ -634,9 +739,9 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
634
739
  };
635
740
  const onSearchResultSelectionChanged = (e) => {
636
741
  setSelectedSearchResult(e);
637
- if (deviceType === DeviceType.MOBILE) {
638
- setSplitterSize(['0', '100%']);
639
- }
742
+ // Scelto il tipo documento si passa ai risultati (a livello di layout è ignorato se i due
743
+ // step sono affiancati, ma vale comunque come "l'utente non è più fermo sul selettore")
744
+ setNavStep('results');
640
745
  onSelectedTIDChanged?.(e.fromTID);
641
746
  };
642
747
  const showToppyForApprove = useMemo(() => {
@@ -660,14 +765,15 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
660
765
  && !showTodoDcmtForm);
661
766
  }, [showToppyForApprove, showToppyDraggableHelpCenter, selectedDocs, showApprovePopup, showRejectPopup, showReAssignPopup, showMoreInfoPopup, editPdfForm, openS4TViewer, showTodoDcmtForm]);
662
767
  const searchResutlToolbar = _jsxs(_Fragment, { children: [context === SearchResultContext.FAVORITES_AND_RECENTS &&
663
- _jsx("div", { style: { display: 'flex', alignItems: 'center', gap: '5px' }, children: _jsx(TMButton, { btnStyle: 'icon', icon: _jsx(IconDelete, { color: 'white' }), caption: "Rimuovi da " + (selectedSearchResult?.category === "Favorites" ? '"Preferiti"' : '"Recenti"'), disabled: getSelectedDcmtsOrFocused(selectedItems, focusedItem).length <= 0, onClick: removeDcmtFromFavsOrRecents }) }), _jsx(TMButton, { btnStyle: 'icon', icon: _jsx(IconRefresh, { color: 'white' }), caption: SDKUI_Localizator.Refresh, onClick: onRefreshSearchAsyncDatagrid }), _jsx(TMContextMenu, { items: operationItems, trigger: "left", children: _jsx(IconMenuVertical, { color: 'white', cursor: 'pointer' }) })] });
768
+ _jsx("div", { style: { display: 'flex', alignItems: 'center', gap: '5px' }, children: _jsx(TMButton, { btnStyle: 'icon', icon: _jsx(IconDelete, { color: 'white' }), caption: "Rimuovi da " + (selectedSearchResult?.category === "Favorites" ? '"Preferiti"' : '"Recenti"'), disabled: getSelectedDcmtsOrFocused(selectedItems, focusedItem).length <= 0, onClick: removeDcmtFromFavsOrRecents }) }), _jsx(TMButton, { btnStyle: 'icon', icon: _jsx(IconRefresh, { color: 'white' }), caption: SDKUI_Localizator.Refresh, onClick: onRefreshSearchAsyncDatagrid }), _jsx(TMContextMenu, { items: operationItems, trigger: "left", children: _jsx(IconMenuVertical, { color: 'white', cursor: 'pointer' }) }), showToolbarCloseButton && onClose &&
769
+ _jsx(TMButton, { btnStyle: 'icon', icon: _jsx(IconCloseOutline, { color: 'white' }), caption: SDKUI_Localizator.Close, onClick: onClose })] });
664
770
  const tmSearchResult = useMemo(() => (!searchResults || searchResults.length <= 0)
665
771
  ? _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 }) })] })
666
772
  :
667
- _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 ?
668
- _jsx(TMLayoutItem, { children: _jsx(TMSearchResultSelector, { searchResults: currentSearchResults, disableAccordionIfSingleCategory: disableAccordionIfSingleCategory, selectedTID: selectedSearchResultTID, selectedSearchResult: selectedSearchResult, autoSelectFirst: !isMobile || currentSearchResults.length === 1, onSelectionChanged: onSearchResultSelectionChanged }) })
773
+ _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 ?
774
+ _jsx(TMLayoutItem, { children: _jsx(TMSearchResultSelector, { searchResults: currentSearchResults, disableAccordionIfSingleCategory: disableAccordionIfSingleCategory, selectedTID: selectedSearchResultTID, selectedSearchResult: selectedSearchResult, autoSelectFirst: !isMobile || hasSingleDcmtType, onSelectionChanged: onSearchResultSelectionChanged }) })
669
775
  :
670
- _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: () => {
776
+ _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, showCounterConfig: showCounterConfig, counterConfig: counterConfig, showActionsColumn: showActionsColumn, compactPager: compactPager, onRowActionRequest: requestRowAction }) })] }), 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: () => {
671
777
  updateShowApprovePopup(true);
672
778
  }, onSignApprove: () => {
673
779
  handleSignApprove();
@@ -687,7 +793,6 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
687
793
  showApprovePopup,
688
794
  showRejectPopup,
689
795
  showReAssignPopup,
690
- isOpenBatchUpdate,
691
796
  isOpenDetails,
692
797
  isOpenMaster,
693
798
  isOpenDcmtForm,
@@ -698,6 +803,10 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
698
803
  openS4TViewer,
699
804
  showBackButton,
700
805
  isMobile,
806
+ navStep,
807
+ isSteppedLayout,
808
+ isSelectorCollapsed,
809
+ currentSearchResults,
701
810
  currentMetadataValues,
702
811
  onRefreshSearchAsyncDatagrid,
703
812
  handleNavigateToWGs,
@@ -705,9 +814,14 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
705
814
  editPdfForm,
706
815
  showNoDcmtFoundMessage
707
816
  ]);
708
- 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]);
817
+ /**
818
+ * Il risultato è davvero a video: le pagine restano montate anche in secondo piano, e senza questo
819
+ * bacheca e anteprima scaricherebbero il documento fuori campo.
820
+ */
821
+ const isSearchResultOnScreen = isVisible && !isOpenDcmtForm && !isOpenDetails && !isOpenMaster;
822
+ const tmBlog = useMemo(() => _jsx(TMDcmtBlog, { tid: focusedItem?.TID, did: focusedItem?.DID, isPageVisible: isSearchResultOnScreen, fetchBlogDataTrigger: refreshBlogTrigger, allTasks: allTasks, getAllTasks: getAllTasks, deleteTaskByIdsCallback: deleteTaskByIdsCallback, addTaskCallback: addTaskCallback, editTaskCallback: editTaskCallback, handleNavigateToWGs: handleNavigateToWGs, handleNavigateToDossiers: handleNavigateToDossiers }), [focusedItem, allTasks, refreshBlogTrigger, isSearchResultOnScreen, handleNavigateToWGs, handleNavigateToDossiers, onRefreshBlogDatagrid]);
709
823
  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]);
710
- const tmDcmtPreview = useMemo(() => _jsx(TMDcmtPreviewWrapper, { refreshPreviewTrigger: refreshPreviewTrigger, currentDcmt: currentDcmt, onBack: getOnBackHandler() }), [currentDcmt, refreshPreviewTrigger]);
824
+ const tmDcmtPreview = useMemo(() => _jsx(TMDcmtPreviewWrapper, { refreshPreviewTrigger: refreshPreviewTrigger, currentDcmt: currentDcmt, isPageVisible: isSearchResultOnScreen, onBack: backHandlerSecondary }), [currentDcmt, refreshPreviewTrigger, isSearchResultOnScreen, backHandlerSecondary]);
711
825
  // Auto-fetch indexing info when drawer is open and focusedItem changes
712
826
  useEffect(() => {
713
827
  if (!focusedItem || !showIndexingInfo)
@@ -764,6 +878,22 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
764
878
  }
765
879
  return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', height: '100%', overflow: 'hidden', width: '100%' }, children: [_jsx("div", { style: { padding: '10px', overflow: 'auto', flex: 1 }, children: _jsx("div", { dangerouslySetInnerHTML: { __html: ftExplanation } }) }), _jsxs(StyledIndexingInfoSection, { children: [_jsxs(StyledIndexingToggle, { onClick: handleToggleIndexingInfo, disabled: loadingIndexingInfo, children: [_jsx(StyledLeftContent, { children: _jsx("span", { children: SDKUI_Localizator.IndexingInformation }) }), _jsx(StyledRightContent, { children: _jsx(StyledChevron, { "$isOpen": showIndexingInfo, children: "\u25BC" }) })] }), showIndexingInfo && indexingInfo && (_jsxs(StyledIndexingInfoBox, { children: [_jsx("div", { dangerouslySetInnerHTML: { __html: indexingInfo } }), loadingIndexingInfo && (_jsxs("div", { style: { position: 'absolute', top: '50%', left: '50%', transform: 'translate(-50%, -50%)', background: 'rgba(255, 255, 255, 0.9)', padding: '10px', borderRadius: '4px', boxShadow: '0 2px 8px rgba(0,0,0,0.15)' }, children: [SDKUI_Localizator.Loading, "..."] }))] }))] })] }));
766
880
  }, [selectedSearchResult, focusedItem, indexingInfo, showIndexingInfo, loadingIndexingInfo]);
881
+ /**
882
+ * Su mobile i pannelli si mostrano uno per volta, quindi il layout non va ricordato; con groupId
883
+ * i pannelli sono di un panel manager esterno, che ha un layout suo. Senza la barra laterale
884
+ * l'utente non può aprire/chiudere i pannelli: non ha senso applicargli il layout salvato (li
885
+ * troverebbe aperti senza poterli chiudere) né lasciargli sporcare quello condiviso.
886
+ * Il layout salvato vale solo per il login con Surfer: gli altri moduli hanno layout propri.
887
+ */
888
+ const isLayoutPersisted = SDK_Globals.tmSession?.SessionDescr?.appModuleID === AppModules.SURFER && !isMobile && !groupId?.length && showSearchResultSidebar && PERSISTED_LAYOUT_CONTEXTS.includes(context);
889
+ /**
890
+ * Pannelli che questo risultato non può mostrare (la bacheca è l'unico, vedi PanelDisabledStateHandler).
891
+ * Il layout è condiviso con gli altri risultati aperti: qui non vanno aperti quando il layout lo chiede,
892
+ * e il loro stato salvato non va toccato, altrimenti li si chiuderebbe dove invece sono attivi.
893
+ */
894
+ const disabledLayoutPanelIds = isBoardDisabled ? ['tmBlog'] : [];
895
+ /** Bacheca aperta nel layout salvato: da riaprire quando il tipo documento torna a prevederla */
896
+ const savedBlogVisible = isLayoutPersisted && (getSearchResultPanelLayout()['tmBlog']?.visible ?? false);
767
897
  const allInitialPanelVisibility = {
768
898
  'tmSearchResult': true,
769
899
  'tmBlog': false,
@@ -786,9 +916,11 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
786
916
  component: tmSearchResult,
787
917
  panelContainer: {
788
918
  title: getTitleHeader(),
919
+ enableTitleMarquee: true,
789
920
  showHeader: showToolbarHeader,
790
921
  allowMaximize: !isMobile,
791
- onBack: getOnBackHandler(true),
922
+ onBack: backHandler,
923
+ backCaption: backCaption,
792
924
  onClose: isClosable ? onBack : undefined,
793
925
  toolbar: searchResutlToolbar
794
926
  },
@@ -803,7 +935,7 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
803
935
  panelContainer: {
804
936
  title: SDKUI_Localizator.BlogCase,
805
937
  allowMaximize: !isMobile,
806
- onBack: getOnBackHandler(),
938
+ onBack: backHandlerSecondary,
807
939
  }
808
940
  },
809
941
  toolbarOptions: { icon: _jsx(IconBoard, { fontSize: 24 }), visible: getSearchToolbarVisibility(SDK_Globals.tmSession?.SessionDescr?.appModuleID ?? AppModules.SURFER).tmBlog, orderNumber: 2, isActive: allInitialPanelVisibility['tmBlog'] }
@@ -816,7 +948,7 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
816
948
  panelContainer: {
817
949
  title: SDKUI_Localizator.MetadataSystem,
818
950
  allowMaximize: !isMobile,
819
- onBack: getOnBackHandler(),
951
+ onBack: backHandlerSecondary,
820
952
  }
821
953
  },
822
954
  toolbarOptions: { icon: _jsx(IconDcmtTypeSys, { fontSize: 24 }), visible: getSearchToolbarVisibility(SDK_Globals.tmSession?.SessionDescr?.appModuleID ?? AppModules.SURFER).tmSysMetadata, orderNumber: 3, isActive: allInitialPanelVisibility['tmSysMetadata'] }
@@ -830,7 +962,7 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
830
962
  panelContainer: {
831
963
  title: SDKUI_Localizator.ResultDetails,
832
964
  allowMaximize: !isMobile,
833
- onBack: getOnBackHandler(),
965
+ onBack: backHandlerSecondary,
834
966
  }
835
967
  },
836
968
  toolbarOptions: { icon: _jsx(IconPlatform, { fontSize: 20 }), visible: getSearchToolbarVisibility(SDK_Globals.tmSession?.SessionDescr?.appModuleID ?? AppModules.SURFER).tmFullTextSearch, orderNumber: 4, isActive: allInitialPanelVisibility['tmFullTextSearch'] }
@@ -844,21 +976,49 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
844
976
  },
845
977
  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'] }
846
978
  }
847
- ], [tmSearchResult, tmBlog, tmSysMetadata, tmDcmtPreview, tmFullTextSearch, showToolbarHeader, context, isMobile]);
848
- return (_jsxs(StyledMultiViewPanel, { "$isVisible": isVisible, children: [_jsx(StyledMultiViewPanel, { "$isVisible": !isOpenDcmtForm && !isOpenDetails && !isOpenMaster, style: {
979
+ ], [tmSearchResult, tmBlog, tmSysMetadata, tmDcmtPreview, tmFullTextSearch, showToolbarHeader, context, isMobile, backHandler, backHandlerSecondary, isClosable, onBack, backCaption]);
980
+ /** Contenuto del panel manager: con groupId sta nel provider del contenitore, altrimenti in quello qui sotto */
981
+ const panelManagerContent = (_jsxs(_Fragment, { children: [_jsx(PanelDisabledStateHandler, { isBoardDisabled: isBoardDisabled, savedBlogVisible: savedBlogVisible }), isLayoutPersisted && _jsx(SavedLayoutSyncHandler, { disabledPanelIds: disabledLayoutPanelIds }), _jsx(ShowMainPanelBridge, { showMainPanelRef: showMainPanelRef }), _jsx(TMPanelManagerContainer, { panels: initialPanels, direction: "horizontal", parentId: groupId, showToolbar: showSearchResultSidebar })] }));
982
+ return (_jsxs(StyledMultiViewPanel, { "$isVisible": isVisible, children: [_jsx(StyledMultiViewPanel, { "$isVisible": (!isOpenDcmtForm || isDcmtFormModal) && !isOpenDetails && !isOpenMaster, style: {
849
983
  display: 'flex',
850
984
  flexDirection: isMobile ? 'column' : 'row',
851
985
  justifyContent: 'space-between',
852
986
  gap: Gutters.getGutters(),
853
987
  width: '100%',
854
988
  height: '100%',
855
- }, 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) ?
856
- _jsxs(_Fragment, { children: [_jsx(PanelDisabledStateHandler, { isBoardDisabled: isBoardDisabled }), _jsx(TMPanelManagerContainer, { panels: initialPanels, direction: "horizontal", parentId: groupId, showToolbar: showSearchResultSidebar })] })
857
- :
858
- _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] }));
989
+ }, 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) ? panelManagerContent
990
+ // Senza le prop di persistenza il provider si comporta come quello base:
991
+ // i contesti che non ricordano il layout restano come prima
992
+ : _jsx(TMPanelManagerWithPersistenceProvider, { panels: initialPanels, initialVisibility: allInitialPanelVisibility, defaultDimensions: initialPanelDimensions, initialDimensions: initialPanelDimensions, initialMobilePanelId: 'tmSearchResult', isPersistenceEnabled: isLayoutPersisted && hasSearchResultPanelLayout(), persistPanelStates: isLayoutPersisted ? (state) => saveSearchResultPanelLayout(state, disabledLayoutPanelIds) : undefined, persistedPanelStates: isLayoutPersisted ? getSearchResultPanelLayout() : undefined, children: panelManagerContent }) }) }) }), renderDcmtOperations] }));
859
993
  };
860
994
  export default TMSearchResult;
861
- 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 }) => {
995
+ /** Cerca una voce di operazione per id, anche dentro i sottomenu */
996
+ const findOperationItemById = (items, id) => {
997
+ for (const item of items) {
998
+ if (item.id === id)
999
+ return item;
1000
+ const found = item.submenu ? findOperationItemById(item.submenu, id) : undefined;
1001
+ if (found)
1002
+ return found;
1003
+ }
1004
+ return undefined;
1005
+ };
1006
+ /**
1007
+ * Azioni della colonna di riga. Gli id sono quelli di useDocumentOperations, così il comportamento è
1008
+ * lo stesso del menu contestuale; icone e colori sono allineati alle altre griglie mentre il tooltip
1009
+ * resta il nome dell'operazione. "Cancella" è la cancellazione logica: ripristino ed eliminazione
1010
+ * fisica restano nel menu contestuale, dove la scelta è esplicita.
1011
+ */
1012
+ const rowActions = [
1013
+ { operationId: 'open-form', icon: _jsx(IconEdit, { fontSize: 18, color: TMColors.primary }) },
1014
+ { operationId: 'dup', icon: _jsx(IconDuplicate, { fontSize: 18, color: TMColors.tertiary }) },
1015
+ { operationId: 'del-log', icon: _jsx(IconDelete, { fontSize: 18, color: TMColors.error }), isVisible: (rowData) => rowData?.ISLOGDEL != 1 },
1016
+ ];
1017
+ const rowActionsStyles = {
1018
+ cell: { display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '14px' },
1019
+ icon: { cursor: 'pointer', display: 'flex' },
1020
+ };
1021
+ 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, showCounterConfig = true, counterConfig, showActionsColumn = false, compactPager = false, onRowActionRequest, }) => {
862
1022
  const [dataSource, setDataSource] = useState();
863
1023
  const [columns, setColumns] = useState([]);
864
1024
  const [selectedRowKeys, setSelectedRowKeys] = useState([]);
@@ -879,8 +1039,8 @@ const TMSearchResultGrid = ({ openInOffice, fromDTD, operationItems, allUsers, i
879
1039
  useEffect(() => {
880
1040
  updateDataSourceFromDataGrid(dataSource ?? []);
881
1041
  }, [dataSource]);
882
- const { loadDataListsAsync, renderDataListCell, dataListsCache } = useDataListItem();
883
- const { loadUsersAsync, renderUserIdViewer, usersCache } = useDataUserIdItem();
1042
+ const { loadDataListsAsync, renderDataListCell, dataListsCache, getDataListItem } = useDataListItem();
1043
+ const { loadUsersAsync, renderUserIdViewer, usersCache, getUserItem, getCompleteUserName } = useDataUserIdItem();
884
1044
  useEffect(() => {
885
1045
  // Sincronizza focusedItem con inputFocusedItem dal padre
886
1046
  if (deepCompare(inputFocusedItem, focusedItem))
@@ -1112,6 +1272,57 @@ const TMSearchResultGrid = ({ openInOffice, fromDTD, operationItems, allUsers, i
1112
1272
  });
1113
1273
  return cols;
1114
1274
  }, [searchResult, dataType, cellRender, getDisplayFormat, dataListsCache, usersCache]);
1275
+ /**
1276
+ * Colonne in cui il testo mostrato è diverso dal valore archiviato: date e numeri formattati, descrizioni
1277
+ * di liste dati e utenti. Su queste la ricerca della griglia va fatta sul testo mostrato, altrimenti non
1278
+ * trova nulla (es. cercando 29/01/2023 su una data, che nei dati è una stringa ISO).
1279
+ * Le cache sono ref lette al momento della ricerca: a quel punto sono già popolate.
1280
+ */
1281
+ const searchTextGetters = useMemo(() => {
1282
+ const getters = new Map();
1283
+ const columnsOfResult = searchResult?.dtdResult?.columns;
1284
+ if (!columnsOfResult)
1285
+ return getters;
1286
+ const uniqueKeys = generateUniqueColumnKeys(columnsOfResult, searchResult?.fromTID);
1287
+ columnsOfResult.forEach((col, index) => {
1288
+ const dataField = uniqueKeys[index];
1289
+ if (!dataField)
1290
+ return;
1291
+ const dataDomain = MetadataDataDomains[(col.extendedProperties?.["DataDomain"] ?? "None")];
1292
+ const dataListID = Number(col.extendedProperties?.["DataListID"]);
1293
+ if (dataDomain === MetadataDataDomains.DataList && dataListID) {
1294
+ getters.set(dataField, (rowData) => getDataListItem(dataListID, rowData?.[dataField])?.name ?? String(rowData?.[dataField] ?? ''));
1295
+ return;
1296
+ }
1297
+ if (dataDomain === MetadataDataDomains.UserID) {
1298
+ getters.set(dataField, (rowData) => {
1299
+ const rawValue = rowData?.[dataField];
1300
+ // Un campo utente vuoto non mostra nulla: senza questo controllo Number('') darebbe 0, cioè l'utente di sistema
1301
+ if (rawValue === undefined || rawValue === null || rawValue === '')
1302
+ return '';
1303
+ const userId = Number(rawValue);
1304
+ if (Number.isNaN(userId))
1305
+ return String(rawValue);
1306
+ if (userId === 0)
1307
+ return SDKUI_Localizator.SystemUser ?? '';
1308
+ const ud = getUserItem(userId);
1309
+ return (ud ? getCompleteUserName(ud.domain, ud.name) : undefined) ?? String(rawValue);
1310
+ });
1311
+ return;
1312
+ }
1313
+ // Data e numero vengono mostrati con il formato definito sulle proprietà estese della colonna
1314
+ const { format, formatCulture } = getColumnFormatInfo(col);
1315
+ if (col.dataType === DataColumnTypes.DateTime) {
1316
+ getters.set(dataField, (rowData) => getDateDisplayText(rowData?.[dataField], format, formatCulture));
1317
+ return;
1318
+ }
1319
+ if (col.dataType === DataColumnTypes.Number) {
1320
+ getters.set(dataField, (rowData) => getNumberDisplayText(rowData?.[dataField], format, formatCulture));
1321
+ }
1322
+ });
1323
+ return getters;
1324
+ }, [searchResult, getDataListItem, getUserItem, getCompleteUserName]);
1325
+ const customizeColumns = useMemo(() => customizeSearchByDisplayText(searchTextGetters), [searchTextGetters]);
1115
1326
  /**
1116
1327
  * Effect che carica i dati necessari e genera le colonne della griglia.
1117
1328
  * Esegue il caricamento delle cache (DataList e UserID) in parallelo prima di generare le colonne,
@@ -1203,6 +1414,9 @@ const TMSearchResultGrid = ({ openInOffice, fromDTD, operationItems, allUsers, i
1203
1414
  // Era sull'icona, non fare nulla
1204
1415
  return;
1205
1416
  }
1417
+ // Due click rapidi su un'icona della colonna azioni non devono aprire anche il form
1418
+ if (e.event?.target.closest('.tm-row-actions'))
1419
+ return;
1206
1420
  // Verifica che il documento abbia TID e DID validi prima di procedere
1207
1421
  if (!e.data.TID || !e.data.DID) {
1208
1422
  ShowAlert({
@@ -1216,8 +1430,43 @@ const TMSearchResultGrid = ({ openInOffice, fromDTD, operationItems, allUsers, i
1216
1430
  onFocusedItemChanged?.(e.data);
1217
1431
  onDblClick();
1218
1432
  }, [onDblClick]);
1433
+ /**
1434
+ * Colonna delle azioni di riga, in testa alla griglia (subito dopo la checkbox di selezione, che
1435
+ * DevExtreme tiene sempre per prima). Vive solo qui e non nello stato `columns`, che è quello
1436
+ * sincronizzato col padre per l'esportazione: una colonna senza dataField finirebbe in Excel come
1437
+ * colonna vuota.
1438
+ *
1439
+ * Le operazioni sono lette da una ref perché `operationItems` è ricostruito a ogni render del
1440
+ * padre: usarlo come dipendenza renderebbe nuova la colonna, e con essa `dataColumns`, ad ogni
1441
+ * giro. La cellRender le rilegge a ogni ridisegno della griglia, quindi resta comunque aggiornata.
1442
+ */
1443
+ const operationItemsRef = useRef(operationItems);
1444
+ operationItemsRef.current = operationItems;
1445
+ const actionsColumn = useMemo(() => ({
1446
+ caption: '',
1447
+ width: 110,
1448
+ alignment: 'center',
1449
+ allowSorting: false,
1450
+ allowFiltering: false,
1451
+ allowHeaderFiltering: false,
1452
+ allowResizing: false,
1453
+ allowReordering: false,
1454
+ allowHiding: false,
1455
+ allowExporting: false,
1456
+ showInColumnChooser: false,
1457
+ cssClass: 'tm-row-actions',
1458
+ cellRender: (cellData) => (_jsx("div", { style: rowActionsStyles.cell, children: rowActions.map(action => {
1459
+ const operation = findOperationItemById(operationItemsRef.current, action.operationId);
1460
+ if (!operation || operation.visible === false)
1461
+ return null;
1462
+ if (action.isVisible && !action.isVisible(cellData.data))
1463
+ return null;
1464
+ return (_jsx("span", { style: rowActionsStyles.icon, title: operation.name, onClick: () => onRowActionRequest?.(action.operationId, cellData.data), children: action.icon }, action.operationId));
1465
+ }) })),
1466
+ }), [onRowActionRequest]);
1219
1467
  const dataColumns = useMemo(() => {
1220
1468
  return [
1469
+ ...(showActionsColumn ? [actionsColumn] : []),
1221
1470
  {
1222
1471
  dataType: "object",
1223
1472
  dataField: 'FILEEXT',
@@ -1231,7 +1480,7 @@ const TMSearchResultGrid = ({ openInOffice, fromDTD, operationItems, allUsers, i
1231
1480
  },
1232
1481
  ...columns.filter(col => col.dataField !== 'FILEEXT')
1233
1482
  ];
1234
- }, [columns, onDownloadDcmtsAsync, openInOffice]);
1483
+ }, [columns, onDownloadDcmtsAsync, openInOffice, showActionsColumn, actionsColumn]);
1235
1484
  const onContentReady = useCallback((e) => {
1236
1485
  if (e === undefined)
1237
1486
  return;
@@ -1242,21 +1491,22 @@ const TMSearchResultGrid = ({ openInOffice, fromDTD, operationItems, allUsers, i
1242
1491
  setVisibleItems(visibleData);
1243
1492
  }, []);
1244
1493
  useEffect(() => { onVisibleItemChanged?.(visibleItems); }, [visibleItems]);
1245
- return _jsxs("div", { style: { width: "100%", height: "100%" }, children: [!isDataGridReady && (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', height: '100%', width: '100%', gap: '10px' }, children: [_jsx(LoadIndicator, { height: 60, width: 60 }), _jsx("div", { children: SDKUI_Localizator.Loading })] })), isDataGridReady && _jsx(TMDataGrid, { ref: dataGridRef, id: "tm-search-result", keyExpr: "rowIndex", dataColumns: dataColumns, dataSource: dataSource, repaintChangesOnly: true, selectedRowKeys: selectedRowKeys, focusedRowKey: disableAutoFocus ? undefined : Number(focusedItem?.rowIndex ?? 0), showSearchPanel: showSearchTMDatagrid, showFilterPanel: true, sorting: { mode: "multiple" }, selection: { mode: allowMultipleSelection ? 'multiple' : 'single' }, pageSize: pageSize, onSelectionChanged: handleSelectionChange, onFocusedRowChanged: handleFocusedRowChange, onRowDblClick: onRowDblClick, onContentReady: onContentReady, showHeaderColumnChooser: true, onKeyDown: onKeyDown, customContextMenuItems: operationItems, counterConfig: {
1494
+ const defaultCounterItems = dcmtsReturned !== undefined && dcmtsFound !== undefined
1495
+ ? new Map([
1496
+ [CounterItemKey.allItems, {
1497
+ key: CounterItemKey.allItems,
1246
1498
  show: true,
1247
- items: dcmtsReturned !== undefined && dcmtsFound !== undefined
1248
- ? new Map([
1249
- [CounterItemKey.allItems, {
1250
- key: CounterItemKey.allItems,
1251
- show: true,
1252
- caption: `${dcmtsReturned} restituiti su ${dcmtsFound} trovati`,
1253
- value: `${dcmtsReturned}/${dcmtsFound}`,
1254
- icon: _jsx(IconAll, {}),
1255
- order: -1
1256
- }]
1257
- ])
1258
- : undefined
1259
- } })] });
1499
+ caption: `${dcmtsReturned} restituiti su ${dcmtsFound} trovati`,
1500
+ value: `${dcmtsReturned}/${dcmtsFound}`,
1501
+ icon: _jsx(IconAll, {}),
1502
+ order: -1
1503
+ }]
1504
+ ])
1505
+ : undefined;
1506
+ const gridCounterConfig = showCounterConfig
1507
+ ? { ...counterConfig, show: true, items: counterConfig?.items ?? defaultCounterItems }
1508
+ : { show: false };
1509
+ return _jsxs("div", { style: { width: "100%", height: "100%" }, children: [!isDataGridReady && (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', height: '100%', width: '100%', gap: '10px' }, children: [_jsx(LoadIndicator, { height: 60, width: 60 }), _jsx("div", { children: SDKUI_Localizator.Loading })] })), isDataGridReady && _jsx(TMDataGrid, { ref: dataGridRef, id: "tm-search-result", keyExpr: "rowIndex", dataColumns: dataColumns, dataSource: dataSource, customizeColumns: customizeColumns, repaintChangesOnly: true, selectedRowKeys: selectedRowKeys, focusedRowKey: disableAutoFocus ? undefined : Number(focusedItem?.rowIndex ?? 0), showSearchPanel: showSearchTMDatagrid, showFilterPanel: true, sorting: { mode: "multiple" }, selection: { mode: allowMultipleSelection ? 'multiple' : 'single' }, pageSize: pageSize, compactPager: compactPager, onSelectionChanged: handleSelectionChange, onFocusedRowChanged: handleFocusedRowChange, onRowDblClick: onRowDblClick, onContentReady: onContentReady, showHeaderColumnChooser: true, onKeyDown: onKeyDown, customContextMenuItems: operationItems, counterConfig: gridCounterConfig })] });
1260
1510
  };
1261
1511
  //#region TMSearchResultSelector
1262
1512
  const StyledItemTemplate = styled.div `
@@ -1317,14 +1567,29 @@ const TMSearchResultSelector = ({ searchResults = [], disableAccordionIfSingleCa
1317
1567
  }
1318
1568
  }
1319
1569
  }, [sortedCategories.length, autoSelectFirst]);
1570
+ // La preselezione esterna va applicata una volta per valore: riapplicarla a ogni cambio di
1571
+ // searchResults faceva sì che un refresh annullasse la navigazione dell'utente.
1572
+ const appliedSelectedTIDRef = useRef(undefined);
1320
1573
  useEffect(() => {
1321
- if (selectedTID !== undefined) {
1322
- const found = searchResults.find(r => Number(r.fromTID) === selectedTID);
1323
- handleSelect(found);
1574
+ // Richiesta ritirata: dimentichiamo il valore applicato, così se il chiamante richiede di
1575
+ // nuovo lo stesso TID la preselezione riparte
1576
+ if (selectedTID === undefined) {
1577
+ appliedSelectedTIDRef.current = undefined;
1578
+ return;
1324
1579
  }
1580
+ if (appliedSelectedTIDRef.current === selectedTID)
1581
+ return;
1582
+ const found = searchResults.find(r => Number(r.fromTID) === selectedTID);
1583
+ if (!found)
1584
+ return; // risultati non ancora arrivati: riproviamo al prossimo giro
1585
+ appliedSelectedTIDRef.current = selectedTID;
1586
+ handleSelect(found);
1325
1587
  }, [selectedTID, searchResults]);
1326
- const handleSelect = useCallback((result) => {
1327
- if (selectedResult !== result && result !== undefined) {
1588
+ // force = true per i click dell'utente: la selezione va notificata anche se il tipo documento è già quello selezionato
1589
+ const handleSelect = useCallback((result, force = false) => {
1590
+ if (result === undefined)
1591
+ return;
1592
+ if (selectedResult !== result || force) {
1328
1593
  setSelectedResult(result);
1329
1594
  onSelectionChanged?.(result);
1330
1595
  }
@@ -1369,32 +1634,88 @@ const TMSearchResultSelector = ({ searchResults = [], disableAccordionIfSingleCa
1369
1634
  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 })] })),
1370
1635
  itemHeight: 40
1371
1636
  })), [sortedCategories, groupedResults, searchResults]);
1372
- 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))) }));
1637
+ 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))) }));
1373
1638
  if (disableAccordionIfSingleCategory && sortedCategories.length === 1) {
1374
1639
  const category = sortedCategories[0];
1375
1640
  return renderCategoryItems(category);
1376
1641
  }
1377
- return (_jsx(TMAccordionNew, { groups: accordionGroups, selectedItem: selectedResult, onSelectedItemChange: (result) => handleSelect(result) }));
1642
+ return (_jsx(TMAccordionNew, { groups: accordionGroups, selectedItem: selectedResult, onSelectedItemChange: (result) => handleSelect(result, true) }));
1378
1643
  };
1379
1644
  //#endregion TMSearchResultSelector
1380
- const PanelDisabledStateHandler = ({ isBoardDisabled }) => {
1381
- const { setPanelVisibilityById, setToolbarButtonDisabled } = useTMPanelManagerContext();
1645
+ /** Senza UI: vive dentro il provider e passa a onBack, via ref, l'azione "mostra il pannello lista". */
1646
+ const ShowMainPanelBridge = ({ showMainPanelRef }) => {
1647
+ const { setPanelVisibilityById } = useTMPanelManagerContext();
1382
1648
  useEffect(() => {
1383
- if (isBoardDisabled) {
1384
- setToolbarButtonDisabled('tmBlog', true);
1385
- setPanelVisibilityById('tmBlog', false);
1386
- }
1387
- else {
1388
- setToolbarButtonDisabled('tmBlog', false);
1389
- }
1649
+ // Su mobile mostrare un pannello nasconde gli altri; su desktop la lista è già visibile
1650
+ showMainPanelRef.current = () => setPanelVisibilityById('tmSearchResult', true);
1651
+ return () => { showMainPanelRef.current = null; };
1652
+ }, [setPanelVisibilityById, showMainPanelRef]);
1653
+ return null;
1654
+ };
1655
+ /** Senza UI: allinea i pannelli quando il layout viene salvato da un altro risultato di ricerca già aperto */
1656
+ const SavedLayoutSyncHandler = ({ disabledPanelIds }) => {
1657
+ const { panelVisibility, panelDimensions, maximizedPanels, isResizingActive, setPanelVisibilityById, setPanelDimensionsById } = useTMPanelManagerContext();
1658
+ // Il layout è condiviso fra i risultati aperti: si sottoscrive la firma, che è una stringa, perché
1659
+ // React confronta le letture per riferimento e su un oggetto ri-renderizzerebbe all'infinito
1660
+ const savedLayoutSignature = useSyncExternalStore(subscribeSearchResultPanelLayout, getSearchResultPanelLayoutSignature);
1661
+ useEffect(() => {
1662
+ // Con un pannello massimizzato o un separatore in movimento comanda l'utente di questa istanza
1663
+ if (maximizedPanels.length > 0 || isResizingActive)
1664
+ return;
1665
+ // Al mount panelVisibility è ancora vuota: lo stato iniziale lo applica il provider.
1666
+ // Un pannello disabilitato qui non va aperto, gli altri si allineano comunque
1667
+ const panelsToApply = Object.entries(getSearchResultPanelLayout())
1668
+ .filter(([id, state]) => panelVisibility[id] !== undefined && !(state?.visible && disabledPanelIds.includes(id)));
1669
+ panelsToApply.forEach(([id, state]) => {
1670
+ const isVisible = state?.visible ?? false;
1671
+ if (panelVisibility[id] !== isVisible)
1672
+ setPanelVisibilityById(id, isVisible);
1673
+ });
1674
+ // Le larghezze salvate valgono come pesi, non come percentuali: qui si normalizzano sui soli pannelli
1675
+ // che questo risultato apre davvero, così lo spazio di quelli scartati non resta vuoto
1676
+ const panelsToShow = panelsToApply.filter(([, state]) => state?.visible && parseFloat(state.width) > 0 && parseFloat(state.height) > 0);
1677
+ const weightTotal = panelsToShow.reduce((total, [, state]) => total + parseFloat(state.width), 0);
1678
+ if (weightTotal <= 0)
1679
+ return;
1680
+ // Dopo la visibilità, che accoda un suo ricalcolo delle quote: così restano quelle salvate.
1681
+ // Le quote sono arrotondate, altrimenti due risultati si rincorrono sugli ultimi decimali
1682
+ panelsToShow.forEach(([id, state]) => {
1683
+ const width = `${Math.round(parseFloat(state.width) / weightTotal * 10000) / 100}%`;
1684
+ if (panelDimensions[id]?.width === width && panelDimensions[id]?.height === state.height)
1685
+ return;
1686
+ setPanelDimensionsById(id, width, state.height);
1687
+ });
1688
+ }, [savedLayoutSignature]);
1689
+ return null;
1690
+ };
1691
+ const PanelDisabledStateHandler = ({ isBoardDisabled, savedBlogVisible = false }) => {
1692
+ const { panelVisibility, setPanelVisibilityById, setToolbarButtonDisabled } = useTMPanelManagerContext();
1693
+ const isBlogVisible = panelVisibility['tmBlog'] ?? false;
1694
+ useEffect(() => {
1695
+ setToolbarButtonDisabled('tmBlog', isBoardDisabled);
1390
1696
  }, [isBoardDisabled]);
1697
+ /**
1698
+ * Senza bacheca nel tipo documento il pannello va chiuso ogni volta che ricompare, non solo al cambio
1699
+ * di stato: il layout salvato viene applicato dopo questo effetto e può riportarlo a video.
1700
+ * È un effetto di layout per chiuderlo prima che venga disegnato, altrimenti lampeggia.
1701
+ */
1702
+ useLayoutEffect(() => {
1703
+ if (isBoardDisabled && isBlogVisible)
1704
+ setPanelVisibilityById('tmBlog', false);
1705
+ }, [isBoardDisabled, isBlogVisible]);
1706
+ // Bacheca di nuovo prevista: si riapre come da layout salvato. isBlogVisible non è fra le dipendenze,
1707
+ // altrimenti riaprirebbe il pannello appena chiuso dall'utente
1708
+ useEffect(() => {
1709
+ if (!isBoardDisabled && savedBlogVisible && !isBlogVisible)
1710
+ setPanelVisibilityById('tmBlog', true);
1711
+ }, [isBoardDisabled, savedBlogVisible]);
1391
1712
  return null;
1392
1713
  };
1393
- const TMDcmtPreviewWrapper = ({ refreshPreviewTrigger, currentDcmt, isVisible, onBack }) => {
1714
+ const TMDcmtPreviewWrapper = ({ refreshPreviewTrigger, currentDcmt, isVisible, isPageVisible = true, onBack }) => {
1394
1715
  const { setPanelVisibilityById, toggleMaximize, isResizingActive, countVisibleLeafPanels } = useTMPanelManagerContext();
1395
1716
  const deviceType = useDeviceType();
1396
1717
  const isMobile = deviceType === DeviceType.MOBILE;
1397
- return (_jsx(TMDcmtPreview, { dcmtData: currentDcmt, onClosePanel: (!isMobile && countVisibleLeafPanels() > 1) ? () => setPanelVisibilityById('tmDcmtPreview', false) : undefined, onBack: onBack, allowMaximize: !isMobile && countVisibleLeafPanels() > 1, onMaximizePanel: (!isMobile && countVisibleLeafPanels() > 1) ? () => toggleMaximize("tmDcmtPreview") : undefined, isResizingActive: isResizingActive, isVisible: isVisible }, refreshPreviewTrigger));
1718
+ return (_jsx(TMDcmtPreview, { dcmtData: currentDcmt, onClosePanel: (!isMobile && countVisibleLeafPanels() > 1) ? () => setPanelVisibilityById('tmDcmtPreview', false) : undefined, onBack: onBack, allowMaximize: !isMobile && countVisibleLeafPanels() > 1, onMaximizePanel: (!isMobile && countVisibleLeafPanels() > 1) ? () => toggleMaximize("tmDcmtPreview") : undefined, isResizingActive: isResizingActive, isVisible: isVisible && isPageVisible }, refreshPreviewTrigger));
1398
1719
  };
1399
1720
  // Styled Components
1400
1721
  const StyledPlaceholder = styled.div `