@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
- for (let i = 0; i < row.length; i++) {
102
- const column = searchResult?.dtdResult?.columns?.[i];
103
- const mid = Number(column?.extendedProperties?.["MID"] ?? "0");
104
- if (hideSysMetadata && mid < 100)
105
- continue;
106
- // For system metadata (MID <= 100), use UPPERCASE caption as key
107
- // For custom metadata (MID > 100), use caption as key but handle duplicates
108
- let key = column?.caption ?? '';
109
- if (mid <= 100) {
110
- key = key.toUpperCase();
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
- else {
113
- // If key already exists with a different MID, append MID to make it unique
114
- if (item[key] !== undefined && item[key]?.md?.id !== mid) {
115
- key = `${key}_${mid}`;
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
- const value = row[i];
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
- // ALWAYS expand the first container (even if empty, to show infoMessage)
441
- const firstRootContainer = tree[0];
442
- if (!firstRootContainer)
443
- return tree;
444
- // Create a deep copy of the first container with expanded=true, isRoot=true
445
- let newFirstContainer = {
446
- ...firstRootContainer,
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
- else if (firstDocOrContainer.isContainer) {
478
- // First item is a container (correlation folder)
479
- // Count how many sibling containers there are
480
- const siblingContainers = firstRootItems.filter(child => child.isContainer);
481
- const shouldExpand = siblingContainers.length === 1;
482
- let newFirstCorrelation = {
483
- ...firstDocOrContainer,
484
- expanded: shouldExpand
485
- };
486
- // Find first document inside this container and mark for focus
487
- const containerItems = newFirstCorrelation.items;
488
- if (containerItems && containerItems.length > 0 && containerItems[0].isDcmt) {
489
- const newFirstDoc = { ...containerItems[0], isRoot: true };
490
- newFirstCorrelation = {
491
- ...newFirstCorrelation,
492
- items: [newFirstDoc, ...containerItems.slice(1)]
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
- // If firstRootItems is empty/undefined, the container is still expanded (will show infoMessage)
503
- // Return new tree with the modified first container
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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@topconsultnpm/sdkui-react",
3
- "version": "6.21.0-dev5.2",
3
+ "version": "6.21.0-dev5.3",
4
4
  "description": "",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1",