@topconsultnpm/sdkui-react 6.22.0-t1 → 6.22.0-t3

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 (286) hide show
  1. package/lib/components/NewComponents/ContextMenu/TMContextMenu.js +3 -3
  2. package/lib/components/NewComponents/ContextMenu/hooks.d.ts +1 -1
  3. package/lib/components/NewComponents/ContextMenu/styles.d.ts +45 -43
  4. package/lib/components/NewComponents/FloatingMenuBar/TMFloatingMenuBar.js +6 -6
  5. package/lib/components/NewComponents/FloatingMenuBar/styles.d.ts +80 -73
  6. package/lib/components/base/Styled.d.ts +92 -76
  7. package/lib/components/base/Styled.js +7 -5
  8. package/lib/components/base/TMAccordionNew.js +7 -7
  9. package/lib/components/base/TMAlert.d.ts +32 -32
  10. package/lib/components/base/TMAlert.js +6 -8
  11. package/lib/components/base/TMAreaManager.js +53 -55
  12. package/lib/components/base/TMButton.d.ts +13 -0
  13. package/lib/components/base/TMButton.js +139 -7
  14. package/lib/components/base/TMClosableList.js +3 -3
  15. package/lib/components/base/TMCustomButton.js +40 -48
  16. package/lib/components/base/TMDataGrid.d.ts +4 -0
  17. package/lib/components/base/TMDataGrid.js +292 -12
  18. package/lib/components/base/TMDataGridExportForm.js +39 -30
  19. package/lib/components/base/TMDropDownMenu.js +1 -1
  20. package/lib/components/base/TMEditorBase.d.ts +2 -0
  21. package/lib/components/base/TMFileManager.js +5 -3
  22. package/lib/components/base/TMFileManagerDataGridView.js +12 -3
  23. package/lib/components/base/TMFileManagerThumbnailItems.js +20 -10
  24. package/lib/components/base/TMFileManagerUtils.d.ts +6 -6
  25. package/lib/components/base/TMFileManagerUtils.js +37 -1
  26. package/lib/components/base/TMLayout.d.ts +2 -2
  27. package/lib/components/base/TMLayout.js +3 -3
  28. package/lib/components/base/TMList.js +1 -1
  29. package/lib/components/base/TMListView.js +3 -3
  30. package/lib/components/base/TMModal.d.ts +11 -0
  31. package/lib/components/base/TMModal.js +47 -13
  32. package/lib/components/base/TMPanel.d.ts +6 -0
  33. package/lib/components/base/TMPanel.js +141 -28
  34. package/lib/components/base/TMPopUp.js +5 -2
  35. package/lib/components/base/TMResizableMenu.js +2 -2
  36. package/lib/components/base/TMRightSidebar.js +1 -1
  37. package/lib/components/base/TMShowAllOrMaxItemsButton.js +1 -1
  38. package/lib/components/base/TMSpinner.d.ts +2 -1
  39. package/lib/components/base/TMSpinner.js +9 -7
  40. package/lib/components/base/TMTab.js +2 -2
  41. package/lib/components/base/TMTabControl.d.ts +106 -0
  42. package/lib/components/base/TMTabControl.js +664 -0
  43. package/lib/components/base/TMToggleButton.js +2 -2
  44. package/lib/components/base/TMToolbarCard.js +3 -3
  45. package/lib/components/base/TMTooltip.d.ts +2 -1
  46. package/lib/components/base/TMTooltip.js +24 -8
  47. package/lib/components/base/TMTreeView.d.ts +13 -7
  48. package/lib/components/base/TMTreeView.js +143 -70
  49. package/lib/components/base/TMWaitPanel.js +3 -3
  50. package/lib/components/base/waitPanel/TMWaitPanelConfirm.d.ts +12 -0
  51. package/lib/components/base/waitPanel/TMWaitPanelConfirm.js +185 -0
  52. package/lib/components/base/waitPanel/TMWaitPanelManager.d.ts +169 -0
  53. package/lib/components/base/waitPanel/TMWaitPanelManager.js +320 -0
  54. package/lib/components/base/waitPanel/TMWaitPanelView.d.ts +53 -0
  55. package/lib/components/base/waitPanel/TMWaitPanelView.js +274 -0
  56. package/lib/components/base/waitPanel/index.d.ts +1 -0
  57. package/lib/components/base/waitPanel/index.js +1 -0
  58. package/lib/components/choosers/TMCultureIDPicker.js +2 -2
  59. package/lib/components/choosers/TMDataListItemChooser.js +1 -1
  60. package/lib/components/choosers/TMDataListItemPicker.js +2 -2
  61. package/lib/components/choosers/TMDcmtTypeChooser.js +5 -4
  62. package/lib/components/choosers/TMDiskChooser.js +1 -1
  63. package/lib/components/choosers/TMDistinctValues.d.ts +9 -7
  64. package/lib/components/choosers/TMDistinctValues.js +157 -141
  65. package/lib/components/choosers/TMDynDataListItemChooser.js +1 -1
  66. package/lib/components/choosers/TMImageIDChooser.js +1 -1
  67. package/lib/components/choosers/TMMetadataChooser.js +3 -3
  68. package/lib/components/choosers/TMPathChooser.js +17 -34
  69. package/lib/components/choosers/TMQuickSearchInfo.d.ts +13 -0
  70. package/lib/components/choosers/TMQuickSearchInfo.js +279 -0
  71. package/lib/components/choosers/TMQuickSearchSettingsForm.d.ts +16 -0
  72. package/lib/components/choosers/TMQuickSearchSettingsForm.js +94 -0
  73. package/lib/components/choosers/TMRelationChooser.js +1 -1
  74. package/lib/components/choosers/TMSelectedValuesSummary.d.ts +13 -0
  75. package/lib/components/choosers/TMSelectedValuesSummary.js +89 -0
  76. package/lib/components/choosers/TMUserChooser.js +1 -1
  77. package/lib/components/editors/TMCheckBox.js +1 -1
  78. package/lib/components/editors/TMDateBox.d.ts +16 -1
  79. package/lib/components/editors/TMDateBox.js +90 -22
  80. package/lib/components/editors/TMDropDown.d.ts +3 -0
  81. package/lib/components/editors/TMDropDown.js +197 -5
  82. package/lib/components/editors/TMEditorStyled.d.ts +21 -43
  83. package/lib/components/editors/TMFormulaEditor.js +1 -1
  84. package/lib/components/editors/TMHtmlContentDisplay.js +1 -1
  85. package/lib/components/editors/TMLocalizedTextBox.js +1 -1
  86. package/lib/components/editors/TMMetadataEditor.d.ts +5 -1
  87. package/lib/components/editors/TMMetadataEditor.js +27 -23
  88. package/lib/components/editors/TMMetadataValues.js +6 -6
  89. package/lib/components/editors/TMRadioButton.js +2 -2
  90. package/lib/components/editors/TMSummary.js +3 -3
  91. package/lib/components/editors/TMTextArea.d.ts +1 -0
  92. package/lib/components/editors/TMTextArea.js +25 -17
  93. package/lib/components/editors/TMTextBox.d.ts +2 -0
  94. package/lib/components/editors/TMTextBox.js +32 -21
  95. package/lib/components/editors/TMTextExpression.js +1 -1
  96. package/lib/components/features/archive/TMArchive.js +24 -5
  97. package/lib/components/features/assistant/TMHelpOnLineChat.d.ts +29 -0
  98. package/lib/components/features/assistant/TMHelpOnLineChat.js +1384 -0
  99. package/lib/components/features/assistant/TMToppyDraggableHelpCenter.js +4 -4
  100. package/lib/components/features/assistant/TMToppySpeechBubble.js +1 -1
  101. package/lib/components/features/assistant/dataset/Designer-Suggested-Questions.json +102 -0
  102. package/lib/components/features/assistant/dataset/Login-Suggested-Questions.json +32 -0
  103. package/lib/components/features/assistant/dataset/Surfer-Suggested-Questions.json +102 -0
  104. package/lib/components/features/documents/TMBatchUpdateForm.js +22 -9
  105. package/lib/components/features/documents/TMCopyToFolderForm.js +50 -42
  106. package/lib/components/features/documents/TMDcmtBlog.d.ts +3 -0
  107. package/lib/components/features/documents/TMDcmtBlog.js +4 -4
  108. package/lib/components/features/documents/TMDcmtForm.d.ts +7 -0
  109. package/lib/components/features/documents/TMDcmtForm.js +281 -113
  110. package/lib/components/features/documents/TMDcmtFormActionButtons.d.ts +11 -0
  111. package/lib/components/features/documents/TMDcmtFormActionButtons.js +79 -9
  112. package/lib/components/features/documents/TMDcmtIcon.d.ts +1 -3
  113. package/lib/components/features/documents/TMDcmtIcon.js +8 -8
  114. package/lib/components/features/documents/TMDcmtPreview.d.ts +7 -5
  115. package/lib/components/features/documents/TMDcmtPreview.js +16 -10
  116. package/lib/components/features/documents/TMDcmtTasks.js +2 -9
  117. package/lib/components/features/documents/TMDownloadRelationViewerSection.js +1 -1
  118. package/lib/components/features/documents/TMFileUploader.js +7 -7
  119. package/lib/components/features/documents/TMMasterDetailDcmts.d.ts +2 -0
  120. package/lib/components/features/documents/TMMasterDetailDcmts.js +95 -108
  121. package/lib/components/features/documents/TMMasterInfoFields.d.ts +20 -0
  122. package/lib/components/features/documents/TMMasterInfoFields.js +185 -0
  123. package/lib/components/features/documents/TMMergeToPdfForm.d.ts +1 -2
  124. package/lib/components/features/documents/TMMergeToPdfForm.js +83 -56
  125. package/lib/components/features/documents/TMMultiMasterDetailDcmtsForm.d.ts +46 -0
  126. package/lib/components/features/documents/TMMultiMasterDetailDcmtsForm.js +320 -0
  127. package/lib/components/features/documents/TMMultiMasterDetailDcmtsUtils.d.ts +370 -0
  128. package/lib/components/features/documents/TMMultiMasterDetailDcmtsUtils.js +283 -0
  129. package/lib/components/features/documents/TMRelationViewer.d.ts +29 -9
  130. package/lib/components/features/documents/TMRelationViewer.js +310 -208
  131. package/lib/components/features/documents/copyAndMergeDcmtsShared.d.ts +1 -1
  132. package/lib/components/features/documents/relationTreeState.d.ts +32 -0
  133. package/lib/components/features/documents/relationTreeState.js +89 -0
  134. package/lib/components/features/search/TMMetadataOutputForm.js +1 -1
  135. package/lib/components/features/search/TMMetadataSorterForm.js +1 -1
  136. package/lib/components/features/search/TMSavedQuerySelector.js +2 -2
  137. package/lib/components/features/search/TMSearch.d.ts +1 -1
  138. package/lib/components/features/search/TMSearch.js +172 -34
  139. package/lib/components/features/search/TMSearchQueryEditor.js +7 -5
  140. package/lib/components/features/search/TMSearchQueryPanel.d.ts +14 -4
  141. package/lib/components/features/search/TMSearchQueryPanel.js +153 -62
  142. package/lib/components/features/search/TMSearchResult.d.ts +21 -0
  143. package/lib/components/features/search/TMSearchResult.js +461 -152
  144. package/lib/components/features/search/TMSignatureInfoContent.js +1 -1
  145. package/lib/components/features/search/TMTreeSelector.js +130 -83
  146. package/lib/components/features/search/TMViewHistoryDcmt.js +14 -25
  147. package/lib/components/features/tasks/TMTaskForm.js +1 -1
  148. package/lib/components/features/tasks/TMTaskFormUtils.js +3 -3
  149. package/lib/components/features/tasks/TMTasksAgenda.js +1 -1
  150. package/lib/components/features/tasks/TMTasksCalendar.js +1 -1
  151. package/lib/components/features/tasks/TMTasksHeader.js +1 -1
  152. package/lib/components/features/tasks/TMTasksUtilsView.js +1 -1
  153. package/lib/components/features/tasks/TMTasksView.js +97 -102
  154. package/lib/components/features/wg/TMWGsCopyMoveForm.js +92 -92
  155. package/lib/components/features/workflow/TMWorkflowPopup.d.ts +10 -0
  156. package/lib/components/features/workflow/TMWorkflowPopup.js +9 -5
  157. package/lib/components/features/workflow/diagram/ConnectionComponent.js +3 -3
  158. package/lib/components/features/workflow/diagram/DiagramItemComponent.js +3 -3
  159. package/lib/components/features/workflow/diagram/DiagramItemForm.js +1 -1
  160. package/lib/components/features/workflow/diagram/DiagramItemSvgContent.js +1 -1
  161. package/lib/components/features/workflow/diagram/RecipientList.js +1 -1
  162. package/lib/components/features/workflow/diagram/WFDiagram.js +4 -4
  163. package/lib/components/features/workflow/diagram/WorkitemRecipientsEditor.d.ts +3 -1
  164. package/lib/components/forms/Login/ChangePasswordInputs.d.ts +3 -1
  165. package/lib/components/forms/Login/Menu.js +1 -1
  166. package/lib/components/forms/Login/OTPReader.js +1 -1
  167. package/lib/components/forms/Login/PasswordStrengthChecker.js +1 -1
  168. package/lib/components/forms/Login/RapidAccessLogin.js +2 -2
  169. package/lib/components/forms/Login/RecoverPasswordFlow.js +2 -2
  170. package/lib/components/forms/Login/SelectBox.js +1 -1
  171. package/lib/components/forms/Login/StepIndicator.js +1 -1
  172. package/lib/components/forms/Login/TMLoginForm.js +7 -7
  173. package/lib/components/forms/Login/TextBox.js +1 -1
  174. package/lib/components/forms/TMApplyForm.js +3 -3
  175. package/lib/components/forms/TMChooserForm.js +1 -1
  176. package/lib/components/forms/TMResultDialog.js +1 -1
  177. package/lib/components/forms/TMSaveForm.js +3 -3
  178. package/lib/components/grids/TMBlogAttachments.js +14 -3
  179. package/lib/components/grids/TMBlogHeader.js +5 -1
  180. package/lib/components/grids/TMBlogsPost.d.ts +2 -0
  181. package/lib/components/grids/TMBlogsPost.js +122 -99
  182. package/lib/components/grids/TMBlogsPostUtils.d.ts +1 -0
  183. package/lib/components/grids/TMBlogsPostUtils.js +15 -4
  184. package/lib/components/grids/TMRecentsManager.js +4 -4
  185. package/lib/components/grids/TMValidationItemsList.js +1 -1
  186. package/lib/components/index.d.ts +6 -0
  187. package/lib/components/index.js +5 -0
  188. package/lib/components/layout/panelManager/TMPanelManagerContainer.d.ts +1 -0
  189. package/lib/components/layout/panelManager/TMPanelManagerContainer.js +25 -15
  190. package/lib/components/layout/panelManager/TMPanelManagerContext.js +68 -93
  191. package/lib/components/layout/panelManager/TMPanelManagerToolbar.d.ts +4 -5
  192. package/lib/components/layout/panelManager/TMPanelManagerToolbar.js +1 -1
  193. package/lib/components/layout/panelManager/TMPanelWrapper.js +3 -3
  194. package/lib/components/layout/panelManager/types.d.ts +3 -0
  195. package/lib/components/layout/panelManager/utils.js +13 -11
  196. package/lib/components/pages/TMPage.js +30 -32
  197. package/lib/components/query/TMQueryCountButton.js +1 -1
  198. package/lib/components/query/TMQueryEditor.d.ts +17 -10
  199. package/lib/components/query/TMQueryEditor.js +9 -8
  200. package/lib/components/sidebar/TMCommandsPanel.d.ts +4 -4
  201. package/lib/components/sidebar/TMCommandsPanel.js +1 -1
  202. package/lib/components/sidebar/TMHeader.d.ts +1 -0
  203. package/lib/components/sidebar/TMHeader.js +37 -27
  204. package/lib/components/sidebar/TMSidebar.js +1 -1
  205. package/lib/components/sidebar/TMSidebarItem.js +1 -1
  206. package/lib/components/viewers/TMMidViewer.js +6 -3
  207. package/lib/components/viewers/TMTidViewer.js +89 -28
  208. package/lib/components/wizard/TMStepIndicator.js +3 -3
  209. package/lib/css/tm-sdkui.css +1 -1
  210. package/lib/devextreme-license.d.ts +1 -0
  211. package/lib/devextreme-license.js +3 -0
  212. package/lib/helper/Enum_Localizator.d.ts +4 -4
  213. package/lib/helper/Enum_Localizator.js +1 -0
  214. package/lib/helper/Globalization.d.ts +1 -1
  215. package/lib/helper/SDKUI_Globals.d.ts +110 -0
  216. package/lib/helper/SDKUI_Globals.js +151 -0
  217. package/lib/helper/SDKUI_Localizator.d.ts +756 -677
  218. package/lib/helper/SDKUI_Localizator.js +790 -0
  219. package/lib/helper/TMAppUpdatedModal.d.ts +2 -0
  220. package/lib/helper/TMAppUpdatedModal.js +134 -0
  221. package/lib/helper/TMIcons.d.ts +1 -0
  222. package/lib/helper/TMIcons.js +4 -3
  223. package/lib/helper/TMPdfViewer.js +1 -1
  224. package/lib/helper/TMToppyMessage.js +1 -1
  225. package/lib/helper/TMUtils.d.ts +51 -4
  226. package/lib/helper/TMUtils.js +273 -12
  227. package/lib/helper/ZipManager.d.ts +1 -1
  228. package/lib/helper/ZipManager.js +2 -1
  229. package/lib/helper/checkinCheckoutManager.d.ts +1 -1
  230. package/lib/helper/checkinCheckoutManager.js +1 -1
  231. package/lib/helper/dataGridSearchHelper.d.ts +28 -0
  232. package/lib/helper/dataGridSearchHelper.js +51 -0
  233. package/lib/helper/dcmtsHelper.d.ts +22 -0
  234. package/lib/helper/dcmtsHelper.js +60 -1
  235. package/lib/helper/helpers.d.ts +24 -2
  236. package/lib/helper/helpers.js +59 -3
  237. package/lib/helper/index.d.ts +2 -0
  238. package/lib/helper/index.js +2 -0
  239. package/lib/helper/queryHelper.d.ts +6 -1
  240. package/lib/helper/queryHelper.js +8 -3
  241. package/lib/hooks/tmDistinctValuesGridHelper.d.ts +16 -0
  242. package/lib/hooks/tmDistinctValuesGridHelper.js +31 -0
  243. package/lib/hooks/useArchiveListForm.d.ts +39 -0
  244. package/lib/hooks/useArchiveListForm.js +46 -0
  245. package/lib/hooks/useCaseFlowApprove.d.ts +56 -0
  246. package/lib/hooks/useCaseFlowApprove.js +157 -0
  247. package/lib/hooks/useCheckInOutOperations.d.ts +0 -6
  248. package/lib/hooks/useCheckInOutOperations.js +14 -34
  249. package/lib/hooks/useDataUserIdItem.d.ts +1 -0
  250. package/lib/hooks/useDataUserIdItem.js +1 -0
  251. package/lib/hooks/useDcmtOperations.d.ts +3 -12
  252. package/lib/hooks/useDcmtOperations.js +187 -179
  253. package/lib/hooks/useDocumentOperations.d.ts +5 -0
  254. package/lib/hooks/useDocumentOperations.js +64 -33
  255. package/lib/hooks/useInputDialog.js +1 -1
  256. package/lib/hooks/useMultiMasterDetailDcmts.d.ts +98 -0
  257. package/lib/hooks/useMultiMasterDetailDcmts.js +758 -0
  258. package/lib/hooks/usePreventFileDrop.js +9 -0
  259. package/lib/hooks/useQueryParametersDialog.js +1 -1
  260. package/lib/hooks/useRelatedDocuments.d.ts +1 -0
  261. package/lib/hooks/useRelatedDocuments.js +22 -27
  262. package/lib/hooks/useShowDossiersV5.d.ts +12 -0
  263. package/lib/hooks/useShowDossiersV5.js +37 -0
  264. package/lib/hooks/useTMDistinctValuesMetadataDisplay.d.ts +18 -0
  265. package/lib/hooks/useTMDistinctValuesMetadataDisplay.js +103 -0
  266. package/lib/hooks/useTMDistinctValuesQuickSearch.d.ts +49 -0
  267. package/lib/hooks/useTMDistinctValuesQuickSearch.js +307 -0
  268. package/lib/hooks/useTMDistinctValuesSelection.d.ts +42 -0
  269. package/lib/hooks/useTMDistinctValuesSelection.js +103 -0
  270. package/lib/hooks/useTMDistinctValuesSource.d.ts +29 -0
  271. package/lib/hooks/useTMDistinctValuesSource.js +105 -0
  272. package/lib/hooks/useWorkflowApprove.js +33 -17
  273. package/lib/index.d.ts +2 -0
  274. package/lib/index.js +5 -2
  275. package/lib/services/caseflow/CaseFlowService.d.ts +277 -0
  276. package/lib/services/caseflow/CaseFlowService.js +761 -0
  277. package/lib/services/caseflow/CaseFlowTemplateCacheService.d.ts +28 -0
  278. package/lib/services/caseflow/CaseFlowTemplateCacheService.js +121 -0
  279. package/lib/services/caseflow/caseFlowNameCache.d.ts +32 -0
  280. package/lib/services/caseflow/caseFlowNameCache.js +116 -0
  281. package/lib/services/caseflow/caseFlowWorkItemUtils.d.ts +61 -0
  282. package/lib/services/caseflow/caseFlowWorkItemUtils.js +113 -0
  283. package/lib/services/index.d.ts +4 -0
  284. package/lib/services/index.js +4 -0
  285. package/lib/services/platform_services.d.ts +4 -4
  286. package/package.json +16 -13
@@ -0,0 +1,758 @@
1
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
+ import { AccessLevels, ArchiveConstraints, ArchiveEngineByID, DcmtOpers, GeneralRetrieveFormats, MetadataDataDomains, MetadataDataTypes, RelationCacheService, RelationTypes, ResultTypes, RetrieveFileOptions, SDK_Globals, SystemMIDsAsNumber } from '@topconsultnpm/sdk-ts';
3
+ import { MetadataValueDescriptorEx } from '../ts';
4
+ import { clearMultiDetailLayout, Globalization, getExceptionMessage, getMultiDetailSectionsOpen, handleArchiveVisibility, hasMultiDetailLayout, saveMultiDetailSectionsOpen, SDKUI_Localizator } from '../helper';
5
+ import { canViewMetadata, getDcmtMetadataByMid, getUserMetadataSorted, mapAssociationsFromMetadata } from '../helper/dcmtsHelper';
6
+ import { ButtonNames, TMExceptionBoxManager, TMMessageBoxManager } from '../components/base/TMPopUp';
7
+ import TMSpinner from '../components/base/TMSpinner';
8
+ import { TMWaitPanelManager } from '../components/base/waitPanel';
9
+ import ShowAlert from '../components/base/TMAlert';
10
+ import { TMResultManager } from '../components/forms/TMResultDialog';
11
+ import { useDataListItem } from './useDataListItem';
12
+ import { buildColumnsFromDtd, canArchive, canUpdateMasterCallback, getDetailDtdAsync, toArchiveRowValue, toGridNumber } from '../components/features/documents/TMMultiMasterDetailDcmtsUtils';
13
+ /** Fasi del flusso di archiviazione multipla dei dettagli */
14
+ export var MultiDetailArchivePhase;
15
+ (function (MultiDetailArchivePhase) {
16
+ /** Scelta del tipo documento */
17
+ MultiDetailArchivePhase[MultiDetailArchivePhase["SelectDcmtType"] = 0] = "SelectDcmtType";
18
+ /** Riepilogo delle archiviazioni */
19
+ MultiDetailArchivePhase[MultiDetailArchivePhase["ArchiveSummary"] = 1] = "ArchiveSummary";
20
+ })(MultiDetailArchivePhase || (MultiDetailArchivePhase = {}));
21
+ /**
22
+ * Logica dell'archiviazione multipla dei dettagli: relazioni, sezioni, righe del riepilogo e
23
+ * archiviazione. Il rendering sta in TMMultiMasterDetailDcmtsForm
24
+ */
25
+ export const useMultiMasterDetailDcmts = ({ dtd, masterDcmt, onClose, canNavigateMaster, onNavigateMaster }) => {
26
+ const { renderDataListCell, loadDataListsAsync } = useDataListItem();
27
+ const [isLoading, setIsLoading] = useState(true);
28
+ const [phase, setPhase] = useState(MultiDetailArchivePhase.SelectDcmtType);
29
+ const [detailRelationItems, setDetailRelationItems] = useState([]);
30
+ const [selectedRelation, setSelectedRelation] = useState(undefined);
31
+ // Il mount della griglia è differito di un frame, così stepper e titolo si dipingono subito
32
+ const [isSummaryReady, setIsSummaryReady] = useState(false);
33
+ // Struttura del tipo documento selezionato
34
+ const [detailColumns, setDetailColumns] = useState([]);
35
+ const [detailSupportsFile, setDetailSupportsFile] = useState(false);
36
+ // Mid numerici del tipo di dettaglio: in griglia le loro celle portano il numero (vedi buildArchiveRow)
37
+ const numericMidsRef = useRef(new Set());
38
+ // Dettagli già archiviati del tipo selezionato, collegati al master
39
+ const [archivedDetailResults, setArchivedDetailResults] = useState([]);
40
+ const [isLoadingArchivedDetails, setIsLoadingArchivedDetails] = useState(false);
41
+ const areArchivedDetailsLoadedRef = useRef(false);
42
+ // Sezioni aperte/collassate, come le ha lasciate l'utente
43
+ const [sectionsOpen, setSectionsOpen] = useState(getMultiDetailSectionsOpen());
44
+ // Cambia a ogni cancellazione del layout, per rimontare i pannelli sulle dimensioni di default
45
+ const [layoutResetToken, setLayoutResetToken] = useState(0);
46
+ // Layout dell'utente nel local storage: senza non c'è niente da cancellare (è uno stato perché le altezze le salva il panel manager)
47
+ const [hasSavedLayout, setHasSavedLayout] = useState(hasMultiDetailLayout());
48
+ // Master
49
+ const [isOpenMasterForm, setIsOpenMasterForm] = useState(false);
50
+ const [masterInfoReloadToken, setMasterInfoReloadToken] = useState(0);
51
+ // Righe del riepilogo
52
+ const [archiveRows, setArchiveRows] = useState([]);
53
+ // La modifica in cella aggiorna la riga fuori dallo stato (vedi updateRowValues)
54
+ const archiveRowsRef = useRef(archiveRows);
55
+ archiveRowsRef.current = archiveRows;
56
+ const rowIdRef = useRef(0);
57
+ // Copia di un dettaglio archiviato in corso: i clic successivi sull'icona vanno ignorati (vedi copyArchivedDetailToRows)
58
+ const isCopyingArchivedDetailRef = useRef(false);
59
+ const [focusedRowKey, setFocusedRowKey] = useState(undefined);
60
+ const [selectedRowKeys, setSelectedRowKeys] = useState([]);
61
+ // Form di inserimento/modifica di una riga
62
+ const [isOpenAddForm, setIsOpenAddForm] = useState(false);
63
+ const [editingRowId, setEditingRowId] = useState(undefined); // Undefined = inserimento
64
+ const [associationInputMids, setAssociationInputMids] = useState([]); // Associazioni pre-compilate dal master
65
+ // Archiviazione finale
66
+ const [stopOnFirstError, setStopOnFirstError] = useState(true);
67
+ const [isArchiving, setIsArchiving] = useState(false);
68
+ const selectedItem = detailRelationItems.find(item => item.relation === selectedRelation);
69
+ const hasMultipleTypes = detailRelationItems.length > 1;
70
+ const canUpdateMaster = canUpdateMasterCallback(dtd, masterDcmt);
71
+ // Stessi metadati delle card del master (vedi TMMasterInfoFields): quelli non visibili non contano
72
+ const masterFieldsCount = useMemo(() => getUserMetadataSorted(dtd).filter(canViewMetadata).length, [dtd]);
73
+ // Nel riepilogo si mostrano solo le righe del tipo selezionato
74
+ const currentRows = useMemo(() => archiveRows.filter(r => r.__tid === selectedRelation?.detailTID), [archiveRows, selectedRelation?.detailTID]);
75
+ const archivedDetailsCount = archivedDetailResults.reduce((total, r) => total + (r.dcmtsFound ?? r.jsonResult?.length ?? 0), 0);
76
+ // =============================================================================================
77
+ // MASTER
78
+ // =============================================================================================
79
+ /** Identifica il master: cambia quando la navigazione sposta il modale su un altro documento */
80
+ const masterKey = `${masterDcmt?.TID}_${masterDcmt?.DID}`;
81
+ // Metadati del master indicizzati per mid: si leggono una sola volta per documento e la cache
82
+ // si invalida se cambia il master (TID/DID) o dopo un suo salvataggio
83
+ const masterMetadataCacheRef = useRef(undefined);
84
+ const getMasterMetadataAsync = () => {
85
+ if (masterMetadataCacheRef.current?.key !== masterKey) {
86
+ masterMetadataCacheRef.current = { key: masterKey, promise: getDcmtMetadataByMid(masterDcmt) };
87
+ }
88
+ return masterMetadataCacheRef.current.promise;
89
+ };
90
+ const handleMasterSavedAsync = async () => {
91
+ masterMetadataCacheRef.current = undefined;
92
+ setMasterInfoReloadToken(token => token + 1);
93
+ setIsOpenMasterForm(false);
94
+ };
95
+ // La navigazione ha spostato il modale su un altro master: le righe del riepilogo erano del
96
+ // documento precedente e sono già state scartate con conferma (vedi navigateMaster).
97
+ // Il tipo di dettaglio scelto resta: nella lista di provenienza i documenti sono dello stesso tipo
98
+ const previousMasterKeyRef = useRef(masterKey);
99
+ useEffect(() => {
100
+ if (previousMasterKeyRef.current === masterKey)
101
+ return;
102
+ previousMasterKeyRef.current = masterKey;
103
+ setArchiveRows([]);
104
+ setFocusedRowKey(undefined);
105
+ setSelectedRowKeys([]);
106
+ setIsOpenAddForm(false);
107
+ setEditingRowId(undefined);
108
+ setIsOpenMasterForm(false);
109
+ }, [masterKey]);
110
+ // Associazioni della relazione valorizzate dal master, per pre-compilare il dettaglio
111
+ const getMasterAssociationsForDetailAsync = async (relation) => {
112
+ if (!relation)
113
+ return [];
114
+ return mapAssociationsFromMetadata(relation, 'detail', await getMasterMetadataAsync());
115
+ };
116
+ // =============================================================================================
117
+ // RELAZIONI E STRUTTURA DEL DETTAGLIO
118
+ // =============================================================================================
119
+ // Relazioni di dettaglio archiviabili; con una sola relazione si salta la scelta del tipo.
120
+ // Dipende dal tipo del master: la navigazione resta nella stessa lista, quindi di norma non cambia
121
+ useEffect(() => {
122
+ const loadAsync = async () => {
123
+ try {
124
+ setIsLoading(true);
125
+ TMSpinner.show({ description: SDKUI_Localizator.Loading });
126
+ const relations = await RelationCacheService.GetAllAsync(SDK_Globals.tmSession);
127
+ if (!relations)
128
+ throw new Error('Impossibile caricare le relazioni.');
129
+ // Relazioni in cui il tipo corrente è master: i target sono i dettagli archiviabili
130
+ const detailRelations = relations.filter(r => r.masterTID === dtd?.id
131
+ && r.relationType !== RelationTypes.ManyToMany
132
+ && r.associations
133
+ && r.associations.length > 0);
134
+ const items = await Promise.all(detailRelations.map(async (relation) => ({
135
+ relation,
136
+ detailDtd: await getDetailDtdAsync(relation.detailTID),
137
+ })));
138
+ setDetailRelationItems(items);
139
+ if (items.length === 1) {
140
+ setSelectedRelation(items[0].relation);
141
+ setPhase(MultiDetailArchivePhase.ArchiveSummary);
142
+ }
143
+ else {
144
+ setPhase(MultiDetailArchivePhase.SelectDcmtType);
145
+ }
146
+ }
147
+ catch (error) {
148
+ TMExceptionBoxManager.show({ exception: error });
149
+ }
150
+ finally {
151
+ TMSpinner.hide();
152
+ setIsLoading(false);
153
+ }
154
+ };
155
+ loadAsync();
156
+ }, [dtd?.id]);
157
+ // Colonne della griglia di riepilogo e supporto al file del tipo selezionato
158
+ useEffect(() => {
159
+ const loadDetailStructureAsync = async () => {
160
+ if (!selectedRelation?.detailTID) {
161
+ setDetailColumns([]);
162
+ setDetailSupportsFile(false);
163
+ numericMidsRef.current = new Set();
164
+ return;
165
+ }
166
+ try {
167
+ const detailDtd = await getDetailDtdAsync(selectedRelation.detailTID);
168
+ setDetailSupportsFile(detailDtd?.archiveConstraint !== ArchiveConstraints.OnlyMetadata);
169
+ // Liste dati referenziate dai metadati, servono al render delle celle
170
+ const dataListIDs = new Set();
171
+ const numericMids = new Set();
172
+ (detailDtd?.metadata ?? []).forEach(md => {
173
+ if (md.dataDomain === MetadataDataDomains.DataList && md.dataListID)
174
+ dataListIDs.add(md.dataListID);
175
+ if (md.dataType === MetadataDataTypes.Number && md.id !== undefined)
176
+ numericMids.add(md.id);
177
+ });
178
+ numericMidsRef.current = numericMids;
179
+ await loadDataListsAsync(dataListIDs);
180
+ setDetailColumns(buildColumnsFromDtd(detailDtd, renderDataListCell));
181
+ }
182
+ catch {
183
+ setDetailColumns([]);
184
+ setDetailSupportsFile(false);
185
+ numericMidsRef.current = new Set();
186
+ }
187
+ };
188
+ loadDetailStructureAsync();
189
+ }, [selectedRelation?.detailTID]);
190
+ useEffect(() => {
191
+ if (phase !== MultiDetailArchivePhase.ArchiveSummary) {
192
+ setIsSummaryReady(false);
193
+ return;
194
+ }
195
+ setIsSummaryReady(false);
196
+ const rafId = requestAnimationFrame(() => setIsSummaryReady(true));
197
+ return () => cancelAnimationFrame(rafId);
198
+ }, [phase, selectedRelation?.detailTID]);
199
+ // =============================================================================================
200
+ // DETTAGLI GIÀ ARCHIVIATI
201
+ // =============================================================================================
202
+ const refreshArchivedDetailsAsync = async () => {
203
+ const detailTID = selectedRelation?.detailTID;
204
+ if (detailTID === undefined || !masterDcmt?.TID || !masterDcmt?.DID)
205
+ return;
206
+ try {
207
+ setIsLoadingArchivedDetails(true);
208
+ const searchEngine = SDK_Globals.tmSession?.NewSearchEngine();
209
+ const results = await searchEngine?.GetAllDetailDcmtsAsync(masterDcmt.TID, masterDcmt.DID) ?? [];
210
+ let filtered = results.filter(r => selectedRelation?.id !== undefined && r.relationID === selectedRelation.id);
211
+ if (filtered.length === 0)
212
+ filtered = results.filter(r => r.fromTID === detailTID);
213
+ setArchivedDetailResults(filtered);
214
+ areArchivedDetailsLoadedRef.current = true;
215
+ }
216
+ catch (error) {
217
+ TMExceptionBoxManager.show({ exception: error });
218
+ }
219
+ finally {
220
+ setIsLoadingArchivedDetails(false);
221
+ }
222
+ };
223
+ // I risultati seguono il tipo documento selezionato e il master (che cambia con la navigazione), e si
224
+ // caricano anche a sezione chiusa: il contatore nella testata e nella barra dei pannelli deve essere
225
+ // corretto senza dover aprire la sezione
226
+ useEffect(() => {
227
+ setArchivedDetailResults([]);
228
+ areArchivedDetailsLoadedRef.current = false;
229
+ if (selectedRelation?.detailTID !== undefined)
230
+ refreshArchivedDetailsAsync();
231
+ }, [selectedRelation?.detailTID, masterKey]);
232
+ // =============================================================================================
233
+ // SEZIONI
234
+ // =============================================================================================
235
+ const setSectionOpen = (section, isOpen) => {
236
+ const next = { ...sectionsOpen, [section]: isOpen };
237
+ setSectionsOpen(next);
238
+ saveMultiDetailSectionsOpen(next);
239
+ // Riaperte tutte le sezioni il collasso torna ai default: senza altezze salvate non c'è più layout
240
+ setHasSavedLayout(hasMultiDetailLayout());
241
+ // I dettagli già archiviati sono caricati insieme al tipo di dettaglio: qui si riprova solo se quel caricamento è fallito
242
+ if (section === 'archivedDetails' && isOpen && !areArchivedDetailsLoadedRef.current && !isLoadingArchivedDetails)
243
+ refreshArchivedDetailsAsync();
244
+ };
245
+ /**
246
+ * Apre tutte le sezioni senza salvare: serve dove il collasso non è una scelta dell'utente ma una
247
+ * conseguenza del layout (su mobile si vede un pannello alla volta, e una sezione chiusa lascerebbe
248
+ * a schermo la sola testata). Non si persiste per non sovrascrivere le sezioni che l'utente ha
249
+ * chiuso nella vista a pannelli impilati.
250
+ */
251
+ const openAllSections = () => {
252
+ setSectionsOpen(prev => Object.values(prev).every(Boolean) ? prev : { masterInfo: true, archivedDetails: true, archiveList: true });
253
+ // I dettagli già archiviati sono caricati insieme al tipo di dettaglio: qui si riprova solo se quel caricamento è fallito
254
+ if (!areArchivedDetailsLoadedRef.current && !isLoadingArchivedDetails)
255
+ refreshArchivedDetailsAsync();
256
+ };
257
+ /**
258
+ * Cancella il layout configurato dall'utente (altezze dei pannelli e sezioni collassate), previa
259
+ * conferma. Il token cambia per rimontare il panel manager, che legge le dimensioni solo all'avvio.
260
+ */
261
+ const clearLayout = () => {
262
+ TMMessageBoxManager.show({
263
+ title: SDKUI_Localizator.RemoveLayout,
264
+ message: SDKUI_Localizator.RemoveLayout_Confirm,
265
+ buttons: [ButtonNames.YES, ButtonNames.NO],
266
+ onButtonClick: (button) => {
267
+ if (button !== ButtonNames.YES)
268
+ return;
269
+ clearMultiDetailLayout();
270
+ const restored = getMultiDetailSectionsOpen();
271
+ setSectionsOpen(restored);
272
+ setLayoutResetToken(prev => prev + 1);
273
+ setHasSavedLayout(false);
274
+ // I dettagli già archiviati sono caricati insieme al tipo di dettaglio: qui si riprova solo se quel caricamento è fallito
275
+ if (!areArchivedDetailsLoadedRef.current && !isLoadingArchivedDetails)
276
+ refreshArchivedDetailsAsync();
277
+ ShowAlert({ message: SDKUI_Localizator.LayoutRemoved, mode: 'success', title: SDKUI_Localizator.RemoveLayout, duration: 3000 });
278
+ },
279
+ });
280
+ };
281
+ // =============================================================================================
282
+ // NAVIGAZIONE
283
+ // =============================================================================================
284
+ const selectRelation = (item) => {
285
+ setSelectedRelation(item.relation);
286
+ setPhase(MultiDetailArchivePhase.ArchiveSummary);
287
+ };
288
+ const backToTypeSelection = () => {
289
+ setSelectedRelation(undefined);
290
+ setPhase(MultiDetailArchivePhase.SelectDcmtType);
291
+ };
292
+ // Con righe inserite per il tipo corrente il ritorno alla scelta del tipo è bloccato
293
+ const isBackLocked = currentRows.length > 0;
294
+ const closeModal = () => {
295
+ if (archiveRows.length === 0) {
296
+ onClose();
297
+ return;
298
+ }
299
+ TMMessageBoxManager.show({
300
+ title: SDKUI_Localizator.Archive,
301
+ message: SDKUI_Localizator.ExitWithoutArchiving_Confirm,
302
+ buttons: [ButtonNames.YES, ButtonNames.NO],
303
+ onButtonClick: (button) => { if (button === ButtonNames.YES)
304
+ onClose(); },
305
+ });
306
+ };
307
+ // Navigazione del master sulla lista di provenienza: senza la sua callback le frecce non si mostrano
308
+ const canNavigateMasterPrev = !!onNavigateMaster && (canNavigateMaster?.('prev') ?? false);
309
+ const canNavigateMasterNext = !!onNavigateMaster && (canNavigateMaster?.('next') ?? false);
310
+ const isMasterNavigationEnabled = !!onNavigateMaster;
311
+ /**
312
+ * Sposta il master sul documento precedente/successivo della lista di provenienza. Le righe già
313
+ * inserite appartengono al master corrente: si perdono, quindi prima si chiede conferma
314
+ */
315
+ const navigateMaster = (dir) => {
316
+ if (!onNavigateMaster)
317
+ return;
318
+ if (dir === 'prev' ? !canNavigateMasterPrev : !canNavigateMasterNext)
319
+ return;
320
+ if (archiveRows.length === 0) {
321
+ onNavigateMaster(dir);
322
+ return;
323
+ }
324
+ TMMessageBoxManager.show({
325
+ title: SDKUI_Localizator.Archive,
326
+ message: SDKUI_Localizator.ChangeMasterWithoutArchiving_Confirm,
327
+ buttons: [ButtonNames.YES, ButtonNames.NO],
328
+ // Le righe le azzera l'effetto sul cambio di master, non serve svuotarle qui
329
+ onButtonClick: (button) => { if (button === ButtonNames.YES)
330
+ onNavigateMaster(dir); },
331
+ });
332
+ };
333
+ // =============================================================================================
334
+ // RIGHE DEL RIEPILOGO
335
+ // =============================================================================================
336
+ const openAddForm = () => {
337
+ if (!selectedRelation?.detailTID)
338
+ return;
339
+ setEditingRowId(undefined);
340
+ setIsOpenAddForm(true);
341
+ };
342
+ // Identità stabile: la griglia dei dettagli la usa nelle sue callback memoizzate
343
+ const openEditForm = useCallback((row) => {
344
+ if (!row)
345
+ return;
346
+ setEditingRowId(row.id);
347
+ setIsOpenAddForm(true);
348
+ }, []);
349
+ const closeAddForm = () => {
350
+ setEditingRowId(undefined);
351
+ setIsOpenAddForm(false);
352
+ };
353
+ const deleteRow = useCallback((row) => {
354
+ if (!row)
355
+ return;
356
+ TMMessageBoxManager.show({
357
+ title: SDKUI_Localizator.Remove,
358
+ message: SDKUI_Localizator.RemoveDetailFromSummary_Confirm,
359
+ buttons: [ButtonNames.YES, ButtonNames.NO],
360
+ onButtonClick: (button) => {
361
+ if (button !== ButtonNames.YES)
362
+ return;
363
+ setArchiveRows(prev => prev.filter(r => r.id !== row.id));
364
+ setFocusedRowKey(prev => (prev === row.id ? undefined : prev));
365
+ setSelectedRowKeys(prev => prev.filter(k => k !== row.id));
366
+ },
367
+ });
368
+ }, []);
369
+ // I valori del form sono stringhe: le celle numeriche vogliono il numero, richiesto dal loro editor
370
+ const buildArchiveRow = useCallback((id, tid, values, file) => {
371
+ const row = { id, __tid: tid, __values: values, __file: file };
372
+ for (const v of values) {
373
+ if (v.mid === undefined)
374
+ continue;
375
+ row[String(v.mid)] = numericMidsRef.current.has(v.mid) ? toGridNumber(v.value) : (v.value ?? '');
376
+ }
377
+ return row;
378
+ }, []);
379
+ // Aggiunge/aggiorna nel riepilogo la riga compilata nel form, senza archiviare a sistema
380
+ const upsertRow = (values, file) => {
381
+ const tid = selectedRelation?.detailTID;
382
+ if (tid === undefined)
383
+ return;
384
+ if (editingRowId !== undefined) {
385
+ setArchiveRows(prev => prev.map(r => (r.id === editingRowId ? buildArchiveRow(editingRowId, tid, values, file) : r)));
386
+ }
387
+ else {
388
+ rowIdRef.current += 1;
389
+ setArchiveRows(prev => [...prev, buildArchiveRow(rowIdRef.current, tid, values, file)]);
390
+ }
391
+ closeAddForm();
392
+ };
393
+ /**
394
+ * Modifica diretta in griglia di uno o più metadati testuali o numerici (changes: mid -> valore).
395
+ * La riga è aggiornata sul posto e non con setArchiveRows: un nuovo riferimento all'array
396
+ * ricaricherebbe la griglia a ogni cella confermata, perdendo il clic sulla cella successiva.
397
+ * L'array __values è sostituito, non modificato: il duplicato, che lo condivide, resta invariato
398
+ */
399
+ const updateRowValues = useCallback((rowId, changes) => {
400
+ const changedMids = Object.keys(changes);
401
+ if (changedMids.length === 0)
402
+ return;
403
+ const row = archiveRowsRef.current.find(r => r.id === rowId);
404
+ if (!row)
405
+ return;
406
+ // I __values vanno a sistema come stringhe, anche quelli che in griglia sono numeri. La griglia
407
+ // restituisce null sull'editor svuotato: a sistema il metadato va vuoto, non nullo
408
+ const normalized = {};
409
+ changedMids.forEach(mid => { normalized[mid] = changes[mid] === null || changes[mid] === undefined ? '' : String(changes[mid]); });
410
+ const values = row.__values.map(v => (v.mid !== undefined && normalized[String(v.mid)] !== undefined
411
+ ? { ...v, value: normalized[String(v.mid)] }
412
+ : v));
413
+ // Un metadato assente dai valori del form non arriverebbe all'archiviazione
414
+ changedMids.filter(mid => !values.some(v => String(v.mid) === mid)).forEach(mid => {
415
+ const added = new MetadataValueDescriptorEx();
416
+ added.tid = row.__tid;
417
+ added.mid = Number(mid);
418
+ added.value = normalized[mid];
419
+ values.push(added);
420
+ });
421
+ row.__values = values;
422
+ // In cella il numero resta un numero, come lo vuole il suo editor
423
+ changedMids.forEach(mid => { row[mid] = numericMidsRef.current.has(Number(mid)) ? toGridNumber(normalized[mid]) : normalized[mid]; });
424
+ }, []);
425
+ // I __values sono condivisi per riferimento: la modifica sostituisce l'intero array, quindi non intacca il duplicato
426
+ const duplicateRow = useCallback((row) => {
427
+ if (!row)
428
+ return;
429
+ rowIdRef.current += 1;
430
+ setArchiveRows(prev => [...prev, buildArchiveRow(rowIdRef.current, row.__tid, row.__values, row.__file)]);
431
+ }, [buildArchiveRow]);
432
+ /**
433
+ * Copia un dettaglio già archiviato tra le righe da archiviare: i suoi metadati (e il file, se il
434
+ * tipo lo prevede e il documento ne ha uno) diventano una nuova riga del riepilogo. Il documento
435
+ * archiviato non è toccato: quello che si ottiene è un nuovo dettaglio, ancora da archiviare.
436
+ * I numeratori non si copiano, il loro valore lo assegna il sistema all'archiviazione.
437
+ * Ogni motivo che impedisce la copia, o che la rende parziale, arriva all'utente con un avviso:
438
+ * la riga entra nel riepilogo solo se i metadati del dettaglio sono stati letti davvero.
439
+ */
440
+ const copyArchivedDetailToRows = async (dcmt) => {
441
+ // Clic ripetuti sull'icona: una copia alla volta, altrimenti spinner e righe si sovrappongono
442
+ if (isCopyingArchivedDetailRef.current)
443
+ return;
444
+ try {
445
+ const detailTID = selectedRelation?.detailTID;
446
+ // Senza tipo selezionato il riepilogo non ha colonne in cui copiare il dettaglio
447
+ if (detailTID === undefined) {
448
+ ShowAlert({ message: SDKUI_Localizator.SelectDcmtTypeForDetailArchiving, mode: 'warning', title: SDKUI_Localizator.Duplicate, duration: 3000 });
449
+ return;
450
+ }
451
+ // La griglia dei risultati porta i valori di riga come stringhe: TID e DID vanno usati come numeri
452
+ const archivedDcmt = { TID: Number(dcmt?.TID), DID: Number(dcmt?.DID) };
453
+ if (!Number.isFinite(archivedDcmt.TID) || !Number.isFinite(archivedDcmt.DID) || archivedDcmt.TID <= 0 || archivedDcmt.DID <= 0) {
454
+ ShowAlert({ message: SDKUI_Localizator.InvalidDcmt, mode: 'warning', title: SDKUI_Localizator.Duplicate, duration: 3000 });
455
+ return;
456
+ }
457
+ // Le righe del riepilogo sono del tipo selezionato: un dettaglio di altro tipo non ne ha le colonne
458
+ if (archivedDcmt.TID !== detailTID) {
459
+ ShowAlert({ message: SDKUI_Localizator.DetailNotOfSelectedDcmtType, mode: 'warning', title: SDKUI_Localizator.Duplicate, duration: 3000 });
460
+ return;
461
+ }
462
+ isCopyingArchivedDetailRef.current = true;
463
+ TMSpinner.show({ description: SDKUI_Localizator.Loading });
464
+ const [detailDtd, metadataByMid] = await Promise.all([getDetailDtdAsync(detailTID), getDcmtMetadataByMid(archivedDcmt)]);
465
+ // Senza la struttura del tipo non si sa quali metadati copiare: la riga nascerebbe vuota
466
+ if (!detailDtd) {
467
+ ShowAlert({ message: SDKUI_Localizator.DcmtTypeStructureNotRetrieved, mode: 'error', title: SDKUI_Localizator.Duplicate, duration: 5000 });
468
+ return;
469
+ }
470
+ // Il permesso serve comunque all'archiviazione: dirlo qui evita di compilare righe inutili
471
+ if (!canArchive(detailDtd)) {
472
+ ShowAlert({ message: SDKUI_Localizator.YouDoNotHavePermissionsToArchiveDetailDocumentsOfThisType, mode: 'warning', title: SDKUI_Localizator.Duplicate, duration: 5000 });
473
+ return;
474
+ }
475
+ // Metadati non letti (sessione non valida, documento non più visibile): una riga di valori vuoti non serve
476
+ if (metadataByMid.size === 0) {
477
+ ShowAlert({ message: SDKUI_Localizator.DetailMetadataNotRetrieved, mode: 'error', title: SDKUI_Localizator.Duplicate, duration: 5000 });
478
+ return;
479
+ }
480
+ const userMetadata = getUserMetadataSorted(detailDtd);
481
+ const archivableMetadata = userMetadata.filter(md => md.dataDomain !== MetadataDataDomains.Numerator && handleArchiveVisibility(md));
482
+ // Niente da copiare: il tipo non ha metadati utente, o nessuno di questi è archiviabile
483
+ if (archivableMetadata.length === 0) {
484
+ const message = userMetadata.length > 0
485
+ ? SDKUI_Localizator.YouDoNotHavePermissionsToArchiveDetailDocumentsOfThisType
486
+ : SDKUI_Localizator.DetailMetadataNotRetrieved;
487
+ ShowAlert({ message, mode: 'warning', title: SDKUI_Localizator.Duplicate, duration: 5000 });
488
+ return;
489
+ }
490
+ // Gli stessi metadati che compila il form della riga: valori come stringhe, mancanti a vuoto
491
+ const values = archivableMetadata.map(md => {
492
+ const mvd = new MetadataValueDescriptorEx();
493
+ mvd.tid = detailTID;
494
+ mvd.mid = md.id;
495
+ mvd.md = md;
496
+ // Date e numeri nel formato del form: la riga copiata si rilegge e si modifica come le altre
497
+ mvd.value = toArchiveRowValue(metadataByMid.get(md.id), md.dataType);
498
+ mvd.isRequired = md.isRequired?.toString();
499
+ return mvd;
500
+ });
501
+ // Il file segue la riga solo se il tipo lo prevede e il documento ne ha uno; se non si
502
+ // recupera la riga si inserisce comunque, il file si aggiunge dal form della riga
503
+ let file = undefined;
504
+ // Il file c'è se lo dice il metadato di sistema del documento: la colonna della griglia
505
+ // dei risultati è solo un ripiego, la ricerca potrebbe non averla tra i suoi valori
506
+ const fileExt = String(metadataByMid.get(SystemMIDsAsNumber.FileExt) ?? dcmt?.FILEEXT ?? '');
507
+ const hasFile = fileExt !== '';
508
+ const supportsFile = detailDtd.archiveConstraint !== ArchiveConstraints.OnlyMetadata;
509
+ if (hasFile && supportsFile) {
510
+ if (detailDtd.perm?.canRetrieveFile !== AccessLevels.Yes) {
511
+ ShowAlert({ message: `${SDKUI_Localizator.DetailFileNotRetrieved}: ${SDKUI_Localizator.YouDoNotHavePermissionsToRetrieveTheFile}`, mode: 'warning', title: SDKUI_Localizator.Duplicate, duration: 5000 });
512
+ }
513
+ else {
514
+ try {
515
+ const rfo = new RetrieveFileOptions();
516
+ rfo.retrieveReason = DcmtOpers.None;
517
+ rfo.generalRetrieveFormat = GeneralRetrieveFormats.OriginalUnsigned;
518
+ file = await SDK_Globals.tmSession?.NewSearchEngine().RetrieveFileAsync(archivedDcmt.TID, archivedDcmt.DID, rfo);
519
+ // Nessun errore ma nemmeno il file (sessione non valida): l'utente deve saperlo
520
+ if (!file)
521
+ ShowAlert({ message: SDKUI_Localizator.DetailFileNotRetrieved, mode: 'warning', title: SDKUI_Localizator.Duplicate, duration: 5000 });
522
+ }
523
+ catch (error) {
524
+ ShowAlert({ message: `${SDKUI_Localizator.DetailFileNotRetrieved}: ${getExceptionMessage(error)}`, mode: 'warning', title: SDKUI_Localizator.Duplicate, duration: 5000 });
525
+ }
526
+ }
527
+ }
528
+ rowIdRef.current += 1;
529
+ const newRowId = rowIdRef.current;
530
+ setArchiveRows(prev => [...prev, buildArchiveRow(newRowId, detailTID, values, file)]);
531
+ // La riga copiata è quella su cui si lavora: fuoco e selezione la seguono
532
+ setFocusedRowKey(newRowId);
533
+ setSelectedRowKeys([newRowId]);
534
+ // Con la sezione chiusa la riga appena copiata non si vedrebbe
535
+ if (!sectionsOpen.archiveList)
536
+ setSectionOpen('archiveList', true);
537
+ ShowAlert({ message: SDKUI_Localizator.DetailCopiedToArchiveList, mode: 'success', title: SDKUI_Localizator.Duplicate, duration: 3000 });
538
+ // Il tipo pretende il file: senza, la riga non passerebbe l'archiviazione
539
+ if (!file && detailDtd.archiveConstraint === ArchiveConstraints.ContentCompulsory) {
540
+ ShowAlert({ message: SDKUI_Localizator.DetailFileToAddFromRowForm, mode: 'warning', title: SDKUI_Localizator.Duplicate, duration: 5000 });
541
+ }
542
+ }
543
+ catch (error) {
544
+ TMExceptionBoxManager.show({ exception: error });
545
+ }
546
+ finally {
547
+ // Spinner e blocco partono insieme: se la copia non è nemmeno iniziata non c'è niente da chiudere
548
+ if (isCopyingArchivedDetailRef.current) {
549
+ isCopyingArchivedDetailRef.current = false;
550
+ TMSpinner.hide();
551
+ }
552
+ }
553
+ };
554
+ // All'apertura del form pre-compila i MID di collegamento valorizzati dal master
555
+ useEffect(() => {
556
+ if (!isOpenAddForm || !selectedRelation) {
557
+ setAssociationInputMids([]);
558
+ return;
559
+ }
560
+ let cancelled = false;
561
+ getMasterAssociationsForDetailAsync(selectedRelation)
562
+ .then(associations => { if (!cancelled)
563
+ setAssociationInputMids(associations); })
564
+ .catch(() => { if (!cancelled)
565
+ setAssociationInputMids([]); });
566
+ return () => { cancelled = true; };
567
+ }, [isOpenAddForm, selectedRelation]);
568
+ // MID pre-compilati: in inserimento i collegamenti dal master, in modifica i valori della riga
569
+ const editingRow = editingRowId !== undefined ? archiveRows.find(r => r.id === editingRowId) : undefined;
570
+ const nextFormInputMids = editingRow
571
+ ? editingRow.__values
572
+ .filter(v => v.mid !== undefined && v.value !== undefined && v.value !== null && v.value !== '')
573
+ .map(v => ({ mid: v.mid, value: String(v.value) }))
574
+ : associationInputMids;
575
+ // Il form riapplica i valori quando cambia il riferimento di inputMids: si mantiene la stessa
576
+ // istanza finché il contenuto non cambia, così le modifiche dell'utente non vengono sovrascritte
577
+ const formInputMidsSignature = nextFormInputMids.map(o => `${o.mid}=${o.value}`).join('|');
578
+ const formInputMidsRef = useRef(nextFormInputMids);
579
+ const formInputMidsSignatureRef = useRef(formInputMidsSignature);
580
+ if (formInputMidsSignatureRef.current !== formInputMidsSignature) {
581
+ formInputMidsSignatureRef.current = formInputMidsSignature;
582
+ formInputMidsRef.current = nextFormInputMids;
583
+ }
584
+ const formInputMids = formInputMidsRef.current;
585
+ // =============================================================================================
586
+ // ARCHIVIAZIONE
587
+ // =============================================================================================
588
+ // Archivia solo le righe del tipo corrente: quelle di altri tipi restano intatte
589
+ const archiveAllAsync = async () => {
590
+ const detailTID = selectedRelation?.detailTID;
591
+ if (detailTID === undefined)
592
+ return;
593
+ const rowsToArchive = archiveRows.filter(r => r.__tid === detailTID);
594
+ if (rowsToArchive.length === 0) {
595
+ ShowAlert({ message: SDKUI_Localizator.NoDataToDisplay, mode: 'warning', title: SDKUI_Localizator.Archive, duration: 3000 });
596
+ return;
597
+ }
598
+ try {
599
+ setIsArchiving(true);
600
+ const detailDtd = await getDetailDtdAsync(detailTID);
601
+ if (!canArchive(detailDtd)) {
602
+ ShowAlert({ message: SDKUI_Localizator.YouDoNotHavePermissionsToArchiveDetailDocumentsOfThisType, mode: 'warning', title: SDKUI_Localizator.Archive, duration: 5000 });
603
+ return;
604
+ }
605
+ const dataTypeByMid = new Map();
606
+ (detailDtd?.metadata ?? []).forEach(md => { if (md.id !== undefined)
607
+ dataTypeByMid.set(md.id, md.dataType); });
608
+ // Collegamenti della relazione valorizzati dal master (prevalgono sui valori del form)
609
+ const associationMids = await getMasterAssociationsForDetailAsync(selectedRelation);
610
+ // Con piu' righe: primaria sui documenti, secondaria sui byte. Con una riga sola solo i byte.
611
+ const isBatch = rowsToArchive.length > 1;
612
+ const waitPanel = TMWaitPanelManager.show({
613
+ title: SDKUI_Localizator.Archiving,
614
+ max: isBatch ? rowsToArchive.length : 0,
615
+ cancelable: true,
616
+ showSecondary: isBatch,
617
+ showCounters: isBatch,
618
+ primaryLabel: isBatch ? SDKUI_Localizator.Documents : undefined,
619
+ secondaryLabel: isBatch ? 'File corrente' : undefined,
620
+ });
621
+ const publishBytes = (loaded, total) => {
622
+ const text = total > 0
623
+ ? `Uploading... ${Globalization.getNumberDisplayValue(loaded, true)} / ${Globalization.getNumberDisplayValue(total, true)}`
624
+ : '';
625
+ if (isBatch)
626
+ waitPanel.setSecondary({ value: loaded, max: total, text });
627
+ else
628
+ waitPanel.update({ value: loaded, max: total, subtitle: text });
629
+ };
630
+ const result = [];
631
+ const archivedRowIds = new Set();
632
+ try {
633
+ for (let i = 0; i < rowsToArchive.length; i++) {
634
+ // Con la conferma di interruzione aperta il loop resta in pausa qui
635
+ await waitPanel.waitWhilePaused();
636
+ if (waitPanel.aborted) {
637
+ result.push({ rowIndex: i, id1: detailTID, id2: undefined, resultType: ResultTypes.WARNING, description: `Operazione interrotta. Elaborati ${i} documenti` });
638
+ waitPanel.warning();
639
+ break;
640
+ }
641
+ if (isBatch)
642
+ waitPanel.setSubtitle(`${SDKUI_Localizator.Archiving} ${i + 1} / ${rowsToArchive.length}`);
643
+ const rowData = rowsToArchive[i];
644
+ try {
645
+ const newDID = await archiveRowAsync(rowData, detailTID, dataTypeByMid, associationMids, waitPanel.signal, publishBytes);
646
+ archivedRowIds.add(rowData.id);
647
+ result.push({ rowIndex: i, id1: detailTID, id2: newDID, resultType: ResultTypes.SUCCESS });
648
+ waitPanel.ok();
649
+ }
650
+ catch (error) {
651
+ const err = error;
652
+ // Annullamento richiesto dall'utente: interrompe senza mostrare l'errore
653
+ if (err?.name === 'CanceledError' || waitPanel.aborted) {
654
+ result.push({ rowIndex: i, id1: detailTID, id2: undefined, resultType: ResultTypes.WARNING, description: `Operazione interrotta. Elaborati ${i} documenti` });
655
+ waitPanel.warning();
656
+ break;
657
+ }
658
+ result.push({ rowIndex: i, id1: detailTID, id2: undefined, resultType: ResultTypes.ERROR, description: getExceptionMessage(error) });
659
+ waitPanel.error();
660
+ if (stopOnFirstError)
661
+ break;
662
+ }
663
+ // Avanza a elaborazione conclusa: la barra non arriva al 100% prima dell'ultimo documento
664
+ if (isBatch)
665
+ await waitPanel.step();
666
+ }
667
+ }
668
+ finally {
669
+ // Chiuso prima del riepilogo dei risultati
670
+ waitPanel.close();
671
+ }
672
+ // Le righe archiviate escono dal riepilogo
673
+ if (archivedRowIds.size > 0) {
674
+ setArchiveRows(prev => prev.filter(r => !archivedRowIds.has(r.id)));
675
+ setSelectedRowKeys([]);
676
+ setFocusedRowKey(undefined);
677
+ refreshArchivedDetailsAsync();
678
+ }
679
+ const successCount = result.filter(o => o.resultType === ResultTypes.SUCCESS).length;
680
+ const successMsg = `${successCount} ${successCount === 1 ? 'documento di dettaglio archiviato' : 'documenti di dettaglio archiviati'} con successo`;
681
+ TMResultManager.show(result, SDKUI_Localizator.Archive, 'TID', 'DID', successMsg);
682
+ // Tutte le righe del tipo corrente archiviate senza errori: il modale si chiude
683
+ const errorCount = result.filter(o => o.resultType === ResultTypes.ERROR).length;
684
+ if (errorCount === 0 && archivedRowIds.size === rowsToArchive.length)
685
+ onClose();
686
+ }
687
+ catch (error) {
688
+ TMExceptionBoxManager.show({ exception: error });
689
+ }
690
+ finally {
691
+ setIsArchiving(false);
692
+ }
693
+ };
694
+ // Archiviazione di una singola riga, con avanzamento dell'upload notificato a onBytes
695
+ const archiveRowAsync = async (row, detailTID, dataTypeByMid, associationMids, signal, onBytes) => {
696
+ const ae = new ArchiveEngineByID(SDK_Globals.tmSession);
697
+ ae.TID = detailTID;
698
+ ae.Metadata_ClearAll();
699
+ // Valori del form + associazioni con il master (le associazioni prevalgono). I metadati di
700
+ // sistema (TID, DID, FILEEXT...) non si archiviano: li assegna il sistema e mandarli fa fallire
701
+ // l'archiviazione. Il form li esclude già (vedi useArchiveListForm), qui è la rete di sicurezza
702
+ // per le righe che arrivano da altre strade, come la copia di un dettaglio già archiviato
703
+ const valueByMid = new Map();
704
+ for (const v of row.__values) {
705
+ if (v.mid !== undefined && v.mid > 99 && v.value)
706
+ valueByMid.set(v.mid, v.value);
707
+ }
708
+ for (const a of associationMids) {
709
+ if (a.mid > 99 && a.value)
710
+ valueByMid.set(a.mid, a.value);
711
+ }
712
+ valueByMid.forEach((value, mid) => {
713
+ switch (dataTypeByMid.get(mid)) {
714
+ case MetadataDataTypes.DateTime:
715
+ ae.Metadata_AddDateTime(mid, new Date(value));
716
+ break;
717
+ case MetadataDataTypes.Number:
718
+ ae.Metadata_AddNumber(mid, parseFloat(value));
719
+ break;
720
+ default:
721
+ ae.Metadata_AddString(mid, value);
722
+ break;
723
+ }
724
+ });
725
+ if (row.__file)
726
+ ae.ArchivingFile = row.__file;
727
+ return ae.ArchiveAsync(signal, (pd) => {
728
+ // Upload completato: la barra si azzera per il file successivo
729
+ if (pd.ProgressBarValue === pd.ProgressBarMaximum)
730
+ onBytes(0, 0);
731
+ else
732
+ onBytes(pd.ProgressBarValue ?? 0, pd.ProgressBarMaximum ?? 0);
733
+ });
734
+ };
735
+ return {
736
+ // Flusso
737
+ isLoading, isSummaryReady, phase, setPhase,
738
+ detailRelationItems, selectedRelation, selectedItem, hasMultipleTypes,
739
+ selectRelation, backToTypeSelection, isBackLocked, closeModal,
740
+ // Navigazione del master sulla lista di provenienza
741
+ isMasterNavigationEnabled, canNavigateMasterPrev, canNavigateMasterNext, navigateMaster,
742
+ // Sezioni
743
+ sectionsOpen, setSectionOpen, openAllSections, clearLayout, layoutResetToken,
744
+ hasSavedLayout, setHasSavedLayout,
745
+ // Master
746
+ canUpdateMaster, masterFieldsCount, getMasterMetadataAsync, masterInfoReloadToken,
747
+ isOpenMasterForm, setIsOpenMasterForm, handleMasterSavedAsync,
748
+ // Dettagli già archiviati
749
+ archivedDetailResults, archivedDetailsCount, isLoadingArchivedDetails, refreshArchivedDetailsAsync,
750
+ // Dettagli da archiviare
751
+ detailColumns, detailSupportsFile, archiveRows, currentRows,
752
+ focusedRowKey, setFocusedRowKey, selectedRowKeys, setSelectedRowKeys,
753
+ isOpenAddForm, editingRowId, editingRow, formInputMids,
754
+ openAddForm, openEditForm, closeAddForm, deleteRow, duplicateRow, upsertRow, updateRowValues, copyArchivedDetailToRows,
755
+ // Archiviazione
756
+ stopOnFirstError, setStopOnFirstError, isArchiving, archiveAllAsync,
757
+ };
758
+ };