@topconsultnpm/sdkui-react 6.22.0-t2 → 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.
- package/lib/components/NewComponents/ContextMenu/TMContextMenu.js +3 -3
- package/lib/components/NewComponents/ContextMenu/hooks.d.ts +1 -1
- package/lib/components/NewComponents/FloatingMenuBar/TMFloatingMenuBar.js +6 -6
- package/lib/components/NewComponents/FloatingMenuBar/styles.d.ts +15 -15
- package/lib/components/base/Styled.js +7 -5
- package/lib/components/base/TMAccordionNew.js +7 -7
- package/lib/components/base/TMAlert.d.ts +32 -32
- package/lib/components/base/TMAlert.js +6 -8
- package/lib/components/base/TMAreaManager.js +53 -55
- package/lib/components/base/TMButton.js +4 -4
- package/lib/components/base/TMClosableList.js +3 -3
- package/lib/components/base/TMCustomButton.js +40 -48
- package/lib/components/base/TMDataGrid.d.ts +2 -0
- package/lib/components/base/TMDataGrid.js +35 -7
- package/lib/components/base/TMDropDownMenu.js +1 -1
- package/lib/components/base/TMFileManagerDataGridView.js +1 -1
- package/lib/components/base/TMFileManagerThumbnailItems.js +2 -2
- package/lib/components/base/TMLayout.d.ts +2 -2
- package/lib/components/base/TMLayout.js +3 -3
- package/lib/components/base/TMList.js +1 -1
- package/lib/components/base/TMListView.js +3 -3
- package/lib/components/base/TMModal.js +1 -1
- package/lib/components/base/TMPanel.js +8 -8
- package/lib/components/base/TMPopUp.js +2 -2
- package/lib/components/base/TMResizableMenu.js +2 -2
- package/lib/components/base/TMRightSidebar.js +1 -1
- package/lib/components/base/TMShowAllOrMaxItemsButton.js +1 -1
- package/lib/components/base/TMSpinner.d.ts +2 -1
- package/lib/components/base/TMSpinner.js +9 -7
- package/lib/components/base/TMTab.js +2 -2
- package/lib/components/base/TMTabControl.d.ts +106 -0
- package/lib/components/base/TMTabControl.js +664 -0
- package/lib/components/base/TMToggleButton.js +2 -2
- package/lib/components/base/TMToolbarCard.js +3 -3
- package/lib/components/base/TMTreeView.js +35 -3
- package/lib/components/base/TMWaitPanel.js +3 -3
- package/lib/components/base/waitPanel/TMWaitPanelConfirm.d.ts +12 -0
- package/lib/components/base/waitPanel/TMWaitPanelConfirm.js +185 -0
- package/lib/components/base/waitPanel/TMWaitPanelManager.d.ts +169 -0
- package/lib/components/base/waitPanel/TMWaitPanelManager.js +320 -0
- package/lib/components/base/waitPanel/TMWaitPanelView.d.ts +53 -0
- package/lib/components/base/waitPanel/TMWaitPanelView.js +274 -0
- package/lib/components/base/waitPanel/index.d.ts +1 -0
- package/lib/components/base/waitPanel/index.js +1 -0
- package/lib/components/choosers/TMCultureIDPicker.js +2 -2
- package/lib/components/choosers/TMDataListItemChooser.js +1 -1
- package/lib/components/choosers/TMDataListItemPicker.js +2 -2
- package/lib/components/choosers/TMDcmtTypeChooser.js +1 -1
- package/lib/components/choosers/TMDiskChooser.js +1 -1
- package/lib/components/choosers/TMDistinctValues.js +28 -19
- package/lib/components/choosers/TMDynDataListItemChooser.js +1 -1
- package/lib/components/choosers/TMImageIDChooser.js +1 -1
- package/lib/components/choosers/TMMetadataChooser.js +2 -2
- package/lib/components/choosers/TMPathChooser.js +17 -34
- package/lib/components/choosers/TMQuickSearchInfo.js +4 -4
- package/lib/components/choosers/TMQuickSearchSettingsForm.js +1 -1
- package/lib/components/choosers/TMRelationChooser.js +1 -1
- package/lib/components/choosers/TMSelectedValuesSummary.js +1 -3
- package/lib/components/choosers/TMUserChooser.js +1 -1
- package/lib/components/editors/TMCheckBox.js +1 -1
- package/lib/components/editors/TMDropDown.js +7 -7
- package/lib/components/editors/TMEditorStyled.d.ts +1 -1
- package/lib/components/editors/TMFormulaEditor.js +1 -1
- package/lib/components/editors/TMHtmlContentDisplay.js +1 -1
- package/lib/components/editors/TMLocalizedTextBox.js +1 -1
- package/lib/components/editors/TMMetadataValues.js +6 -6
- package/lib/components/editors/TMRadioButton.js +2 -2
- package/lib/components/editors/TMSummary.js +3 -3
- package/lib/components/editors/TMTextArea.js +3 -3
- package/lib/components/editors/TMTextBox.js +3 -3
- package/lib/components/editors/TMTextExpression.js +1 -1
- package/lib/components/features/archive/TMArchive.js +7 -2
- package/lib/components/features/assistant/TMHelpOnLineChat.d.ts +29 -0
- package/lib/components/features/assistant/TMHelpOnLineChat.js +1384 -0
- package/lib/components/features/assistant/TMToppyDraggableHelpCenter.js +4 -4
- package/lib/components/features/assistant/TMToppySpeechBubble.js +1 -1
- package/lib/components/features/assistant/dataset/Designer-Suggested-Questions.json +102 -0
- package/lib/components/features/assistant/dataset/Login-Suggested-Questions.json +32 -0
- package/lib/components/features/assistant/dataset/Surfer-Suggested-Questions.json +102 -0
- package/lib/components/features/documents/TMBatchUpdateForm.js +4 -5
- package/lib/components/features/documents/TMCopyToFolderForm.js +50 -42
- package/lib/components/features/documents/TMDcmtForm.js +53 -64
- package/lib/components/features/documents/TMDcmtFormActionButtons.js +2 -2
- package/lib/components/features/documents/TMDcmtIcon.d.ts +1 -3
- package/lib/components/features/documents/TMDcmtIcon.js +3 -5
- package/lib/components/features/documents/TMDcmtPreview.js +6 -4
- package/lib/components/features/documents/TMDcmtTasks.js +2 -9
- package/lib/components/features/documents/TMDownloadRelationViewerSection.js +1 -1
- package/lib/components/features/documents/TMFileUploader.js +7 -7
- package/lib/components/features/documents/TMMasterDetailDcmts.d.ts +2 -0
- package/lib/components/features/documents/TMMasterDetailDcmts.js +55 -130
- package/lib/components/features/documents/TMMasterInfoFields.js +4 -5
- package/lib/components/features/documents/TMMergeToPdfForm.js +56 -54
- package/lib/components/features/documents/TMMultiMasterDetailDcmtsForm.js +3 -6
- package/lib/components/features/documents/TMMultiMasterDetailDcmtsUtils.d.ts +1 -1
- package/lib/components/features/documents/TMMultiMasterDetailDcmtsUtils.js +4 -4
- package/lib/components/features/documents/TMRelationViewer.d.ts +16 -9
- package/lib/components/features/documents/TMRelationViewer.js +240 -168
- package/lib/components/features/documents/copyAndMergeDcmtsShared.d.ts +1 -1
- package/lib/components/features/documents/relationTreeState.d.ts +32 -0
- package/lib/components/features/documents/relationTreeState.js +89 -0
- package/lib/components/features/search/TMMetadataOutputForm.js +1 -1
- package/lib/components/features/search/TMMetadataSorterForm.js +1 -1
- package/lib/components/features/search/TMSavedQuerySelector.js +2 -2
- package/lib/components/features/search/TMSearch.d.ts +1 -1
- package/lib/components/features/search/TMSearch.js +71 -12
- package/lib/components/features/search/TMSearchQueryEditor.js +2 -2
- package/lib/components/features/search/TMSearchQueryPanel.js +1 -5
- package/lib/components/features/search/TMSearchResult.d.ts +5 -0
- package/lib/components/features/search/TMSearchResult.js +34 -18
- package/lib/components/features/search/TMSignatureInfoContent.js +1 -1
- package/lib/components/features/search/TMTreeSelector.js +38 -36
- package/lib/components/features/search/TMViewHistoryDcmt.js +14 -25
- package/lib/components/features/tasks/TMTaskForm.js +1 -1
- package/lib/components/features/tasks/TMTaskFormUtils.js +3 -3
- package/lib/components/features/tasks/TMTasksAgenda.js +1 -1
- package/lib/components/features/tasks/TMTasksCalendar.js +1 -1
- package/lib/components/features/tasks/TMTasksHeader.js +1 -1
- package/lib/components/features/tasks/TMTasksUtilsView.js +1 -1
- package/lib/components/features/tasks/TMTasksView.js +97 -102
- package/lib/components/features/wg/TMWGsCopyMoveForm.js +92 -92
- package/lib/components/features/workflow/TMWorkflowPopup.js +2 -2
- package/lib/components/features/workflow/diagram/ConnectionComponent.js +3 -3
- package/lib/components/features/workflow/diagram/DiagramItemComponent.js +3 -3
- package/lib/components/features/workflow/diagram/DiagramItemForm.js +1 -1
- package/lib/components/features/workflow/diagram/DiagramItemSvgContent.js +1 -1
- package/lib/components/features/workflow/diagram/RecipientList.js +1 -1
- package/lib/components/features/workflow/diagram/WFDiagram.js +4 -4
- package/lib/components/forms/Login/Menu.js +1 -1
- package/lib/components/forms/Login/OTPReader.js +1 -1
- package/lib/components/forms/Login/PasswordStrengthChecker.js +1 -1
- package/lib/components/forms/Login/RapidAccessLogin.js +2 -2
- package/lib/components/forms/Login/RecoverPasswordFlow.js +2 -2
- package/lib/components/forms/Login/SelectBox.js +1 -1
- package/lib/components/forms/Login/StepIndicator.js +1 -1
- package/lib/components/forms/Login/TMLoginForm.js +7 -7
- package/lib/components/forms/Login/TextBox.js +1 -1
- package/lib/components/forms/TMApplyForm.js +3 -3
- package/lib/components/forms/TMChooserForm.js +1 -1
- package/lib/components/forms/TMResultDialog.js +1 -1
- package/lib/components/forms/TMSaveForm.js +3 -3
- package/lib/components/grids/TMBlogAttachments.js +1 -1
- package/lib/components/grids/TMBlogsPost.js +92 -96
- package/lib/components/grids/TMBlogsPostUtils.js +1 -1
- package/lib/components/grids/TMRecentsManager.js +4 -4
- package/lib/components/grids/TMValidationItemsList.js +1 -1
- package/lib/components/index.d.ts +6 -0
- package/lib/components/index.js +5 -0
- package/lib/components/layout/panelManager/TMPanelManagerContainer.js +3 -3
- package/lib/components/layout/panelManager/TMPanelManagerToolbar.js +1 -1
- package/lib/components/layout/panelManager/TMPanelWrapper.js +1 -1
- package/lib/components/pages/TMPage.js +30 -32
- package/lib/components/query/TMQueryCountButton.js +1 -1
- package/lib/components/query/TMQueryEditor.d.ts +3 -0
- package/lib/components/query/TMQueryEditor.js +9 -8
- package/lib/components/sidebar/TMCommandsPanel.js +1 -1
- package/lib/components/sidebar/TMHeader.d.ts +1 -0
- package/lib/components/sidebar/TMHeader.js +9 -9
- package/lib/components/sidebar/TMSidebar.js +1 -1
- package/lib/components/sidebar/TMSidebarItem.js +1 -1
- package/lib/components/viewers/TMMidViewer.js +2 -2
- package/lib/components/viewers/TMTidViewer.js +3 -3
- package/lib/components/wizard/TMStepIndicator.js +3 -3
- package/lib/helper/Enum_Localizator.d.ts +4 -4
- package/lib/helper/Enum_Localizator.js +1 -0
- package/lib/helper/Globalization.d.ts +1 -1
- package/lib/helper/SDKUI_Localizator.d.ts +736 -712
- package/lib/helper/SDKUI_Localizator.js +240 -0
- package/lib/helper/TMAppUpdatedModal.js +2 -2
- package/lib/helper/TMIcons.js +1 -3
- package/lib/helper/TMPdfViewer.js +1 -1
- package/lib/helper/TMToppyMessage.js +1 -1
- package/lib/helper/TMUtils.js +1 -1
- package/lib/helper/ZipManager.d.ts +1 -1
- package/lib/helper/ZipManager.js +2 -1
- package/lib/helper/checkinCheckoutManager.d.ts +1 -1
- package/lib/helper/checkinCheckoutManager.js +1 -1
- package/lib/helper/dcmtsHelper.d.ts +4 -2
- package/lib/helper/dcmtsHelper.js +3 -1
- package/lib/helper/helpers.d.ts +1 -1
- package/lib/helper/helpers.js +2 -2
- package/lib/hooks/tmDistinctValuesGridHelper.d.ts +1 -1
- package/lib/hooks/useCaseFlowApprove.d.ts +3 -3
- package/lib/hooks/useCheckInOutOperations.d.ts +0 -6
- package/lib/hooks/useCheckInOutOperations.js +14 -34
- package/lib/hooks/useDcmtOperations.d.ts +3 -12
- package/lib/hooks/useDcmtOperations.js +187 -179
- package/lib/hooks/useDocumentOperations.d.ts +1 -0
- package/lib/hooks/useDocumentOperations.js +16 -27
- package/lib/hooks/useInputDialog.js +1 -1
- package/lib/hooks/useMultiMasterDetailDcmts.d.ts +1 -16
- package/lib/hooks/useMultiMasterDetailDcmts.js +65 -67
- package/lib/hooks/usePreventFileDrop.js +9 -0
- package/lib/hooks/useQueryParametersDialog.js +1 -1
- package/lib/hooks/useWorkflowApprove.js +33 -17
- package/lib/services/caseflow/CaseFlowService.d.ts +47 -6
- package/lib/services/caseflow/CaseFlowService.js +82 -14
- package/lib/services/platform_services.d.ts +4 -4
- package/package.json +9 -10
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
3
3
|
import { DcmtTypeListCacheService, SDK_Globals, DataColumnTypes, MetadataDataDomains, RelationCacheService, RelationTypes, UserListCacheService, LayoutModes } from "@topconsultnpm/sdk-ts";
|
|
4
4
|
import { genUniqueId, IconFolder, IconBackhandIndexPointingRight, IconCircleInfo, getDcmtCicoStatus, IconChevronDown, IconChevronRight, SDKUI_Localizator, buildDcmtDisplayName, SDKUI_Globals, searchResultToMetadataValues, getColumnFormatInfo, formatDateTimeByMetadataFormat, formatNumberByMetadataFormat } from '../../../helper';
|
|
5
5
|
import ShowAlert from '../../base/TMAlert';
|
|
@@ -7,12 +7,13 @@ import TMToppyMessage from '../../../helper/TMToppyMessage';
|
|
|
7
7
|
import { TMColors } from '../../../utils/theme';
|
|
8
8
|
import { StyledDivHorizontal, StyledBadge } from '../../base/Styled';
|
|
9
9
|
import TMTreeView from '../../base/TMTreeView';
|
|
10
|
-
import
|
|
10
|
+
import TMWaitPanelManager from '../../base/waitPanel/TMWaitPanelManager';
|
|
11
11
|
import TMDataListItemViewer from '../../viewers/TMDataListItemViewer';
|
|
12
12
|
import TMDcmtIcon from './TMDcmtIcon';
|
|
13
13
|
import TMDataUserIdItemViewer from '../../viewers/TMDataUserIdItemViewer';
|
|
14
14
|
import TMTooltip from '../../base/TMTooltip';
|
|
15
15
|
import TMSpinner from '../../base/TMSpinner';
|
|
16
|
+
import { applyRelationTreeState, collectRelationTreeState, findRelationNodeByPath } from './relationTreeState';
|
|
16
17
|
/**
|
|
17
18
|
* Check if document type has detail relations
|
|
18
19
|
*/
|
|
@@ -65,10 +66,24 @@ export const getDisplayValueByColumn = (col, value) => {
|
|
|
65
66
|
return formatNumberByMetadataFormat(value, format, formatCulture);
|
|
66
67
|
return value;
|
|
67
68
|
};
|
|
69
|
+
/**
|
|
70
|
+
* Attende la chiamata, ma smette di aspettarla (restituendo undefined) appena l'utente interrompe.
|
|
71
|
+
* Serve perché il SearchEngine dell'SDK non accetta un AbortSignal: senza, "Interrompi" aspetterebbe la fine della chiamata in corso.
|
|
72
|
+
*/
|
|
73
|
+
const untilAborted = (promise, signal) => {
|
|
74
|
+
if (!signal)
|
|
75
|
+
return promise;
|
|
76
|
+
if (signal.aborted)
|
|
77
|
+
return Promise.resolve(undefined);
|
|
78
|
+
const aborted = new Promise(resolve => signal.addEventListener('abort', () => resolve(undefined), { once: true }));
|
|
79
|
+
// Vince chi termina prima: la risposta del server oppure l'interruzione (la chiamata prosegue in background e viene ignorata)
|
|
80
|
+
return Promise.race([promise, aborted]);
|
|
81
|
+
};
|
|
68
82
|
/**
|
|
69
83
|
* Convert SearchResultDescriptor to structured data source with metadata
|
|
70
|
-
*
|
|
71
|
-
*
|
|
84
|
+
* I valori dei documenti sono già presenti nelle righe restituite dalla ricerca:
|
|
85
|
+
* qui vengono solo abbinati ai rispettivi MetadataDescriptor (perm,
|
|
86
|
+
* isSpecialSearchOutput, ...), risolti una sola volta per tipo documento.
|
|
72
87
|
*/
|
|
73
88
|
export const searchResultToDataSource = async (searchResult, hideSysMetadata, signal) => {
|
|
74
89
|
const rows = searchResult?.dtdResult?.rows ?? [];
|
|
@@ -77,27 +92,28 @@ export const searchResultToDataSource = async (searchResult, hideSysMetadata, si
|
|
|
77
92
|
const columns = searchResult?.dtdResult?.columns ?? [];
|
|
78
93
|
const tidColIndex = columns.findIndex(col => col.caption?.toUpperCase() === 'TID');
|
|
79
94
|
const didColIndex = columns.findIndex(col => col.caption?.toUpperCase() === 'DID');
|
|
95
|
+
const firstRow = rows[0] ?? [];
|
|
96
|
+
const tid = tidColIndex >= 0 && firstRow.length > 0 ? Number(firstRow[tidColIndex]) : searchResult?.fromTID;
|
|
97
|
+
const firstDid = didColIndex >= 0 && firstRow.length > 0 ? Number(firstRow[didColIndex]) : undefined;
|
|
98
|
+
const dtdWithMetadata = await DcmtTypeListCacheService.GetWithNotGrantedAsync(tid, firstDid, searchResult);
|
|
99
|
+
const mdList = dtdWithMetadata?.metadata ?? [];
|
|
100
|
+
// Posizione di ogni MID nelle colonne del risultato: identica per tutte le righe,
|
|
101
|
+
// quindi calcolata una volta sola invece di una find() per metadato per riga.
|
|
102
|
+
const mids = columns.map(col => Number(col.extendedProperties?.["MID"] ?? "0"));
|
|
103
|
+
const columnByMid = new Map();
|
|
104
|
+
columns.forEach((col, i) => { if (!columnByMid.has(mids[i]))
|
|
105
|
+
columnByMid.set(mids[i], col); });
|
|
106
|
+
const dtdResult = searchResult?.dtdResult;
|
|
80
107
|
for (let index = 0; index < rows.length; index++) {
|
|
81
108
|
// Interruzione richiesta dall'utente: restituisce i documenti già elaborati
|
|
82
109
|
if (signal?.aborted)
|
|
83
110
|
break;
|
|
84
111
|
const row = rows[index];
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const did = didColIndex >= 0 ? Number(row[didColIndex]) : undefined;
|
|
88
|
-
// Fetch complete metadata for this specific document (like getFilteredMetadata does)
|
|
89
|
-
const metadata = await SDK_Globals.tmSession?.NewSearchEngine().GetMetadataAsync(tid, did, true);
|
|
90
|
-
if (metadata) {
|
|
91
|
-
// Get full DTD with metadata for this document
|
|
92
|
-
const dtdResult = metadata.dtdResult;
|
|
93
|
-
const metadataRows = dtdResult?.rows?.[0] ?? [];
|
|
94
|
-
const mids = metadata.selectMIDs;
|
|
95
|
-
const dtdWithMetadata = await DcmtTypeListCacheService.GetWithNotGrantedAsync(tid, did, metadata);
|
|
96
|
-
const mdList = dtdWithMetadata?.metadata ?? [];
|
|
112
|
+
const item = { rowIndex: index };
|
|
113
|
+
if (mdList.length > 0) {
|
|
97
114
|
// Convert to MetadataValueDescriptorEx array
|
|
98
|
-
const metadataList = searchResultToMetadataValues(tid, dtdResult,
|
|
115
|
+
const metadataList = searchResultToMetadataValues(tid, dtdResult, row, mids, mdList, LayoutModes.Update);
|
|
99
116
|
// Convert to DcmtMetadataMap format (like getFilteredMetadata does)
|
|
100
|
-
const item = { rowIndex: index };
|
|
101
117
|
for (const mvd of metadataList) {
|
|
102
118
|
if (hideSysMetadata && mvd.mid !== undefined && mvd.mid < 100)
|
|
103
119
|
continue;
|
|
@@ -106,23 +122,18 @@ export const searchResultToDataSource = async (searchResult, hideSysMetadata, si
|
|
|
106
122
|
: (mvd.md?.name ?? '');
|
|
107
123
|
if (key) {
|
|
108
124
|
// Find the corresponding column by MID to format the value
|
|
109
|
-
const column = dtdResult?.columns?.find(c => Number(c.extendedProperties?.["MID"] ?? "0") === mvd.mid);
|
|
110
125
|
item[key] = {
|
|
111
126
|
md: mvd.md,
|
|
112
|
-
value: getDisplayValueByColumn(
|
|
127
|
+
value: getDisplayValueByColumn(columnByMid.get(mvd.mid ?? 0), mvd.value)
|
|
113
128
|
};
|
|
114
129
|
}
|
|
115
130
|
}
|
|
116
|
-
output.push(item);
|
|
117
131
|
}
|
|
118
132
|
else {
|
|
119
|
-
// Fallback:
|
|
120
|
-
const item = { rowIndex: index };
|
|
121
|
-
const fallbackTid = searchResult?.fromTID;
|
|
122
|
-
const dtd = await DcmtTypeListCacheService.GetWithNotGrantedAsync(fallbackTid, undefined, searchResult);
|
|
133
|
+
// Fallback: il DTD non è risolvibile, usa le colonne grezze del risultato
|
|
123
134
|
for (let i = 0; i < row.length; i++) {
|
|
124
|
-
const column =
|
|
125
|
-
const mid =
|
|
135
|
+
const column = columns[i];
|
|
136
|
+
const mid = mids[i];
|
|
126
137
|
if (hideSysMetadata && mid < 100)
|
|
127
138
|
continue;
|
|
128
139
|
let key = column?.caption ?? '';
|
|
@@ -134,20 +145,18 @@ export const searchResultToDataSource = async (searchResult, hideSysMetadata, si
|
|
|
134
145
|
key = `${key}_${mid}`;
|
|
135
146
|
}
|
|
136
147
|
}
|
|
137
|
-
const value = row[i];
|
|
138
|
-
const md = dtd?.metadata?.find(o => o.id == mid);
|
|
139
148
|
item[key] = {
|
|
140
|
-
md:
|
|
141
|
-
value: getDisplayValueByColumn(column,
|
|
149
|
+
md: undefined,
|
|
150
|
+
value: getDisplayValueByColumn(column, row[i])
|
|
142
151
|
};
|
|
143
152
|
}
|
|
144
|
-
output.push(item);
|
|
145
153
|
}
|
|
154
|
+
output.push(item);
|
|
146
155
|
}
|
|
147
156
|
return output;
|
|
148
157
|
};
|
|
149
158
|
export const DEFAULT_RELATION_EXPAND_LEVEL = 4; // Default maximum depth level for expansion (can be adjusted)
|
|
150
|
-
const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndicator = true, allowShowZeroDcmts = true, initialShowZeroDcmts = false, allowedTIDs, allowMultipleSelection = false, focusedItem, selectedItems, onFocusedItemChanged, onSelectedItemsChanged, onAllItemsChanged, onDocumentDoubleClick, customItemRender, customDocumentStyle, customMainContainerContent, customDocumentContent, showMetadataNames = false, maxDepthLevel = 2, invertMasterNavigation = true, additionalStaticItems, showMainDocument = true, labelMainContainer, onNoRelationsFound, onItemContextMenu,
|
|
159
|
+
const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndicator = true, allowShowZeroDcmts = true, initialShowZeroDcmts = false, allowedTIDs, allowMultipleSelection = false, focusedItem, selectedItems, onFocusedItemChanged, onSelectedItemsChanged, onAllItemsChanged, onDocumentDoubleClick, customItemRender, customDocumentStyle, customMainContainerContent, customDocumentContent, showMetadataNames = false, maxDepthLevel = 2, invertMasterNavigation = true, additionalStaticItems, showMainDocument = true, labelMainContainer, onNoRelationsFound, onItemContextMenu, showExpandAllButton = false, defaultExpandAll = false, onLoadingStateChanged, onStopCalculationReady, onCalculationStopped, initialTreeState, onTreeStateChange }) => {
|
|
151
160
|
// State
|
|
152
161
|
const [dcmtTypes, setDcmtTypes] = useState([]);
|
|
153
162
|
const [treeData, setTreeData] = useState([]);
|
|
@@ -156,21 +165,21 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
|
|
|
156
165
|
const getInitialExpandLevel = () => SDKUI_Globals.userSettings?.searchSettings?.relationExpandLevel ?? DEFAULT_RELATION_EXPAND_LEVEL;
|
|
157
166
|
const [expandLevel, setExpandLevel] = useState(getInitialExpandLevel);
|
|
158
167
|
const [isAllCollapsed, setIsAllCollapsed] = useState(false);
|
|
159
|
-
const initialExpandAllAppliedRef =
|
|
160
|
-
|
|
161
|
-
const [
|
|
162
|
-
const
|
|
163
|
-
const [waitPanelMaxValuePrimary, setWaitPanelMaxValuePrimary] = useState(0);
|
|
168
|
+
const initialExpandAllAppliedRef = useRef(false);
|
|
169
|
+
// Calcolo iniziale: il pannello e' imperativo, qui resta solo il flag per il render.
|
|
170
|
+
const [isInitialLoading, setIsInitialLoading] = useState(false);
|
|
171
|
+
const loadPanelRef = useRef(undefined);
|
|
164
172
|
const [abortController] = useState(new AbortController());
|
|
165
173
|
// True quando l'utente ha interrotto il calcolo delle correlazioni (pulsante "Interrompi")
|
|
166
174
|
const [loadStoppedByUser, setLoadStoppedByUser] = useState(false);
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
const [
|
|
170
|
-
|
|
171
|
-
const
|
|
175
|
+
// Espansione di un nodo: il pannello e' imperativo, qui resta solo il flag per il render.
|
|
176
|
+
// qui resta solo il flag che serve al render e alla notifica al padre.
|
|
177
|
+
const [isExpansionLoading, setIsExpansionLoading] = useState(false);
|
|
178
|
+
// AbortController dell'espansione in corso, accessibile da callback stabili
|
|
179
|
+
const expansionAbortControllerRef = useRef(undefined);
|
|
180
|
+
const expansionPanelRef = useRef(undefined);
|
|
172
181
|
// Ref to track last loaded input to prevent unnecessary reloads
|
|
173
|
-
const lastLoadedInputRef =
|
|
182
|
+
const lastLoadedInputRef = useRef('');
|
|
174
183
|
// State to track loaded input key - triggers re-render for focus selection
|
|
175
184
|
const [loadedInputKey, setLoadedInputKey] = React.useState('');
|
|
176
185
|
// State to track how many GetMetadataAsync calls failed
|
|
@@ -178,9 +187,17 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
|
|
|
178
187
|
// State to track total master documents found (used when isForMaster is true)
|
|
179
188
|
const [totalMasterDocuments, setTotalMasterDocuments] = React.useState(0);
|
|
180
189
|
// Ref to track if user has manually expanded/collapsed static items
|
|
181
|
-
const userInteractedWithStaticItemsRef =
|
|
190
|
+
const userInteractedWithStaticItemsRef = useRef(false);
|
|
182
191
|
// Ref to track the last inputKey for which we set the focused item
|
|
183
|
-
const lastFocusedInputRef =
|
|
192
|
+
const lastFocusedInputRef = useRef('');
|
|
193
|
+
// Catturato al mount (il padre lo aggiorna di continuo) e azzerato dopo l'uso,
|
|
194
|
+
// così vale solo per il primo caricamento
|
|
195
|
+
const stateToRestoreRef = useRef(initialTreeState);
|
|
196
|
+
// Parte dallo stato ricevuto: il padre azzera il fuoco prima di rimontare, quindi
|
|
197
|
+
// la prima notifica dopo il rimontaggio lo perderebbe
|
|
198
|
+
const lastReportedStateRef = useRef(initialTreeState);
|
|
199
|
+
// Percorso su cui rimettere il fuoco dopo un ripristino
|
|
200
|
+
const pendingFocusPathRef = useRef(undefined);
|
|
184
201
|
// State for all users (used for checkout status display)
|
|
185
202
|
const [allUsers, setAllUsers] = useState([]);
|
|
186
203
|
// Load all users for checkout status resolution
|
|
@@ -193,20 +210,19 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
|
|
|
193
210
|
}, []);
|
|
194
211
|
// Notify parent when loading state changes
|
|
195
212
|
useEffect(() => {
|
|
196
|
-
const isLoading =
|
|
213
|
+
const isLoading = isInitialLoading || isExpansionLoading;
|
|
197
214
|
onLoadingStateChanged?.(isLoading);
|
|
198
|
-
}, [
|
|
199
|
-
//
|
|
200
|
-
|
|
201
|
-
useEffect(() => {
|
|
202
|
-
|
|
203
|
-
|
|
215
|
+
}, [isInitialLoading, isExpansionLoading, onLoadingStateChanged]);
|
|
216
|
+
// I pannelli vivono fuori dall'albero React: se il viewer viene smontato mentre un
|
|
217
|
+
// caricamento e' in corso vanno chiusi a mano, altrimenti resterebbero a schermo.
|
|
218
|
+
useEffect(() => () => {
|
|
219
|
+
loadPanelRef.current?.close();
|
|
220
|
+
expansionPanelRef.current?.close();
|
|
221
|
+
}, []);
|
|
204
222
|
/**
|
|
205
223
|
* Interrompe il calcolo delle correlazioni (iniziale e/o espansione in corso):
|
|
206
224
|
* non vengono eseguite ulteriori chiamate GetMetadata e vengono mantenute
|
|
207
225
|
* solo le correlazioni già calcolate.
|
|
208
|
-
* È sincrona: può essere invocata immediatamente prima di smontare il viewer
|
|
209
|
-
* (es. pulsante "Indietro"), quando un effect non farebbe in tempo.
|
|
210
226
|
*/
|
|
211
227
|
const stopCalculation = useCallback(() => {
|
|
212
228
|
if (!abortController.signal.aborted)
|
|
@@ -264,7 +280,7 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
|
|
|
264
280
|
const searchEngine = SDK_Globals.tmSession?.NewSearchEngine();
|
|
265
281
|
if (!searchEngine)
|
|
266
282
|
return items;
|
|
267
|
-
const results = await searchEngine.GetAllDetailDcmtsAsync(mTID, mDID) ?? [];
|
|
283
|
+
const results = await untilAborted(searchEngine.GetAllDetailDcmtsAsync(mTID, mDID), signal) ?? [];
|
|
268
284
|
for (const searchResult of results) {
|
|
269
285
|
// Interruzione richiesta dall'utente: restituisce le correlazioni già calcolate
|
|
270
286
|
if (signal?.aborted)
|
|
@@ -354,7 +370,7 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
|
|
|
354
370
|
const searchEngine = SDK_Globals.tmSession?.NewSearchEngine();
|
|
355
371
|
if (!searchEngine)
|
|
356
372
|
return items;
|
|
357
|
-
const results = await searchEngine.GetAllMasterDcmtsAsync(dTID, dDID) ?? [];
|
|
373
|
+
const results = await untilAborted(searchEngine.GetAllMasterDcmtsAsync(dTID, dDID), signal) ?? [];
|
|
358
374
|
for (const searchResult of results) {
|
|
359
375
|
// Interruzione richiesta dall'utente: restituisce le correlazioni già calcolate
|
|
360
376
|
if (signal?.aborted)
|
|
@@ -478,6 +494,26 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
|
|
|
478
494
|
return { ...node, hidden: shouldHide, items: updatedItems };
|
|
479
495
|
});
|
|
480
496
|
}, [showZeroDcmts]);
|
|
497
|
+
/** Messaggio informativo in testa se tutti i figli sono nascosti dal filtro; senza figli il nodo resta vuoto (nessuna freccina). */
|
|
498
|
+
const applyZeroFilterInfoMessage = useCallback((nodeKey, items) => {
|
|
499
|
+
const cleaned = items.filter(child => !(child.isInfoMessage && child.key?.startsWith('__info__')));
|
|
500
|
+
if (showZeroDcmts)
|
|
501
|
+
return cleaned;
|
|
502
|
+
const realChildren = cleaned.filter(child => !child.isInfoMessage);
|
|
503
|
+
if (realChildren.length === 0 || !realChildren.every(child => child.hidden))
|
|
504
|
+
return cleaned;
|
|
505
|
+
return [
|
|
506
|
+
{
|
|
507
|
+
key: `__info__${nodeKey}`,
|
|
508
|
+
name: SDKUI_Localizator.NoRelatedDocumentsToDisplay,
|
|
509
|
+
isContainer: false,
|
|
510
|
+
isDcmt: false,
|
|
511
|
+
isInfoMessage: true,
|
|
512
|
+
isExpandible: false
|
|
513
|
+
},
|
|
514
|
+
...cleaned // Keep hidden containers so they can be shown when toggling
|
|
515
|
+
];
|
|
516
|
+
}, [showZeroDcmts]);
|
|
481
517
|
/**
|
|
482
518
|
* Helper function to set up initial tree expansion state.
|
|
483
519
|
* Called after all data is loaded but before setTreeData.
|
|
@@ -565,10 +601,22 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
|
|
|
565
601
|
});
|
|
566
602
|
return result;
|
|
567
603
|
};
|
|
604
|
+
/**
|
|
605
|
+
* Carica i figli di un documento durante il ripristino. Usa i loader e non
|
|
606
|
+
* calculateItemsForNode, che accenderebbe il wait panel di espansione mentre
|
|
607
|
+
* è già attivo quello del caricamento principale.
|
|
608
|
+
*/
|
|
609
|
+
const loadChildrenForRestore = async (node) => {
|
|
610
|
+
if (!node.tid || !node.did)
|
|
611
|
+
return [];
|
|
612
|
+
if (isForMaster && invertMasterNavigation)
|
|
613
|
+
return await getMasterDcmtsAsync(node.tid, node.did, 1, abortController.signal);
|
|
614
|
+
return await getDetailDcmtsAsync(node.tid, node.did, 1, abortController.signal);
|
|
615
|
+
};
|
|
568
616
|
/**
|
|
569
617
|
* Main data loading function
|
|
570
618
|
*/
|
|
571
|
-
const loadData = useCallback(async () => {
|
|
619
|
+
const loadData = useCallback(async (panel) => {
|
|
572
620
|
if (!inputDcmts || inputDcmts.length === 0 || dcmtTypes.length === 0) {
|
|
573
621
|
setTreeData([]);
|
|
574
622
|
return;
|
|
@@ -583,14 +631,14 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
|
|
|
583
631
|
break;
|
|
584
632
|
}
|
|
585
633
|
// Update wait panel progress
|
|
586
|
-
|
|
634
|
+
panel.setSubtitle(`Caricamento correlazione ${processedCount + 1} di ${inputDcmts.length}`);
|
|
587
635
|
const searchEngine = SDK_Globals.tmSession?.NewSearchEngine();
|
|
588
636
|
if (!searchEngine)
|
|
589
637
|
continue;
|
|
590
638
|
// Load document metadata
|
|
591
639
|
let result;
|
|
592
640
|
try {
|
|
593
|
-
result = await searchEngine.GetMetadataAsync(dcmt.TID, dcmt.DID, false);
|
|
641
|
+
result = await untilAborted(searchEngine.GetMetadataAsync(dcmt.TID, dcmt.DID, false), abortController.signal);
|
|
594
642
|
}
|
|
595
643
|
catch (metadataError) {
|
|
596
644
|
console.warn('[TMRelationViewer] GetMetadataAsync failed for TID:', dcmt.TID, 'DID:', dcmt.DID, metadataError);
|
|
@@ -603,9 +651,12 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
|
|
|
603
651
|
});
|
|
604
652
|
// Skip this document and continue with the next one
|
|
605
653
|
processedCount++;
|
|
606
|
-
|
|
654
|
+
await panel.step();
|
|
607
655
|
continue;
|
|
608
656
|
}
|
|
657
|
+
// Interrotto durante la chiamata: esce con le correlazioni gia' calcolate
|
|
658
|
+
if (abortController.signal.aborted)
|
|
659
|
+
break;
|
|
609
660
|
const dtd = await DcmtTypeListCacheService.GetAsync(dcmt.TID);
|
|
610
661
|
// Parse document values
|
|
611
662
|
const docValues = await searchResultToDataSource(result, undefined, abortController.signal);
|
|
@@ -638,6 +689,11 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
|
|
|
638
689
|
}
|
|
639
690
|
}
|
|
640
691
|
}
|
|
692
|
+
// Interruzione richiesta dall'utente: il documento corrente non è stato
|
|
693
|
+
// caricato per intero, quindi non viene aggiunto all'albero. Il parziale
|
|
694
|
+
// resta così a granularità di documento (es. 3 completi su 10).
|
|
695
|
+
if (abortController.signal.aborted)
|
|
696
|
+
break;
|
|
641
697
|
// Aggiungi i master come root, raggruppando per TID e relationId
|
|
642
698
|
for (const masterContainer of masterDocs) {
|
|
643
699
|
const existingContainer = tree.find(c => c.tid === masterContainer.tid && c.relationId === masterContainer.relationId && c.isContainer);
|
|
@@ -666,6 +722,9 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
|
|
|
666
722
|
const relatedDocs = isForMaster
|
|
667
723
|
? await getMasterDcmtsAsync(dcmt.TID, dcmt.DID, maxDepthLevel, abortController.signal)
|
|
668
724
|
: await getDetailDcmtsAsync(dcmt.TID, dcmt.DID, maxDepthLevel, abortController.signal);
|
|
725
|
+
// Interruzione richiesta dall'utente
|
|
726
|
+
if (abortController.signal.aborted)
|
|
727
|
+
break;
|
|
669
728
|
// Se showMainDocument è false, mostra i container dei dettagli direttamente sotto il container principale
|
|
670
729
|
if (!showMainDocument) {
|
|
671
730
|
// Check if a container for this TID already exists in the tree
|
|
@@ -773,7 +832,8 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
|
|
|
773
832
|
}
|
|
774
833
|
// Update progress counter
|
|
775
834
|
processedCount++;
|
|
776
|
-
|
|
835
|
+
// step() attende anche la conferma di interruzione: col dialogo aperto il ciclo resta fermo
|
|
836
|
+
await panel.step();
|
|
777
837
|
}
|
|
778
838
|
/**
|
|
779
839
|
* Helper function to check if a node has any actual correlated documents
|
|
@@ -817,7 +877,7 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
|
|
|
817
877
|
const wasStopped = abortController.signal.aborted;
|
|
818
878
|
if (wasStopped) {
|
|
819
879
|
setLoadStoppedByUser(true);
|
|
820
|
-
onCalculationStopped?.();
|
|
880
|
+
onCalculationStopped?.(processedCount, inputDcmts.length);
|
|
821
881
|
}
|
|
822
882
|
// If no relations found, notify parent
|
|
823
883
|
if (!wasStopped && hasNoRelations && onNoRelationsFound) {
|
|
@@ -843,12 +903,20 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
|
|
|
843
903
|
}
|
|
844
904
|
// FIRST setup initial expansion state (root container expanded, first document expanded with focus, first correlation folder expanded)
|
|
845
905
|
// This must run BEFORE updateHiddenProperty so that infoMessage logic sees expanded=true
|
|
846
|
-
|
|
906
|
+
let expandedTree = setupInitialTreeExpansion(tree);
|
|
907
|
+
// Stato precedente da ripristinare (rimontaggio dopo un'operazione): si riapplica
|
|
908
|
+
// sopra l'espansione iniziale, che resta necessaria perché marca isRoot.
|
|
909
|
+
const stateToRestore = stateToRestoreRef.current;
|
|
910
|
+
stateToRestoreRef.current = undefined;
|
|
911
|
+
if (stateToRestore && stateToRestore.inputKey === getInputKey()) {
|
|
912
|
+
expandedTree = await applyRelationTreeState(expandedTree, stateToRestore, loadChildrenForRestore, () => abortController.signal.aborted);
|
|
913
|
+
pendingFocusPathRef.current = stateToRestore.focusedPath;
|
|
914
|
+
}
|
|
847
915
|
// THEN apply hidden property transformations (creates new objects, adds infoMessage where needed)
|
|
848
916
|
// Now it will correctly detect expanded nodes and add infoMessage if all children are hidden
|
|
849
917
|
const processedTree = updateHiddenProperty(expandedTree);
|
|
850
918
|
setTreeData(processedTree);
|
|
851
|
-
}, [inputDcmts, dcmtTypes, maxDepthLevel, isForMaster, invertMasterNavigation, getDetailDcmtsAsync, getMasterDcmtsAsync, abortController, updateHiddenProperty, showMainDocument, labelMainContainer, onNoRelationsFound, onCalculationStopped]);
|
|
919
|
+
}, [inputDcmts, dcmtTypes, maxDepthLevel, isForMaster, invertMasterNavigation, getDetailDcmtsAsync, getMasterDcmtsAsync, abortController, updateHiddenProperty, showMainDocument, labelMainContainer, onNoRelationsFound, onCalculationStopped, getInputKey]);
|
|
852
920
|
/**
|
|
853
921
|
* Merge main tree data with additional static items
|
|
854
922
|
*/
|
|
@@ -863,6 +931,18 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
|
|
|
863
931
|
// and may contain custom properties that would be lost by the spread operator
|
|
864
932
|
return [...treeData, ...itemsToMerge];
|
|
865
933
|
}, [treeData, additionalStaticItems, staticItemsState]);
|
|
934
|
+
/** Notifica al padre lo stato dell'albero, così che possa restituirlo dopo un refresh. */
|
|
935
|
+
useEffect(() => {
|
|
936
|
+
if (!onTreeStateChange)
|
|
937
|
+
return;
|
|
938
|
+
// Ad albero vuoto (caricamento in corso) si sovrascriverebbe lo stato da ripristinare
|
|
939
|
+
if (treeData.length === 0)
|
|
940
|
+
return;
|
|
941
|
+
// Il padre azzera il fuoco all'avvio di un refresh: si conserva l'ultimo noto
|
|
942
|
+
const state = collectRelationTreeState(treeData, getInputKey(), focusedItem?.key, lastReportedStateRef.current?.focusedPath);
|
|
943
|
+
lastReportedStateRef.current = state;
|
|
944
|
+
onTreeStateChange(state);
|
|
945
|
+
}, [treeData, focusedItem, getInputKey, onTreeStateChange]);
|
|
866
946
|
/**
|
|
867
947
|
* Notify parent with a flattened list of all items whenever the merged tree data changes.
|
|
868
948
|
*/
|
|
@@ -903,20 +983,28 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
|
|
|
903
983
|
lastLoadedInputRef.current = currentKey;
|
|
904
984
|
// Reset interaction flag when loading new data
|
|
905
985
|
userInteractedWithStaticItemsRef.current = false;
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
986
|
+
setIsInitialLoading(true);
|
|
987
|
+
const panel = TMWaitPanelManager.show({
|
|
988
|
+
title: isForMaster ? SDKUI_Localizator.LoadingMasterDocuments : SDKUI_Localizator.LoadingDetailDocuments,
|
|
989
|
+
max: inputDcmts.length,
|
|
990
|
+
showCounters: false,
|
|
991
|
+
cancelable: true,
|
|
992
|
+
onAbort: () => abortController.abort(),
|
|
993
|
+
});
|
|
994
|
+
loadPanelRef.current = panel;
|
|
909
995
|
// Call loadData and use .then() instead of await to allow React to render
|
|
910
|
-
loadData().then(() => {
|
|
996
|
+
loadData(panel).then(() => {
|
|
911
997
|
// Mark as loaded AFTER data is ready - state update triggers re-render for focus selection
|
|
912
998
|
lastLoadedInputRef.current = currentKey;
|
|
913
999
|
setLoadedInputKey(currentKey);
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
1000
|
+
}).finally(() => {
|
|
1001
|
+
// Il pannello resta aperto per tutto loadData, ripristini inclusi
|
|
1002
|
+
panel.close();
|
|
1003
|
+
if (loadPanelRef.current === panel)
|
|
1004
|
+
loadPanelRef.current = undefined;
|
|
1005
|
+
setIsInitialLoading(false);
|
|
918
1006
|
});
|
|
919
|
-
}, [inputDcmts, dcmtTypes, maxDepthLevel, getInputKey, loadData, treeData.length]);
|
|
1007
|
+
}, [inputDcmts, dcmtTypes, maxDepthLevel, isForMaster, abortController, getInputKey, loadData, treeData.length]);
|
|
920
1008
|
/**
|
|
921
1009
|
* Update tree when showZeroDcmts changes
|
|
922
1010
|
* Updates the visualization without re-fetching data (isLoaded stays true)
|
|
@@ -942,6 +1030,18 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
|
|
|
942
1030
|
if (lastFocusedInputRef.current === currentInputKey) {
|
|
943
1031
|
return;
|
|
944
1032
|
}
|
|
1033
|
+
// Ripristino dopo un refresh. Se il nodo non esiste più (es. documento cancellato
|
|
1034
|
+
// fisicamente) si prosegue con il fuoco predefinito sul root.
|
|
1035
|
+
const focusPathToRestore = pendingFocusPathRef.current;
|
|
1036
|
+
pendingFocusPathRef.current = undefined;
|
|
1037
|
+
if (focusPathToRestore) {
|
|
1038
|
+
const nodeToFocus = findRelationNodeByPath(treeData, focusPathToRestore);
|
|
1039
|
+
if (nodeToFocus) {
|
|
1040
|
+
onFocusedItemChanged(nodeToFocus);
|
|
1041
|
+
lastFocusedInputRef.current = currentInputKey;
|
|
1042
|
+
return;
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
945
1045
|
// Helper function to recursively find the first document with isRoot=true
|
|
946
1046
|
const findFirstRootDocument = (items) => {
|
|
947
1047
|
for (const item of items) {
|
|
@@ -1019,29 +1119,7 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
|
|
|
1019
1119
|
if (node.isLoaded && node.items) {
|
|
1020
1120
|
// Apply current show/hide logic based on showZeroDcmts
|
|
1021
1121
|
const updatedItems = updateHiddenProperty(node.items);
|
|
1022
|
-
|
|
1023
|
-
if (node.isContainer) {
|
|
1024
|
-
// Remove any existing info messages
|
|
1025
|
-
const filteredItems = updatedItems.filter(child => !(child.isInfoMessage && child.key?.startsWith('__info__')));
|
|
1026
|
-
// Check if all real children are hidden
|
|
1027
|
-
const realChildren = filteredItems.filter(child => !child.isInfoMessage);
|
|
1028
|
-
const allChildrenHidden = realChildren.length > 0 && realChildren.every(child => child.hidden);
|
|
1029
|
-
// If showZeroDcmts is false and all items are hidden, show info message + keep hidden containers
|
|
1030
|
-
if (!showZeroDcmts && allChildrenHidden) {
|
|
1031
|
-
return [
|
|
1032
|
-
{
|
|
1033
|
-
key: `__info__${node.key}`,
|
|
1034
|
-
name: SDKUI_Localizator.NoRelatedDocumentsToDisplay,
|
|
1035
|
-
isContainer: false,
|
|
1036
|
-
isDcmt: false,
|
|
1037
|
-
isInfoMessage: true,
|
|
1038
|
-
isExpandible: false
|
|
1039
|
-
},
|
|
1040
|
-
...filteredItems // Keep hidden containers so they can be shown when toggling
|
|
1041
|
-
];
|
|
1042
|
-
}
|
|
1043
|
-
}
|
|
1044
|
-
return updatedItems;
|
|
1122
|
+
return applyZeroFilterInfoMessage(node.key, updatedItems);
|
|
1045
1123
|
}
|
|
1046
1124
|
// ============================================
|
|
1047
1125
|
// CONTAINER: Show items immediately (no API calls)
|
|
@@ -1055,21 +1133,7 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
|
|
|
1055
1133
|
if (!node.items)
|
|
1056
1134
|
return [];
|
|
1057
1135
|
const updatedItems = updateHiddenProperty(node.items);
|
|
1058
|
-
|
|
1059
|
-
if (!showZeroDcmts && updatedItems.length > 0 && updatedItems.every(item => item.hidden)) {
|
|
1060
|
-
return [
|
|
1061
|
-
{
|
|
1062
|
-
key: `__info__${node.key}`,
|
|
1063
|
-
name: SDKUI_Localizator.NoRelatedDocumentsToDisplay,
|
|
1064
|
-
isContainer: false,
|
|
1065
|
-
isDcmt: false,
|
|
1066
|
-
isInfoMessage: true,
|
|
1067
|
-
isExpandible: false
|
|
1068
|
-
},
|
|
1069
|
-
...updatedItems // Keep hidden containers so they can be shown when toggling
|
|
1070
|
-
];
|
|
1071
|
-
}
|
|
1072
|
-
return updatedItems;
|
|
1136
|
+
return applyZeroFilterInfoMessage(node.key, updatedItems);
|
|
1073
1137
|
}
|
|
1074
1138
|
// ============================================
|
|
1075
1139
|
// DOCUMENT: Load its children (relation containers) lazily
|
|
@@ -1077,11 +1141,19 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
|
|
|
1077
1141
|
// ============================================
|
|
1078
1142
|
if (node.isDcmt && node.tid && node.did) {
|
|
1079
1143
|
const newAbortController = new AbortController();
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1144
|
+
expansionAbortControllerRef.current = newAbortController;
|
|
1145
|
+
setIsExpansionLoading(true);
|
|
1146
|
+
// Un solo nodo: niente contatori ne' stima dei tempi, solo l'attesa annullabile.
|
|
1147
|
+
const panel = TMWaitPanelManager.show({
|
|
1148
|
+
title: isForMaster ? SDKUI_Localizator.LoadingMasterDocuments : SDKUI_Localizator.LoadingDetailDocuments,
|
|
1149
|
+
subtitle: 'Caricamento documenti correlati...',
|
|
1150
|
+
max: 1,
|
|
1151
|
+
showCounters: false,
|
|
1152
|
+
showEta: false,
|
|
1153
|
+
cancelable: true,
|
|
1154
|
+
onAbort: () => newAbortController.abort(),
|
|
1155
|
+
});
|
|
1156
|
+
expansionPanelRef.current = panel;
|
|
1085
1157
|
try {
|
|
1086
1158
|
// Check for abort
|
|
1087
1159
|
if (newAbortController.signal.aborted) {
|
|
@@ -1110,34 +1182,22 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
|
|
|
1110
1182
|
// - Relation type containers are always visible (even if empty)
|
|
1111
1183
|
// - Other containers with zero items can be hidden based on showZeroDcmts
|
|
1112
1184
|
const updatedItems = updateHiddenProperty(loadedItems);
|
|
1113
|
-
|
|
1114
|
-
if (!showZeroDcmts && updatedItems.length > 0 && updatedItems.every(item => item.hidden)) {
|
|
1115
|
-
return [
|
|
1116
|
-
{
|
|
1117
|
-
key: `__info__${node.key}`,
|
|
1118
|
-
name: SDKUI_Localizator.NoRelatedDocumentsToDisplay,
|
|
1119
|
-
isContainer: false,
|
|
1120
|
-
isDcmt: false,
|
|
1121
|
-
isInfoMessage: true,
|
|
1122
|
-
isExpandible: false
|
|
1123
|
-
},
|
|
1124
|
-
...updatedItems // Keep hidden containers so they can be shown when toggling
|
|
1125
|
-
];
|
|
1126
|
-
}
|
|
1127
|
-
return updatedItems;
|
|
1185
|
+
return applyZeroFilterInfoMessage(node.key, updatedItems);
|
|
1128
1186
|
}
|
|
1129
1187
|
catch (error) {
|
|
1130
1188
|
console.error('Error loading document relations:', error);
|
|
1131
1189
|
return [];
|
|
1132
1190
|
}
|
|
1133
1191
|
finally {
|
|
1134
|
-
|
|
1135
|
-
|
|
1192
|
+
panel.close();
|
|
1193
|
+
setIsExpansionLoading(false);
|
|
1194
|
+
expansionAbortControllerRef.current = undefined;
|
|
1195
|
+
expansionPanelRef.current = undefined;
|
|
1136
1196
|
}
|
|
1137
1197
|
}
|
|
1138
1198
|
// Default: return existing items
|
|
1139
1199
|
return node.items;
|
|
1140
|
-
}, [isForMaster, invertMasterNavigation, getDetailDcmtsAsync, getMasterDcmtsAsync, updateHiddenProperty, showZeroDcmts]);
|
|
1200
|
+
}, [isForMaster, invertMasterNavigation, getDetailDcmtsAsync, getMasterDcmtsAsync, updateHiddenProperty, applyZeroFilterInfoMessage, showZeroDcmts]);
|
|
1141
1201
|
/**
|
|
1142
1202
|
* Default item renderer with metadata display (adapted from TMMasterDetailDcmts.tsx)
|
|
1143
1203
|
*/
|
|
@@ -1170,8 +1230,7 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
|
|
|
1170
1230
|
gap: '10px',
|
|
1171
1231
|
height: '32px',
|
|
1172
1232
|
padding: '6px 0',
|
|
1173
|
-
opacity: item.isZero ? 0.
|
|
1174
|
-
transition: 'opacity 0.2s ease-in-out'
|
|
1233
|
+
opacity: item.isZero ? 0.4 : 1,
|
|
1175
1234
|
};
|
|
1176
1235
|
// Se è il container principale (root) e showMainDocument è false,
|
|
1177
1236
|
// non mostrare il badge perché conterrebbe il numero di sotto-container, non di documenti
|
|
@@ -1181,7 +1240,7 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
|
|
|
1181
1240
|
textOverflow: 'ellipsis',
|
|
1182
1241
|
fontSize: '1rem',
|
|
1183
1242
|
color: 'inherit'
|
|
1184
|
-
}, children: item.name }), shouldShowBadge && (_jsx(StyledBadge, {
|
|
1243
|
+
}, children: item.name }), shouldShowBadge && (_jsx(StyledBadge, { $backgroundColor: TMColors.info, children: item.items?.length ?? 0 }))] })] }));
|
|
1185
1244
|
// Se è il container principale (root) e abbiamo un custom renderer, usalo
|
|
1186
1245
|
const content = (item.isRoot && customMainContainerContent)
|
|
1187
1246
|
? customMainContainerContent(item, defaultContent)
|
|
@@ -1201,8 +1260,7 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
|
|
|
1201
1260
|
alignItems: 'center',
|
|
1202
1261
|
cursor: 'pointer',
|
|
1203
1262
|
userSelect: 'none',
|
|
1204
|
-
opacity: item.isZero ? 0.
|
|
1205
|
-
transition: 'opacity 0.2s ease-in-out',
|
|
1263
|
+
opacity: item.isZero ? 0.4 : 1,
|
|
1206
1264
|
textDecoration: isLogicallyDeleted ? 'line-through' : 'none'
|
|
1207
1265
|
};
|
|
1208
1266
|
const documentStyle = customDocumentStyle ? { ...defaultDocumentStyle, ...customDocumentStyle(item) } : defaultDocumentStyle;
|
|
@@ -1262,7 +1320,7 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
|
|
|
1262
1320
|
checkoutStatusIcon = checkoutStatus.icon;
|
|
1263
1321
|
}
|
|
1264
1322
|
}
|
|
1265
|
-
return (_jsxs("div", { onDoubleClick: handleDoubleClick, style: documentStyle, children: [item.did && item.tid && showCurrentDcmtIndicator && inputDcmts?.some(d => d.DID === item.did && d.TID === item.tid) ? _jsx(IconBackhandIndexPointingRight, { fontSize: 22, overflow: 'visible' }) : _jsx(_Fragment, {}), item.values && (_jsx(TMDcmtIcon, { tid: item.values?.TID?.value, did: item.values?.DID?.value, fileExtension: item.values?.FILEEXT?.value, fileCount: item.values?.FILECOUNT?.value, isLexProt: item.values?.IsLexProt?.value, isMail: item.values?.ISMAIL?.value, isShared: item.values?.ISSHARED?.value, isSigned: item.values?.ISSIGNED?.value, downloadMode: 'openInNewWindow'
|
|
1323
|
+
return (_jsxs("div", { onDoubleClick: handleDoubleClick, style: documentStyle, children: [item.did && item.tid && showCurrentDcmtIndicator && inputDcmts?.some(d => d.DID === item.did && d.TID === item.tid) ? _jsx(IconBackhandIndexPointingRight, { fontSize: 22, overflow: 'visible' }) : _jsx(_Fragment, {}), item.values && (_jsx(TMDcmtIcon, { tid: item.values?.TID?.value, did: item.values?.DID?.value, fileExtension: item.values?.FILEEXT?.value, fileCount: item.values?.FILECOUNT?.value, isLexProt: item.values?.IsLexProt?.value, isMail: item.values?.ISMAIL?.value, isShared: item.values?.ISSHARED?.value, isSigned: item.values?.ISSIGNED?.value, downloadMode: 'openInNewWindow' })), checkoutStatusIcon, metadataContent] }));
|
|
1266
1324
|
}, [onDocumentDoubleClick, showCurrentDcmtIndicator, inputDcmts, customMainContainerContent, customDocumentStyle, customDocumentContent, showMetadataNames, showMainDocument, allUsers]);
|
|
1267
1325
|
/**
|
|
1268
1326
|
* Wrapper renderer that handles custom rendering if provided
|
|
@@ -1376,15 +1434,18 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
|
|
|
1376
1434
|
}
|
|
1377
1435
|
const targetLevel = expandLevel;
|
|
1378
1436
|
const newAbortController = new AbortController();
|
|
1379
|
-
|
|
1437
|
+
expansionAbortControllerRef.current = newAbortController;
|
|
1438
|
+
// Aperto solo se servono chiamate API (vedi hasApiCalls): con i figli già in
|
|
1439
|
+
// memoria l'espansione è immediata e il pannello lampeggerebbe soltanto.
|
|
1440
|
+
let panel;
|
|
1380
1441
|
// CONTATORI DINAMICI:
|
|
1381
1442
|
// - totalNodes: cresce quando scopriamo nuovi nodi (evita che percentuale > 100%)
|
|
1382
1443
|
// - processedNodes: quanti nodi abbiamo già processato
|
|
1383
|
-
// -
|
|
1444
|
+
// - shownProgress: percentuale mostrata, che non torna mai indietro
|
|
1384
1445
|
let totalNodes = 0;
|
|
1385
1446
|
let processedNodes = 0;
|
|
1386
1447
|
let hasApiCalls = false;
|
|
1387
|
-
let
|
|
1448
|
+
let shownProgress = 0;
|
|
1388
1449
|
/**
|
|
1389
1450
|
* Conta i nodi espandibili in un array (solo primo livello, non ricorsivo).
|
|
1390
1451
|
* Usato per aggiornare totalNodes man mano che scopriamo nuovi figli.
|
|
@@ -1400,6 +1461,7 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
|
|
|
1400
1461
|
};
|
|
1401
1462
|
// Conta iniziale: solo i nodi al livello root
|
|
1402
1463
|
totalNodes = countNodesAtLevel([...workingTreeData, ...workingStaticData]);
|
|
1464
|
+
const initialTotalNodes = totalNodes;
|
|
1403
1465
|
/**
|
|
1404
1466
|
* FUNZIONE RICORSIVA DI ESPANSIONE
|
|
1405
1467
|
*
|
|
@@ -1453,9 +1515,17 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
|
|
|
1453
1515
|
if (!hasApiCalls) {
|
|
1454
1516
|
hasApiCalls = true;
|
|
1455
1517
|
TMSpinner.hide();
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1518
|
+
setIsExpansionLoading(true);
|
|
1519
|
+
// Avanzamento in centesimi: il totale cambia mentre si scoprono i figli, niente ETA.
|
|
1520
|
+
panel = TMWaitPanelManager.show({
|
|
1521
|
+
title: isForMaster ? SDKUI_Localizator.LoadingMasterDocuments : SDKUI_Localizator.LoadingDetailDocuments,
|
|
1522
|
+
max: 100,
|
|
1523
|
+
showCounters: false,
|
|
1524
|
+
showEta: false,
|
|
1525
|
+
cancelable: true,
|
|
1526
|
+
onAbort: () => newAbortController.abort(),
|
|
1527
|
+
});
|
|
1528
|
+
expansionPanelRef.current = panel;
|
|
1459
1529
|
}
|
|
1460
1530
|
try {
|
|
1461
1531
|
let loadedItems = [];
|
|
@@ -1470,7 +1540,8 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
|
|
|
1470
1540
|
loadedItems = await getDetailDcmtsAsync(node.tid, node.did, 1, newAbortController.signal);
|
|
1471
1541
|
}
|
|
1472
1542
|
// Applica le regole di visibilità (showZeroDcmts)
|
|
1473
|
-
|
|
1543
|
+
// Stesse regole dell'espansione manuale (messaggio informativo se tutto nascosto)
|
|
1544
|
+
expandedNode.items = applyZeroFilterInfoMessage(node.key, updateHiddenProperty(loadedItems));
|
|
1474
1545
|
expandedNode.isLoaded = true;
|
|
1475
1546
|
expandedNode.itemsCount = loadedItems.length;
|
|
1476
1547
|
// IMPORTANTE: Aggiungi i nuovi nodi scoperti al contatore totale
|
|
@@ -1497,14 +1568,15 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
|
|
|
1497
1568
|
}
|
|
1498
1569
|
// Aggiorna il progresso DOPO aver processato questo nodo
|
|
1499
1570
|
processedNodes++;
|
|
1500
|
-
//
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1571
|
+
// La barra non torna mai indietro: segue il valore reale quando sale, altrimenti avanza verso il 99%
|
|
1572
|
+
const realProgress = totalNodes > 0 ? (processedNodes / totalNodes) * 100 : 0;
|
|
1573
|
+
shownProgress = Math.min(99, realProgress > shownProgress
|
|
1574
|
+
? realProgress
|
|
1575
|
+
: shownProgress + (99 - shownProgress) * 0.01);
|
|
1576
|
+
panel?.setValue(shownProgress);
|
|
1577
|
+
// Seconda riga: spiega perché percentuale e conteggio non vanno di pari passo
|
|
1578
|
+
panel?.setSubtitle(SDKUI_Localizator.ExpansionProgress.replaceParams(processedNodes.toString(), totalNodes.toString())
|
|
1579
|
+
+ (totalNodes > initialTotalNodes ? `\n${SDKUI_Localizator.ExpansionProgressIsEstimated}` : ''));
|
|
1508
1580
|
// ═══════════════════════════════════════════════════════════════════
|
|
1509
1581
|
// PASSO 3: CHIAMATA RICORSIVA SUI FIGLI
|
|
1510
1582
|
// Questo è il punto chiave: i nuovi nodi appena caricati vengono
|
|
@@ -1532,10 +1604,12 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
|
|
|
1532
1604
|
finally {
|
|
1533
1605
|
// Cleanup: nascondi spinner e pannello di attesa
|
|
1534
1606
|
TMSpinner.hide();
|
|
1535
|
-
|
|
1536
|
-
|
|
1607
|
+
panel?.close();
|
|
1608
|
+
setIsExpansionLoading(false);
|
|
1609
|
+
expansionAbortControllerRef.current = undefined;
|
|
1610
|
+
expansionPanelRef.current = undefined;
|
|
1537
1611
|
}
|
|
1538
|
-
}, [expandLevel, treeData, staticItemsState, isForMaster, invertMasterNavigation, getDetailDcmtsAsync, getMasterDcmtsAsync, updateHiddenProperty]);
|
|
1612
|
+
}, [expandLevel, treeData, staticItemsState, isForMaster, invertMasterNavigation, getDetailDcmtsAsync, getMasterDcmtsAsync, updateHiddenProperty, applyZeroFilterInfoMessage]);
|
|
1539
1613
|
/**
|
|
1540
1614
|
* Apply defaultExpandAll once on initial tree load.
|
|
1541
1615
|
*/
|
|
@@ -1564,8 +1638,10 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
|
|
|
1564
1638
|
// Reset collapsed state when user manually expands a node
|
|
1565
1639
|
setIsAllCollapsed(false);
|
|
1566
1640
|
}, []);
|
|
1567
|
-
|
|
1568
|
-
|
|
1641
|
+
// Il pannello di attesa è montato dal manager fuori dall'albero React: qui resta
|
|
1642
|
+
// solo il posto dell'albero, che compare a calcolo finito.
|
|
1643
|
+
if (isInitialLoading) {
|
|
1644
|
+
return _jsx("div", { style: { width: '100%', height: '100%' } });
|
|
1569
1645
|
}
|
|
1570
1646
|
if (mergedTreeData.length === 0) {
|
|
1571
1647
|
// Calcolo interrotto dall'utente prima di aver calcolato qualsiasi correlazione
|
|
@@ -1622,15 +1698,11 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
|
|
|
1622
1698
|
whiteSpace: 'nowrap',
|
|
1623
1699
|
outline: 'none',
|
|
1624
1700
|
transition: 'background 0.2s'
|
|
1625
|
-
}, children: [_jsx(IconChevronRight, { fontSize: 14 }), _jsx("span", { children: SDKUI_Localizator.CollapseAll })] }) })] })), _jsx("div", { style: { flex: 1, minHeight: 0, overflow: 'auto' }, children: _jsx(TMTreeView, { dataSource: mergedTreeData, itemRender: finalItemRender, calculateItemsForNode: calculateItemsForNode, onDataChanged: handleDataChanged, focusedItem: focusedItem, onFocusedItemChanged: handleFocusedItemChanged, allowMultipleSelection: allowMultipleSelection, selectedItems: selectedItems, itemsPerPage: 100, onSelectionChanged: handleSelectedItemsChanged, onItemContextMenu:
|
|
1701
|
+
}, children: [_jsx(IconChevronRight, { fontSize: 14 }), _jsx("span", { children: SDKUI_Localizator.CollapseAll })] }) })] })), _jsx("div", { style: { flex: 1, minHeight: 0, overflow: 'auto' }, children: _jsx(TMTreeView, { dataSource: mergedTreeData, itemRender: finalItemRender, calculateItemsForNode: calculateItemsForNode, onDataChanged: handleDataChanged, focusedItem: focusedItem, onFocusedItemChanged: handleFocusedItemChanged, allowMultipleSelection: allowMultipleSelection, selectedItems: selectedItems, itemsPerPage: 100, onSelectionChanged: handleSelectedItemsChanged, onItemContextMenu: isExpansionLoading ? undefined : onItemContextMenu, enableVirtualization: true, shouldDelayFocusOnEvent: (node, e) => {
|
|
1626
1702
|
// Ritarda il focus quando si clicca sull'icona del documento
|
|
1627
1703
|
// per permettere al doppio click di funzionare
|
|
1628
1704
|
const target = e.target;
|
|
1629
1705
|
return !!target.closest('.tm-dcmt-icon');
|
|
1630
|
-
} }) })
|
|
1631
|
-
setTimeout(() => {
|
|
1632
|
-
expansionAbortController?.abort();
|
|
1633
|
-
}, 100);
|
|
1634
|
-
} }))] }));
|
|
1706
|
+
} }) })] }));
|
|
1635
1707
|
};
|
|
1636
1708
|
export default TMRelationViewer;
|