@topconsultnpm/sdkui-react 6.21.0-dev5.2 → 6.21.0-dev5.3
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.
|
@@ -31,6 +31,10 @@ const TMMasterDetailDcmts = ({ allTasks = [], getAllTasks, deleteTaskByIdsCallba
|
|
|
31
31
|
const [contextMenuPosition, setContextMenuPosition] = useState({ x: 0, y: 0 });
|
|
32
32
|
// Track if TMRelationViewer is loading (used to disable context menu during loading)
|
|
33
33
|
const [isRelationViewerLoading, setIsRelationViewerLoading] = useState(false);
|
|
34
|
+
// Track if loading has ever been TRUE (to distinguish initial false from post-load false)
|
|
35
|
+
const hasLoadingBeenTrueRef = useRef(false);
|
|
36
|
+
// Track the previous loading state to detect transitions
|
|
37
|
+
const prevIsRelationViewerLoadingRef = useRef(false);
|
|
34
38
|
const [dtdFocused, setDtdFocused] = useState();
|
|
35
39
|
const [refreshKey, setRefreshKey] = useState(0);
|
|
36
40
|
// Separate refresh key for TMFormOrResultWrapper only (doesn't affect tmTreeView)
|
|
@@ -39,6 +43,21 @@ const TMMasterDetailDcmts = ({ allTasks = [], getAllTasks, deleteTaskByIdsCallba
|
|
|
39
43
|
const [focusedItemFormData, setFocusedItemFormData] = useState([]);
|
|
40
44
|
// Trigger operationItems refresh (after file substitution, etc.)
|
|
41
45
|
const [refreshOperationsTrigger, setRefreshOperationsTrigger] = useState(0);
|
|
46
|
+
// Safety net: when TMRelationViewer finishes loading (transition from TRUE to FALSE),
|
|
47
|
+
// hide the spinner regardless of whether a document was focused or not.
|
|
48
|
+
// This handles edge cases where no document gets isRoot=true.
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
const prevLoading = prevIsRelationViewerLoadingRef.current;
|
|
51
|
+
prevIsRelationViewerLoadingRef.current = isRelationViewerLoading;
|
|
52
|
+
if (isRelationViewerLoading) {
|
|
53
|
+
// Loading started - mark that we've seen a true state
|
|
54
|
+
hasLoadingBeenTrueRef.current = true;
|
|
55
|
+
}
|
|
56
|
+
else if (prevLoading && hasLoadingBeenTrueRef.current && isCheckingFirstLoad) {
|
|
57
|
+
// Transition from TRUE to FALSE (loading completed) - hide spinner
|
|
58
|
+
setIsCheckingFirstLoad(false);
|
|
59
|
+
}
|
|
60
|
+
}, [isRelationViewerLoading, isCheckingFirstLoad]);
|
|
42
61
|
// Increments trigger counter to force operationItems to re-calculate
|
|
43
62
|
const onRefreshOperationsDatagrid = useCallback(async () => {
|
|
44
63
|
setRefreshOperationsTrigger(prev => prev + 1);
|
|
@@ -258,6 +277,7 @@ const TMMasterDetailDcmts = ({ allTasks = [], getAllTasks, deleteTaskByIdsCallba
|
|
|
258
277
|
useEffect(() => {
|
|
259
278
|
if (noRelationsOnFirstLoad) {
|
|
260
279
|
setNoRelationsOnFirstLoad(false);
|
|
280
|
+
setIsCheckingFirstLoad(false); // Hide spinner before navigating back
|
|
261
281
|
ShowAlert({
|
|
262
282
|
message: SDKUI_Localizator.RelatedDcmtsNotFound,
|
|
263
283
|
title: SDKUI_Localizator.RelationsNotFound,
|
|
@@ -146,6 +146,8 @@ export declare const hasMasterRelations: (dTID: number | undefined) => Promise<b
|
|
|
146
146
|
export declare const getDisplayValueByColumn: (col: DataColumnDescriptor | undefined, value: any) => any;
|
|
147
147
|
/**
|
|
148
148
|
* Convert SearchResultDescriptor to structured data source with metadata
|
|
149
|
+
* For each document, fetches complete metadata using GetMetadataAsync to ensure
|
|
150
|
+
* all metadata properties (isSpecialSearchOutput, permissions, etc.) are available
|
|
149
151
|
*/
|
|
150
152
|
export declare const searchResultToDataSource: (searchResult: SearchResultDescriptor | undefined, hideSysMetadata?: boolean) => Promise<any[]>;
|
|
151
153
|
export declare const DEFAULT_RELATION_EXPAND_LEVEL = 4;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
|
3
|
-
import { DcmtTypeListCacheService, SDK_Globals, DataColumnTypes, MetadataFormats, MetadataDataDomains, RelationCacheService, RelationTypes, UserListCacheService } from "@topconsultnpm/sdk-ts";
|
|
4
|
-
import { genUniqueId, IconFolder, IconBackhandIndexPointingRight, IconCircleInfo, getDcmtCicoStatus, IconChevronDown, IconChevronRight, SDKUI_Localizator, buildDcmtDisplayName, SDKUI_Globals } from '../../../helper';
|
|
3
|
+
import { DcmtTypeListCacheService, SDK_Globals, DataColumnTypes, MetadataFormats, MetadataDataDomains, RelationCacheService, RelationTypes, UserListCacheService, LayoutModes } from "@topconsultnpm/sdk-ts";
|
|
4
|
+
import { genUniqueId, IconFolder, IconBackhandIndexPointingRight, IconCircleInfo, getDcmtCicoStatus, IconChevronDown, IconChevronRight, SDKUI_Localizator, buildDcmtDisplayName, SDKUI_Globals, searchResultToMetadataValues } from '../../../helper';
|
|
5
5
|
import ShowAlert from '../../base/TMAlert';
|
|
6
6
|
import TMToppyMessage from '../../../helper/TMToppyMessage';
|
|
7
7
|
import { TMColors } from '../../../utils/theme';
|
|
@@ -88,41 +88,77 @@ export const getDisplayValueByColumn = (col, value) => {
|
|
|
88
88
|
};
|
|
89
89
|
/**
|
|
90
90
|
* Convert SearchResultDescriptor to structured data source with metadata
|
|
91
|
+
* For each document, fetches complete metadata using GetMetadataAsync to ensure
|
|
92
|
+
* all metadata properties (isSpecialSearchOutput, permissions, etc.) are available
|
|
91
93
|
*/
|
|
92
94
|
export const searchResultToDataSource = async (searchResult, hideSysMetadata) => {
|
|
93
95
|
const rows = searchResult?.dtdResult?.rows ?? [];
|
|
94
|
-
const tid = searchResult?.fromTID;
|
|
95
|
-
// IMPORTANT: Pass true to get full metadata with all properties (isSpecialSearchOutput, etc.)
|
|
96
|
-
const dtd = await DcmtTypeListCacheService.GetAsync(tid, true);
|
|
97
96
|
const output = [];
|
|
97
|
+
// Find TID and DID column indices
|
|
98
|
+
const columns = searchResult?.dtdResult?.columns ?? [];
|
|
99
|
+
const tidColIndex = columns.findIndex(col => col.caption?.toUpperCase() === 'TID');
|
|
100
|
+
const didColIndex = columns.findIndex(col => col.caption?.toUpperCase() === 'DID');
|
|
98
101
|
for (let index = 0; index < rows.length; index++) {
|
|
99
|
-
const item = { rowIndex: index };
|
|
100
102
|
const row = rows[index];
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
//
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
103
|
+
// Extract TID and DID from row
|
|
104
|
+
const tid = tidColIndex >= 0 ? Number(row[tidColIndex]) : searchResult?.fromTID;
|
|
105
|
+
const did = didColIndex >= 0 ? Number(row[didColIndex]) : undefined;
|
|
106
|
+
// Fetch complete metadata for this specific document (like getFilteredMetadata does)
|
|
107
|
+
const metadata = await SDK_Globals.tmSession?.NewSearchEngine().GetMetadataAsync(tid, did, true);
|
|
108
|
+
if (metadata) {
|
|
109
|
+
// Get full DTD with metadata for this document
|
|
110
|
+
const dtdResult = metadata.dtdResult;
|
|
111
|
+
const metadataRows = dtdResult?.rows?.[0] ?? [];
|
|
112
|
+
const mids = metadata.selectMIDs;
|
|
113
|
+
const dtdWithMetadata = await DcmtTypeListCacheService.GetWithNotGrantedAsync(tid, did, metadata);
|
|
114
|
+
const mdList = dtdWithMetadata?.metadata ?? [];
|
|
115
|
+
// Convert to MetadataValueDescriptorEx array
|
|
116
|
+
const metadataList = searchResultToMetadataValues(tid, dtdResult, metadataRows, mids, mdList, LayoutModes.Update);
|
|
117
|
+
// Convert to DcmtMetadataMap format (like getFilteredMetadata does)
|
|
118
|
+
const item = { rowIndex: index };
|
|
119
|
+
for (const mvd of metadataList) {
|
|
120
|
+
if (hideSysMetadata && mvd.mid !== undefined && mvd.mid < 100)
|
|
121
|
+
continue;
|
|
122
|
+
const key = mvd.mid !== undefined && mvd.mid <= 100
|
|
123
|
+
? (mvd.md?.name ?? '').toUpperCase()
|
|
124
|
+
: (mvd.md?.name ?? '');
|
|
125
|
+
if (key) {
|
|
126
|
+
item[key] = {
|
|
127
|
+
md: mvd.md,
|
|
128
|
+
value: mvd.value
|
|
129
|
+
};
|
|
130
|
+
}
|
|
111
131
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
132
|
+
output.push(item);
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
// Fallback: use original row data with basic parsing
|
|
136
|
+
const item = { rowIndex: index };
|
|
137
|
+
const fallbackTid = searchResult?.fromTID;
|
|
138
|
+
const dtd = await DcmtTypeListCacheService.GetWithNotGrantedAsync(fallbackTid, undefined, searchResult);
|
|
139
|
+
for (let i = 0; i < row.length; i++) {
|
|
140
|
+
const column = searchResult?.dtdResult?.columns?.[i];
|
|
141
|
+
const mid = Number(column?.extendedProperties?.["MID"] ?? "0");
|
|
142
|
+
if (hideSysMetadata && mid < 100)
|
|
143
|
+
continue;
|
|
144
|
+
let key = column?.caption ?? '';
|
|
145
|
+
if (mid <= 100) {
|
|
146
|
+
key = key.toUpperCase();
|
|
116
147
|
}
|
|
148
|
+
else {
|
|
149
|
+
if (item[key] !== undefined && item[key]?.md?.id !== mid) {
|
|
150
|
+
key = `${key}_${mid}`;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
const value = row[i];
|
|
154
|
+
const md = dtd?.metadata?.find(o => o.id == mid);
|
|
155
|
+
item[key] = {
|
|
156
|
+
md: md,
|
|
157
|
+
value: getDisplayValueByColumn(column, value)
|
|
158
|
+
};
|
|
117
159
|
}
|
|
118
|
-
|
|
119
|
-
const md = dtd?.metadata?.find(o => o.id == mid);
|
|
120
|
-
item[key] = {
|
|
121
|
-
md: md,
|
|
122
|
-
value: getDisplayValueByColumn(column, value)
|
|
123
|
-
};
|
|
160
|
+
output.push(item);
|
|
124
161
|
}
|
|
125
|
-
output.push(item);
|
|
126
162
|
}
|
|
127
163
|
return output;
|
|
128
164
|
};
|
|
@@ -437,71 +473,90 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
|
|
|
437
473
|
const setupInitialTreeExpansion = (tree) => {
|
|
438
474
|
if (tree.length === 0)
|
|
439
475
|
return tree;
|
|
440
|
-
//
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
expanded: true,
|
|
448
|
-
isRoot: true
|
|
449
|
-
};
|
|
450
|
-
// 2. Find first document/container and expand it
|
|
451
|
-
const firstRootItems = newFirstContainer.items;
|
|
452
|
-
if (firstRootItems && firstRootItems.length > 0) {
|
|
453
|
-
const firstDocOrContainer = firstRootItems[0];
|
|
454
|
-
if (firstDocOrContainer.isDcmt) {
|
|
455
|
-
// First item is a document - expand it and mark as root for focus
|
|
456
|
-
let newFirstDoc = {
|
|
457
|
-
...firstDocOrContainer,
|
|
458
|
-
expanded: true,
|
|
459
|
-
isRoot: true
|
|
460
|
-
};
|
|
461
|
-
// 3. Expand first correlation folder ONLY if there's exactly one
|
|
462
|
-
const docItems = newFirstDoc.items;
|
|
463
|
-
const containerChildren = docItems?.filter(child => child.isContainer) ?? [];
|
|
464
|
-
if (containerChildren.length === 1 && docItems && docItems.length > 0 && docItems[0].isContainer) {
|
|
465
|
-
const newFirstCorrelation = { ...docItems[0], expanded: true };
|
|
466
|
-
newFirstDoc = {
|
|
467
|
-
...newFirstDoc,
|
|
468
|
-
items: [newFirstCorrelation, ...docItems.slice(1)]
|
|
469
|
-
};
|
|
476
|
+
// Find the first container with documents (not empty)
|
|
477
|
+
// This ensures we focus on an actual document, not an empty container
|
|
478
|
+
const findFirstNonEmptyContainerIndex = () => {
|
|
479
|
+
for (let i = 0; i < tree.length; i++) {
|
|
480
|
+
const container = tree[i];
|
|
481
|
+
if (!container.isZero && container.items && Array.isArray(container.items) && container.items.length > 0) {
|
|
482
|
+
return i;
|
|
470
483
|
}
|
|
471
|
-
// Update the container's items
|
|
472
|
-
newFirstContainer = {
|
|
473
|
-
...newFirstContainer,
|
|
474
|
-
items: [newFirstDoc, ...firstRootItems.slice(1)]
|
|
475
|
-
};
|
|
476
484
|
}
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
485
|
+
return 0; // Fallback to first container if all are empty
|
|
486
|
+
};
|
|
487
|
+
const targetContainerIndex = findFirstNonEmptyContainerIndex();
|
|
488
|
+
// Process all containers, but only mark the target one for focus
|
|
489
|
+
const result = tree.map((container, index) => {
|
|
490
|
+
const isTargetContainer = index === targetContainerIndex;
|
|
491
|
+
const isFirstContainer = index === 0;
|
|
492
|
+
// Always expand the first container (even if empty, to show infoMessage)
|
|
493
|
+
// Also expand the target container if different
|
|
494
|
+
const shouldExpand = isFirstContainer || isTargetContainer;
|
|
495
|
+
if (!shouldExpand && !isTargetContainer) {
|
|
496
|
+
return container; // Keep non-target containers as-is
|
|
497
|
+
}
|
|
498
|
+
// Create a copy with expanded state
|
|
499
|
+
let newContainer = {
|
|
500
|
+
...container,
|
|
501
|
+
expanded: shouldExpand,
|
|
502
|
+
isRoot: isTargetContainer // Only mark target container as root
|
|
503
|
+
};
|
|
504
|
+
// Only process items for the target container (the one with documents)
|
|
505
|
+
if (isTargetContainer) {
|
|
506
|
+
const containerItems = newContainer.items;
|
|
507
|
+
if (containerItems && containerItems.length > 0) {
|
|
508
|
+
const firstDocOrContainer = containerItems[0];
|
|
509
|
+
if (firstDocOrContainer.isDcmt) {
|
|
510
|
+
// First item is a document - expand it and mark as root for focus
|
|
511
|
+
let newFirstDoc = {
|
|
512
|
+
...firstDocOrContainer,
|
|
513
|
+
expanded: true,
|
|
514
|
+
isRoot: true
|
|
515
|
+
};
|
|
516
|
+
// Expand first correlation folder ONLY if there's exactly one
|
|
517
|
+
const docItems = newFirstDoc.items;
|
|
518
|
+
const containerChildren = docItems?.filter(child => child.isContainer) ?? [];
|
|
519
|
+
if (containerChildren.length === 1 && docItems && docItems.length > 0 && docItems[0].isContainer) {
|
|
520
|
+
const newFirstCorrelation = { ...docItems[0], expanded: true };
|
|
521
|
+
newFirstDoc = {
|
|
522
|
+
...newFirstDoc,
|
|
523
|
+
items: [newFirstCorrelation, ...docItems.slice(1)]
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
// Update the container's items
|
|
527
|
+
newContainer = {
|
|
528
|
+
...newContainer,
|
|
529
|
+
items: [newFirstDoc, ...containerItems.slice(1)]
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
else if (firstDocOrContainer.isContainer) {
|
|
533
|
+
// First item is a container (correlation folder)
|
|
534
|
+
const siblingContainers = containerItems.filter(child => child.isContainer);
|
|
535
|
+
const shouldExpandCorrelation = siblingContainers.length === 1;
|
|
536
|
+
let newFirstCorrelation = {
|
|
537
|
+
...firstDocOrContainer,
|
|
538
|
+
expanded: shouldExpandCorrelation
|
|
539
|
+
};
|
|
540
|
+
// Find first document inside this container and mark for focus
|
|
541
|
+
const correlationItems = newFirstCorrelation.items;
|
|
542
|
+
if (correlationItems && correlationItems.length > 0 && correlationItems[0].isDcmt) {
|
|
543
|
+
const newFirstDoc = { ...correlationItems[0], isRoot: true };
|
|
544
|
+
newFirstCorrelation = {
|
|
545
|
+
...newFirstCorrelation,
|
|
546
|
+
items: [newFirstDoc, ...correlationItems.slice(1)]
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
// Update the container's items
|
|
550
|
+
newContainer = {
|
|
551
|
+
...newContainer,
|
|
552
|
+
items: [newFirstCorrelation, ...containerItems.slice(1)]
|
|
553
|
+
};
|
|
554
|
+
}
|
|
494
555
|
}
|
|
495
|
-
// Update the container's items
|
|
496
|
-
newFirstContainer = {
|
|
497
|
-
...newFirstContainer,
|
|
498
|
-
items: [newFirstCorrelation, ...firstRootItems.slice(1)]
|
|
499
|
-
};
|
|
500
556
|
}
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
return [newFirstContainer, ...tree.slice(1)];
|
|
557
|
+
return newContainer;
|
|
558
|
+
});
|
|
559
|
+
return result;
|
|
505
560
|
};
|
|
506
561
|
/**
|
|
507
562
|
* Main data loading function
|
|
@@ -1130,9 +1185,7 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
|
|
|
1130
1185
|
transition: 'opacity 0.2s ease-in-out',
|
|
1131
1186
|
textDecoration: isLogicallyDeleted ? 'line-through' : 'none'
|
|
1132
1187
|
};
|
|
1133
|
-
const documentStyle = customDocumentStyle
|
|
1134
|
-
? { ...defaultDocumentStyle, ...customDocumentStyle(item) }
|
|
1135
|
-
: defaultDocumentStyle;
|
|
1188
|
+
const documentStyle = customDocumentStyle ? { ...defaultDocumentStyle, ...customDocumentStyle(item) } : defaultDocumentStyle;
|
|
1136
1189
|
const textDecoration = isLogicallyDeleted ? 'line-through' : 'none';
|
|
1137
1190
|
const textColor = isLogicallyDeleted ? 'gray' : undefined;
|
|
1138
1191
|
const defaultMetadataContent = item.values && (() => {
|
|
@@ -1165,9 +1218,7 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
|
|
|
1165
1218
|
}, children: value }))] }, `${key}_${index}`));
|
|
1166
1219
|
}) }));
|
|
1167
1220
|
})();
|
|
1168
|
-
const metadataContent = customDocumentContent
|
|
1169
|
-
? customDocumentContent(item, defaultMetadataContent || _jsx(_Fragment, {}))
|
|
1170
|
-
: defaultMetadataContent;
|
|
1221
|
+
const metadataContent = customDocumentContent ? customDocumentContent(item, defaultMetadataContent || _jsx(_Fragment, {})) : defaultMetadataContent;
|
|
1171
1222
|
// Calculate checkout status for non-root documents
|
|
1172
1223
|
let checkoutStatusIcon = null;
|
|
1173
1224
|
if (item.values && item.dtd) {
|