@topconsultnpm/sdkui-react 6.20.0-dev2.9 → 6.20.0-dev3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/lib/components/NewComponents/ContextMenu/styles.d.ts +3 -1
  2. package/lib/components/NewComponents/ContextMenu/styles.js +7 -5
  3. package/lib/components/base/Styled.d.ts +4 -1
  4. package/lib/components/base/Styled.js +11 -3
  5. package/lib/components/base/TMTreeView.d.ts +3 -1
  6. package/lib/components/base/TMTreeView.js +64 -21
  7. package/lib/components/choosers/TMDataListItemEditor.d.ts +11 -0
  8. package/lib/components/choosers/TMDataListItemEditor.js +130 -0
  9. package/lib/components/choosers/TMDataListItemFields.d.ts +11 -0
  10. package/lib/components/choosers/TMDataListItemFields.js +61 -0
  11. package/lib/components/choosers/TMDataListItemPicker.d.ts +1 -0
  12. package/lib/components/choosers/TMDataListItemPicker.js +178 -18
  13. package/lib/components/choosers/TMImageIDChooser.d.ts +16 -0
  14. package/lib/components/choosers/TMImageIDChooser.js +53 -0
  15. package/lib/components/choosers/TMMetadataChooser.js +1 -1
  16. package/lib/components/editors/TMDateBox.js +1 -1
  17. package/lib/components/editors/TMHtmlEditor.js +1 -1
  18. package/lib/components/editors/TMLocalizedTextBox.d.ts +1 -0
  19. package/lib/components/editors/TMLocalizedTextBox.js +3 -3
  20. package/lib/components/editors/TMTextBox.js +9 -10
  21. package/lib/components/features/archive/TMArchive.d.ts +2 -1
  22. package/lib/components/features/archive/TMArchive.js +31 -44
  23. package/lib/components/features/blog/TMBlogCommentForm.d.ts +3 -0
  24. package/lib/components/features/blog/TMBlogCommentForm.js +42 -36
  25. package/lib/components/features/documents/TMDcmtForm.d.ts +3 -1
  26. package/lib/components/features/documents/TMDcmtForm.js +197 -53
  27. package/lib/components/features/documents/TMDcmtTasks.d.ts +3 -1
  28. package/lib/components/features/documents/TMDcmtTasks.js +2 -2
  29. package/lib/components/features/documents/TMFileUploader.d.ts +4 -0
  30. package/lib/components/features/documents/TMFileUploader.js +17 -6
  31. package/lib/components/features/documents/TMMasterDetailDcmts.js +68 -84
  32. package/lib/components/features/documents/TMRelationViewer.d.ts +7 -1
  33. package/lib/components/features/documents/TMRelationViewer.js +395 -78
  34. package/lib/components/features/search/TMSearchResult.d.ts +2 -0
  35. package/lib/components/features/search/TMSearchResult.js +82 -79
  36. package/lib/components/features/search/TMSearchResultsMenuItems.js +2 -2
  37. package/lib/components/features/tasks/TMTaskForm.d.ts +1 -0
  38. package/lib/components/features/tasks/TMTaskForm.js +61 -193
  39. package/lib/components/features/tasks/TMTaskFormUtils.d.ts +80 -0
  40. package/lib/components/features/tasks/TMTaskFormUtils.js +559 -0
  41. package/lib/components/features/tasks/TMTasksUtils.d.ts +3 -1
  42. package/lib/components/features/tasks/TMTasksUtils.js +46 -16
  43. package/lib/components/features/tasks/TMTasksUtilsView.d.ts +0 -7
  44. package/lib/components/features/tasks/TMTasksUtilsView.js +7 -14
  45. package/lib/components/features/tasks/TMTasksView.js +5 -3
  46. package/lib/components/features/workflow/TMWorkflowPopup.d.ts +20 -3
  47. package/lib/components/features/workflow/TMWorkflowPopup.js +14 -92
  48. package/lib/components/features/workflow/diagram/ConnectionComponent.d.ts +1 -0
  49. package/lib/components/features/workflow/diagram/ConnectionComponent.js +6 -2
  50. package/lib/components/features/workflow/diagram/DiagramItemForm.js +1 -1
  51. package/lib/components/features/workflow/diagram/WFDiagram.js +75 -5
  52. package/lib/components/forms/Login/TMLoginForm.js +1 -1
  53. package/lib/components/forms/TMSaveForm.js +61 -13
  54. package/lib/components/grids/TMBlogsPost.js +8 -8
  55. package/lib/components/grids/TMBlogsPostUtils.js +2 -2
  56. package/lib/components/index.d.ts +2 -0
  57. package/lib/components/index.js +2 -0
  58. package/lib/components/pages/TMPage.js +4 -0
  59. package/lib/components/query/TMQueryEditor.d.ts +1 -0
  60. package/lib/components/query/TMQueryEditor.js +2 -2
  61. package/lib/helper/Enum_Localizator.js +5 -0
  62. package/lib/helper/GlobalStyles.js +3 -0
  63. package/lib/helper/SDKUI_Globals.d.ts +8 -0
  64. package/lib/helper/SDKUI_Globals.js +12 -0
  65. package/lib/helper/SDKUI_Localizator.d.ts +19 -3
  66. package/lib/helper/SDKUI_Localizator.js +182 -22
  67. package/lib/helper/TMIcons.d.ts +2 -1
  68. package/lib/helper/TMIcons.js +4 -1
  69. package/lib/helper/TMUtils.d.ts +5 -0
  70. package/lib/helper/TMUtils.js +10 -5
  71. package/lib/helper/helpers.d.ts +6 -2
  72. package/lib/helper/helpers.js +24 -8
  73. package/lib/helper/index.d.ts +1 -0
  74. package/lib/helper/index.js +1 -0
  75. package/lib/helper/queryHelper.js +1 -1
  76. package/lib/hooks/useBetaFeatures.d.ts +1 -0
  77. package/lib/hooks/useBetaFeatures.js +41 -0
  78. package/lib/hooks/useDcmtOperations.js +14 -2
  79. package/lib/hooks/useRelatedDocuments.js +34 -11
  80. package/lib/index.d.ts +1 -0
  81. package/lib/index.js +1 -0
  82. package/lib/services/platform_services.d.ts +1 -1
  83. package/lib/services/platform_services.js +4 -0
  84. package/package.json +2 -2
@@ -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
3
  import { DcmtTypeListCacheService, SDK_Globals, DataColumnTypes, MetadataFormats, SystemMIDs, MetadataDataDomains, RelationCacheService, RelationTypes } from "@topconsultnpm/sdk-ts";
4
- import { genUniqueId, IconFolder, IconBackhandIndexPointingRight } from '../../../helper';
4
+ import { genUniqueId, IconFolder, IconBackhandIndexPointingRight, IconCircleInfo } from '../../../helper';
5
5
  import { TMColors } from '../../../utils/theme';
6
6
  import { StyledDivHorizontal, StyledBadge } from '../../base/Styled';
7
7
  import TMTreeView from '../../base/TMTreeView';
@@ -136,7 +136,7 @@ export const searchResultToDataSource = async (searchResult, hideSysMetadata) =>
136
136
  }
137
137
  return output;
138
138
  };
139
- const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndicator = true, allowShowZeroDcmts = true, initialShowZeroDcmts = false, allowedTIDs, allowMultipleSelection = false, focusedItem, selectedItems, onFocusedItemChanged, onSelectedItemsChanged, onDocumentDoubleClick, customItemRender, customDocumentStyle, customMainContainerContent, customDocumentContent, showMetadataNames = false, maxDepthLevel = 2, invertMasterNavigation = true, additionalStaticItems, showMainDocument = true, labelMainContainer, }) => {
139
+ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndicator = true, allowShowZeroDcmts = true, initialShowZeroDcmts = false, allowedTIDs, allowMultipleSelection = false, focusedItem, selectedItems, onFocusedItemChanged, onSelectedItemsChanged, onDocumentDoubleClick, customItemRender, customDocumentStyle, customMainContainerContent, customDocumentContent, showMetadataNames = false, maxDepthLevel = 2, invertMasterNavigation = true, additionalStaticItems, showMainDocument = true, labelMainContainer, onNoRelationsFound, }) => {
140
140
  // State
141
141
  const [dcmtTypes, setDcmtTypes] = useState([]);
142
142
  const [treeData, setTreeData] = useState([]);
@@ -154,8 +154,12 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
154
154
  const [expansionAbortController, setExpansionAbortController] = useState(undefined);
155
155
  // Ref to track last loaded input to prevent unnecessary reloads
156
156
  const lastLoadedInputRef = React.useRef('');
157
+ // State to track loaded input key - triggers re-render for focus selection
158
+ const [loadedInputKey, setLoadedInputKey] = React.useState('');
157
159
  // Ref to track if user has manually expanded/collapsed static items
158
160
  const userInteractedWithStaticItemsRef = React.useRef(false);
161
+ // Ref to track the last inputKey for which we set the focused item
162
+ const lastFocusedInputRef = React.useRef('');
159
163
  /**
160
164
  * Generate a stable key from inputDcmts to detect real changes
161
165
  */
@@ -219,20 +223,27 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
219
223
  const source = await searchResultToDataSource(searchResult);
220
224
  if (source && source.length > 0) {
221
225
  const dcmtDetails = [];
226
+ // Check once if this document type can have detail relations
227
+ // (optimization: avoid checking for each document)
228
+ const canHaveDetails = await hasDetailRelations(searchResult.fromTID);
222
229
  for (const row of source) {
223
230
  const rowGUID = genUniqueId();
224
231
  const tid = row?.TID?.value;
225
232
  const did = row?.DID?.value;
233
+ const isLogDel = row?.ISLOGDEL?.value;
226
234
  dcmtDetails.push({
227
235
  tid: tid,
228
236
  did: did,
237
+ isLogDel: isLogDel,
229
238
  key: `${tid}_${did}_${searchResult.relationID}_${mTID}_${mDID}_${rowGUID}`,
230
239
  isDcmt: true,
231
240
  isContainer: false,
232
241
  isZero: false, // Documents are never zero (they exist)
242
+ isExpandible: canHaveDetails, // Can this doc be expanded?
233
243
  values: row,
234
244
  searchResult: [searchResult],
235
- itemsCount: 0,
245
+ // Leave items and itemsCount undefined so TMTreeView shows expand arrow based on isExpandible
246
+ // Children will be loaded lazily by calculateItemsForNode when expanded
236
247
  expanded: false,
237
248
  hidden: false,
238
249
  name: row?.SYS_Abstract?.value || row?.SYS_SUBJECT?.value || `Documento ${did}`
@@ -293,20 +304,30 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
293
304
  const source = await searchResultToDataSource(searchResult);
294
305
  if (source && source.length > 0) {
295
306
  const dcmtMasters = [];
307
+ // Check once if this document type can have master relations (for inverted mode)
308
+ // or detail relations (for standard mode when expanding masters)
309
+ // (optimization: avoid checking for each document)
310
+ const canExpand = isForMaster && invertMasterNavigation
311
+ ? await hasMasterRelations(searchResult.fromTID)
312
+ : await hasDetailRelations(searchResult.fromTID);
296
313
  for (const row of source) {
297
314
  const rowGUID = genUniqueId();
298
315
  const tid = row?.TID?.value;
299
316
  const did = row?.DID?.value;
317
+ const isLogDel = row?.ISLOGDEL?.value;
300
318
  dcmtMasters.push({
301
319
  tid: tid,
302
320
  did: did,
321
+ isLogDel: isLogDel,
303
322
  key: `${tid}_${did}_${searchResult.relationID}_${dTID}_${dDID}_${rowGUID}`,
304
323
  isDcmt: true,
305
324
  isContainer: false,
306
325
  isZero: false, // Documents are never zero (they exist)
326
+ isExpandible: canExpand, // Can this doc be expanded?
307
327
  values: row,
308
328
  searchResult: [searchResult],
309
- itemsCount: 0,
329
+ // Leave items and itemsCount undefined so TMTreeView shows expand arrow based on isExpandible
330
+ // Children will be loaded lazily by calculateItemsForNode when expanded
310
331
  expanded: false,
311
332
  hidden: false,
312
333
  name: row?.SYS_Abstract?.value || row?.SYS_SUBJECT?.value || `Documento ${did}`
@@ -324,17 +345,128 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
324
345
  return items;
325
346
  }, [allowedTIDs]);
326
347
  /**
327
- * Update hidden property based on showZeroDcmts (original simple logic)
348
+ * Update hidden property based on showZeroDcmts
349
+ * Updates items in-place without resetting isLoaded (no API calls needed)
328
350
  */
329
351
  const updateHiddenProperty = useCallback((nodes) => {
352
+ // Safety check: if nodes is undefined or not an array, return empty array
353
+ if (!nodes || !Array.isArray(nodes)) {
354
+ return [];
355
+ }
330
356
  return nodes.map(node => {
331
- const updatedNode = { ...node, hidden: !showZeroDcmts && node.isZero };
332
- if (node.items) {
333
- updatedNode.items = updateHiddenProperty(node.items);
357
+ let shouldHide = false;
358
+ let updatedItems = undefined;
359
+ if (node.items && Array.isArray(node.items)) {
360
+ // Recursively update children first - this creates a new array
361
+ let updatedChildren = updateHiddenProperty(node.items);
362
+ // Remove any existing auto-generated info messages (with key starting with __info__)
363
+ const filteredChildren = updatedChildren.filter(child => !(child.isInfoMessage && child.key?.startsWith('__info__')));
364
+ // Check if all real children (excluding info messages) are hidden
365
+ const realChildren = filteredChildren.filter(child => !child.isInfoMessage);
366
+ const allChildrenHidden = realChildren.length > 0 && realChildren.every(child => child.hidden);
367
+ // If node is expanded AND showZeroDcmts is false AND all children are hidden, add info message
368
+ // This applies to both containers and document nodes with children
369
+ if (node.expanded && !showZeroDcmts && allChildrenHidden) {
370
+ // Add info message at the beginning, keep original children (hidden but preserved)
371
+ updatedItems = [
372
+ {
373
+ key: `__info__${node.key}`,
374
+ name: 'Nessun documento correlato da visualizzare',
375
+ isContainer: false,
376
+ isDcmt: false,
377
+ isInfoMessage: true,
378
+ isExpandible: false
379
+ },
380
+ ...filteredChildren
381
+ ];
382
+ // Don't hide the node itself if it's showing an info message
383
+ shouldHide = false;
384
+ }
385
+ else {
386
+ updatedItems = filteredChildren;
387
+ // Hide zero-item containers when showZeroDcmts is false (only if not showing info message)
388
+ shouldHide = !showZeroDcmts && (node.isZero ?? false);
389
+ }
390
+ }
391
+ else {
392
+ // No items, apply normal hide logic
393
+ shouldHide = !showZeroDcmts && (node.isZero ?? false);
334
394
  }
335
- return updatedNode;
395
+ // IMPORTANT: Always create new object to trigger React re-render
396
+ return { ...node, hidden: shouldHide, items: updatedItems };
336
397
  });
337
398
  }, [showZeroDcmts]);
399
+ /**
400
+ * Helper function to set up initial tree expansion state.
401
+ * Called after all data is loaded but before setTreeData.
402
+ * Ensures: root container expanded, first document expanded with isRoot=true (for focus), first correlation folder expanded
403
+ * Returns a NEW tree with the modifications (immutable approach)
404
+ */
405
+ const setupInitialTreeExpansion = (tree) => {
406
+ if (tree.length === 0)
407
+ return tree;
408
+ // ALWAYS expand the first container (even if empty, to show infoMessage)
409
+ const firstRootContainer = tree[0];
410
+ if (!firstRootContainer)
411
+ return tree;
412
+ // Create a deep copy of the first container with expanded=true, isRoot=true
413
+ let newFirstContainer = {
414
+ ...firstRootContainer,
415
+ expanded: true,
416
+ isRoot: true
417
+ };
418
+ // 2. Find first document/container and expand it
419
+ const firstRootItems = newFirstContainer.items;
420
+ if (firstRootItems && firstRootItems.length > 0) {
421
+ const firstDocOrContainer = firstRootItems[0];
422
+ if (firstDocOrContainer.isDcmt) {
423
+ // First item is a document - expand it and mark as root for focus
424
+ let newFirstDoc = {
425
+ ...firstDocOrContainer,
426
+ expanded: true,
427
+ isRoot: true
428
+ };
429
+ // 3. Expand first correlation folder (child of the document)
430
+ const docItems = newFirstDoc.items;
431
+ if (docItems && docItems.length > 0 && docItems[0].isContainer) {
432
+ const newFirstCorrelation = { ...docItems[0], expanded: true };
433
+ newFirstDoc = {
434
+ ...newFirstDoc,
435
+ items: [newFirstCorrelation, ...docItems.slice(1)]
436
+ };
437
+ }
438
+ // Update the container's items
439
+ newFirstContainer = {
440
+ ...newFirstContainer,
441
+ items: [newFirstDoc, ...firstRootItems.slice(1)]
442
+ };
443
+ }
444
+ else if (firstDocOrContainer.isContainer) {
445
+ // First item is a container (correlation folder) - expand it
446
+ let newFirstCorrelation = {
447
+ ...firstDocOrContainer,
448
+ expanded: true
449
+ };
450
+ // Find first document inside this container and mark for focus
451
+ const containerItems = newFirstCorrelation.items;
452
+ if (containerItems && containerItems.length > 0 && containerItems[0].isDcmt) {
453
+ const newFirstDoc = { ...containerItems[0], isRoot: true };
454
+ newFirstCorrelation = {
455
+ ...newFirstCorrelation,
456
+ items: [newFirstDoc, ...containerItems.slice(1)]
457
+ };
458
+ }
459
+ // Update the container's items
460
+ newFirstContainer = {
461
+ ...newFirstContainer,
462
+ items: [newFirstCorrelation, ...firstRootItems.slice(1)]
463
+ };
464
+ }
465
+ }
466
+ // If firstRootItems is empty/undefined, the container is still expanded (will show infoMessage)
467
+ // Return new tree with the modified first container
468
+ return [newFirstContainer, ...tree.slice(1)];
469
+ };
338
470
  /**
339
471
  * Main data loading function
340
472
  */
@@ -405,13 +537,12 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
405
537
  tid: dcmt.TID,
406
538
  dtd,
407
539
  isContainer: true,
408
- isRoot: true,
409
540
  isLoaded: true,
410
541
  isZero: false,
411
542
  searchResult: result ? [result] : [],
412
543
  items: relatedDocs, // Directly show detail containers
413
544
  itemsCount: relatedDocs.length,
414
- expanded: tree.length === 0,
545
+ expanded: false,
415
546
  hidden: false
416
547
  };
417
548
  tree.push(typeContainer);
@@ -426,10 +557,9 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
426
557
  did: dcmt.DID,
427
558
  isDcmt: true,
428
559
  isContainer: false,
429
- expanded: tree.length === 0,
560
+ expanded: false,
430
561
  isZero: dcmt.DID === 0,
431
562
  isMaster: !isForMaster,
432
- isRoot: true,
433
563
  isLoaded: true,
434
564
  hidden: false,
435
565
  values: docRow,
@@ -444,13 +574,12 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
444
574
  tid: dcmt.TID,
445
575
  dtd,
446
576
  isContainer: true,
447
- isRoot: true,
448
577
  isLoaded: true,
449
578
  isZero: false, // Type container is never zero (contains documents)
450
579
  searchResult: result ? [result] : [],
451
580
  items: [docNode],
452
581
  itemsCount: 1,
453
- expanded: tree.length === 0,
582
+ expanded: false,
454
583
  hidden: false
455
584
  };
456
585
  tree.push(typeContainer);
@@ -460,8 +589,55 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
460
589
  processedCount++;
461
590
  setWaitPanelValuePrimary(processedCount);
462
591
  }
463
- setTreeData(updateHiddenProperty(tree));
464
- }, [inputDcmts, dcmtTypes, maxDepthLevel, isForMaster, invertMasterNavigation, getDetailDcmtsAsync, getMasterDcmtsAsync, abortController, updateHiddenProperty, showMainDocument, labelMainContainer]);
592
+ /**
593
+ * Helper function to check if a node has any actual correlated documents
594
+ * Returns true if there are NO documents (relations are empty)
595
+ */
596
+ const hasNoActualDocuments = (items) => {
597
+ if (!items || items.length === 0)
598
+ return true;
599
+ // Check each item
600
+ return items.every(item => {
601
+ // If it's a container, check if it's zero or has no documents
602
+ if (item.isContainer) {
603
+ // Container with isZero=true has no documents
604
+ if (item.isZero)
605
+ return true;
606
+ // Container with no items has no documents
607
+ if (!item.items || item.items.length === 0)
608
+ return true;
609
+ // Otherwise, it has documents
610
+ return false;
611
+ }
612
+ // If it's a document node, check its children (relation containers)
613
+ if (item.isDcmt) {
614
+ // Check if all child containers are empty
615
+ return hasNoActualDocuments(item.items);
616
+ }
617
+ // For other types, assume no documents
618
+ return true;
619
+ });
620
+ };
621
+ // Check if there are no relations for any document
622
+ const hasNoRelations = tree.length === 0 || tree.every(container => {
623
+ // Check if container has no items
624
+ if (!container.items || container.items.length === 0)
625
+ return true;
626
+ // Check recursively if there are any actual documents
627
+ return hasNoActualDocuments(container.items);
628
+ });
629
+ // If no relations found, notify parent
630
+ if (hasNoRelations && onNoRelationsFound) {
631
+ onNoRelationsFound();
632
+ }
633
+ // FIRST setup initial expansion state (root container expanded, first document expanded with focus, first correlation folder expanded)
634
+ // This must run BEFORE updateHiddenProperty so that infoMessage logic sees expanded=true
635
+ const expandedTree = setupInitialTreeExpansion(tree);
636
+ // THEN apply hidden property transformations (creates new objects, adds infoMessage where needed)
637
+ // Now it will correctly detect expanded nodes and add infoMessage if all children are hidden
638
+ const processedTree = updateHiddenProperty(expandedTree);
639
+ setTreeData(processedTree);
640
+ }, [inputDcmts, dcmtTypes, maxDepthLevel, isForMaster, invertMasterNavigation, getDetailDcmtsAsync, getMasterDcmtsAsync, abortController, updateHiddenProperty, showMainDocument, labelMainContainer, onNoRelationsFound]);
465
641
  /**
466
642
  * Merge main tree data with additional static items
467
643
  */
@@ -483,16 +659,18 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
483
659
  if (!inputDcmts || inputDcmts.length === 0 || dcmtTypes.length === 0) {
484
660
  setTreeData([]);
485
661
  lastLoadedInputRef.current = '';
662
+ lastFocusedInputRef.current = '';
663
+ setLoadedInputKey('');
486
664
  userInteractedWithStaticItemsRef.current = false; // Reset interaction flag
487
665
  return;
488
666
  }
489
667
  // Generate current input key
490
668
  const currentKey = getInputKey();
491
- // Skip if we already loaded this exact data
669
+ // Skip if we already loaded or are loading this exact data
492
670
  if (currentKey === lastLoadedInputRef.current && treeData.length > 0) {
493
671
  return;
494
672
  }
495
- // Mark as loading this key
673
+ // Mark as loading this key to prevent duplicate loads
496
674
  lastLoadedInputRef.current = currentKey;
497
675
  // Reset interaction flag when loading new data
498
676
  userInteractedWithStaticItemsRef.current = false;
@@ -501,6 +679,9 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
501
679
  setWaitPanelValuePrimary(0);
502
680
  // Call loadData and use .then() instead of await to allow React to render
503
681
  loadData().then(() => {
682
+ // Mark as loaded AFTER data is ready - state update triggers re-render for focus selection
683
+ lastLoadedInputRef.current = currentKey;
684
+ setLoadedInputKey(currentKey);
504
685
  setShowWaitPanel(false);
505
686
  setWaitPanelTextPrimary('');
506
687
  setWaitPanelMaxValuePrimary(0);
@@ -509,10 +690,53 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
509
690
  }, [inputDcmts, dcmtTypes, maxDepthLevel, getInputKey, loadData, treeData.length]);
510
691
  /**
511
692
  * Update tree when showZeroDcmts changes
693
+ * Updates the visualization without re-fetching data (isLoaded stays true)
512
694
  */
513
695
  useEffect(() => {
514
696
  setTreeData(prevData => updateHiddenProperty(prevData));
515
697
  }, [showZeroDcmts, updateHiddenProperty]);
698
+ /**
699
+ * Set focused item when data finishes loading
700
+ * Focuses on the first document (under root) every time new data is loaded
701
+ * Works both on initial load and on navigation (onPrev/onNext)
702
+ */
703
+ useEffect(() => {
704
+ const currentInputKey = getInputKey();
705
+ // Ensure data has finished loading for current input
706
+ if (loadedInputKey !== currentInputKey) {
707
+ return;
708
+ }
709
+ if (!showMainDocument || !onFocusedItemChanged || !treeData.length || !inputDcmts?.length) {
710
+ return;
711
+ }
712
+ // Skip if we already focused for this inputKey
713
+ if (lastFocusedInputRef.current === currentInputKey) {
714
+ return;
715
+ }
716
+ // Helper function to recursively find the first document with isRoot=true
717
+ const findFirstRootDocument = (items) => {
718
+ for (const item of items) {
719
+ // Check if this item is a document with isRoot=true
720
+ if (item.isDcmt && item.isRoot) {
721
+ return item;
722
+ }
723
+ // Recursively search in children
724
+ if (item.items && Array.isArray(item.items)) {
725
+ const found = findFirstRootDocument(item.items);
726
+ if (found)
727
+ return found;
728
+ }
729
+ }
730
+ return null;
731
+ };
732
+ // Find the first document marked as root (set by setupInitialTreeExpansion)
733
+ const docNode = findFirstRootDocument(treeData);
734
+ if (docNode) {
735
+ // Set the focused item and mark this inputKey as focused
736
+ onFocusedItemChanged(docNode);
737
+ lastFocusedInputRef.current = currentInputKey;
738
+ }
739
+ }, [treeData, loadedInputKey, showMainDocument, onFocusedItemChanged, inputDcmts, getInputKey]);
516
740
  /**
517
741
  * Sync static items state when additionalStaticItems change
518
742
  * IMPORTANT: Only update if user hasn't manually interacted with the tree,
@@ -548,6 +772,10 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
548
772
  /**
549
773
  * Calculate items for node when expanded (lazy loading)
550
774
  * Note: additionalStaticItems are already fully loaded, so skip calculation for them
775
+ *
776
+ * PERFORMANCE OPTIMIZATION:
777
+ * - Containers: Return items immediately (no API calls) - items are already loaded
778
+ * - Documents: Load children lazily only when the specific document is expanded (1 API call)
551
779
  */
552
780
  const calculateItemsForNode = useCallback(async (node) => {
553
781
  // Skip calculation for separator (it has no children)
@@ -558,62 +786,129 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
558
786
  if (node.isStaticItem || node.isAdditionalContainer || node.isAdditional) {
559
787
  return node.items;
560
788
  }
561
- // If it's a document, return existing items
562
- if (node.isDcmt)
563
- return node.items;
564
- // If container is already loaded, return items
565
- if (node.isLoaded)
566
- return node.items;
567
- const newAbortController = new AbortController();
568
- setExpansionAbortController(newAbortController);
569
- const itemsToLoad = node.items?.length ?? 0;
570
- setShowExpansionWaitPanel(true);
571
- setExpansionWaitPanelMaxValue(itemsToLoad);
572
- setExpansionWaitPanelValue(0);
573
- setExpansionWaitPanelText(`Caricamento documenti correlati...`);
574
- try {
575
- const newItems = [];
576
- let processedCount = 0;
577
- for (const dcmt of node.items ?? []) {
578
- if (newAbortController.signal.aborted) {
579
- console.log('Folder expansion aborted by user');
580
- return node.items;
581
- }
582
- const item = { ...dcmt };
583
- if (item.tid && item.did && !item.isLoaded) {
584
- // Update progress
585
- processedCount++;
586
- setExpansionWaitPanelValue(processedCount);
587
- setExpansionWaitPanelText(`Caricamento ${processedCount} di ${itemsToLoad}...`);
588
- // Nella modalità originale (invertMasterNavigation=false),
589
- // i documenti detail non devono caricare i master come figli
590
- if (isForMaster && !invertMasterNavigation) {
591
- // Carica i detail dei detail (navigazione naturale detail→detail)
592
- const loadedItems = await getDetailDcmtsAsync(item.tid, item.did, 1);
593
- item.items = updateHiddenProperty(loadedItems);
594
- }
595
- else {
596
- // Modalità standard o invertita
597
- const loadedItems = isForMaster
598
- ? await getMasterDcmtsAsync(item.tid, item.did, 1)
599
- : await getDetailDcmtsAsync(item.tid, item.did, 1);
600
- item.items = updateHiddenProperty(loadedItems);
601
- }
602
- item.isLoaded = true;
789
+ // If already loaded, apply current visualization logic without re-fetching
790
+ if (node.isLoaded && node.items) {
791
+ // Apply current show/hide logic based on showZeroDcmts
792
+ const updatedItems = updateHiddenProperty(node.items);
793
+ // If this is a container, check if we need to show info message
794
+ if (node.isContainer) {
795
+ // Remove any existing info messages
796
+ const filteredItems = updatedItems.filter(child => !(child.isInfoMessage && child.key?.startsWith('__info__')));
797
+ // Check if all real children are hidden
798
+ const realChildren = filteredItems.filter(child => !child.isInfoMessage);
799
+ const allChildrenHidden = realChildren.length > 0 && realChildren.every(child => child.hidden);
800
+ // If showZeroDcmts is false and all items are hidden, show info message + keep hidden containers
801
+ if (!showZeroDcmts && allChildrenHidden) {
802
+ return [
803
+ {
804
+ key: `__info__${node.key}`,
805
+ name: 'Nessun documento correlato da visualizzare',
806
+ isContainer: false,
807
+ isDcmt: false,
808
+ isInfoMessage: true,
809
+ isExpandible: false
810
+ },
811
+ ...filteredItems // Keep hidden containers so they can be shown when toggling
812
+ ];
603
813
  }
604
- newItems.push(item);
605
814
  }
606
- return newItems;
815
+ return updatedItems;
607
816
  }
608
- catch (error) {
609
- console.error('Error loading folder contents:', error);
610
- return node.items;
817
+ // ============================================
818
+ // CONTAINER: Show items immediately (no API calls)
819
+ // Items are already loaded from initial load or parent expansion
820
+ // ============================================
821
+ if (node.isContainer) {
822
+ // No API calls needed - just return existing items
823
+ // Children of these items will be loaded lazily when user expands each document
824
+ // Apply updateHiddenProperty to respect current showZeroDcmts setting
825
+ // If node has no items, return empty array
826
+ if (!node.items)
827
+ return [];
828
+ const updatedItems = updateHiddenProperty(node.items);
829
+ // If showZeroDcmts is false and all items are hidden, add info message + keep hidden containers
830
+ if (!showZeroDcmts && updatedItems.length > 0 && updatedItems.every(item => item.hidden)) {
831
+ return [
832
+ {
833
+ key: `__info__${node.key}`,
834
+ name: 'Nessun documento correlato da visualizzare',
835
+ isContainer: false,
836
+ isDcmt: false,
837
+ isInfoMessage: true,
838
+ isExpandible: false
839
+ },
840
+ ...updatedItems // Keep hidden containers so they can be shown when toggling
841
+ ];
842
+ }
843
+ return updatedItems;
611
844
  }
612
- finally {
613
- setShowExpansionWaitPanel(false);
614
- setExpansionAbortController(undefined);
845
+ // ============================================
846
+ // DOCUMENT: Load its children (relation containers) lazily
847
+ // Only makes ONE API call for this specific document
848
+ // ============================================
849
+ if (node.isDcmt && node.tid && node.did) {
850
+ const newAbortController = new AbortController();
851
+ setExpansionAbortController(newAbortController);
852
+ setShowExpansionWaitPanel(true);
853
+ setExpansionWaitPanelMaxValue(1);
854
+ setExpansionWaitPanelValue(0);
855
+ setExpansionWaitPanelText(`Caricamento documenti correlati...`);
856
+ try {
857
+ // Check for abort
858
+ if (newAbortController.signal.aborted) {
859
+ return [];
860
+ }
861
+ // Determine which load function to use based on mode
862
+ let loadedItems = [];
863
+ if (isForMaster && !invertMasterNavigation) {
864
+ // Original mode: detail documents load detail documents (natural detail→detail navigation)
865
+ loadedItems = await getDetailDcmtsAsync(node.tid, node.did, 1);
866
+ }
867
+ else if (isForMaster) {
868
+ // Inverted master mode: load master documents
869
+ loadedItems = await getMasterDcmtsAsync(node.tid, node.did, 1);
870
+ }
871
+ else {
872
+ // Standard mode: load detail documents
873
+ loadedItems = await getDetailDcmtsAsync(node.tid, node.did, 1);
874
+ }
875
+ // Espandi automaticamente il primo container se ci sono documenti correlati
876
+ if (loadedItems.length > 0 && loadedItems[0].isContainer) {
877
+ loadedItems[0].expanded = true;
878
+ }
879
+ // Apply updateHiddenProperty to respect current showZeroDcmts setting
880
+ // This ensures that dynamically loaded nodes respect the visibility rules:
881
+ // - Relation type containers are always visible (even if empty)
882
+ // - Other containers with zero items can be hidden based on showZeroDcmts
883
+ const updatedItems = updateHiddenProperty(loadedItems);
884
+ // If showZeroDcmts is false and all items are hidden, add info message + keep hidden containers
885
+ if (!showZeroDcmts && updatedItems.length > 0 && updatedItems.every(item => item.hidden)) {
886
+ return [
887
+ {
888
+ key: `__info__${node.key}`,
889
+ name: 'Nessun documento correlato da visualizzare',
890
+ isContainer: false,
891
+ isDcmt: false,
892
+ isInfoMessage: true,
893
+ isExpandible: false
894
+ },
895
+ ...updatedItems // Keep hidden containers so they can be shown when toggling
896
+ ];
897
+ }
898
+ return updatedItems;
899
+ }
900
+ catch (error) {
901
+ console.error('Error loading document relations:', error);
902
+ return [];
903
+ }
904
+ finally {
905
+ setShowExpansionWaitPanel(false);
906
+ setExpansionAbortController(undefined);
907
+ }
615
908
  }
616
- }, [isForMaster, invertMasterNavigation, getDetailDcmtsAsync, getMasterDcmtsAsync, updateHiddenProperty]);
909
+ // Default: return existing items
910
+ return node.items;
911
+ }, [isForMaster, invertMasterNavigation, getDetailDcmtsAsync, getMasterDcmtsAsync, updateHiddenProperty, showZeroDcmts]);
617
912
  /**
618
913
  * Default item renderer with metadata display (adapted from TMMasterDetailDcmts.tsx)
619
914
  */
@@ -626,6 +921,18 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
626
921
  e.stopPropagation();
627
922
  onDocumentDoubleClick?.(item.tid, item.did, item.name);
628
923
  };
924
+ // Info message rendering
925
+ if (item.isInfoMessage) {
926
+ return (_jsxs("div", { style: {
927
+ display: 'flex',
928
+ alignItems: 'center',
929
+ gap: '10px',
930
+ height: '32px',
931
+ padding: '6px 0',
932
+ color: '#666',
933
+ fontStyle: 'italic'
934
+ }, children: [_jsx(IconCircleInfo, { fontSize: 20, color: TMColors.iconLight }), _jsx("span", { children: item.name })] }));
935
+ }
629
936
  // Container rendering
630
937
  if (item.isContainer || !item.isDcmt) {
631
938
  const defaultContainerStyle = {
@@ -634,7 +941,7 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
634
941
  gap: '10px',
635
942
  height: '32px',
636
943
  padding: '6px 0',
637
- opacity: item.isZero ? 0.4 : 1,
944
+ opacity: item.isZero ? 0.99 : 1,
638
945
  transition: 'opacity 0.2s ease-in-out'
639
946
  };
640
947
  // Se è il container principale (root) e showMainDocument è false,
@@ -653,6 +960,7 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
653
960
  return (_jsx("div", { style: defaultContainerStyle, children: content }));
654
961
  }
655
962
  // Document rendering with full metadata display
963
+ const isLogicallyDeleted = Number(item.isLogDel) === 1;
656
964
  const defaultDocumentStyle = {
657
965
  minWidth: '90px',
658
966
  width: '100%',
@@ -664,18 +972,23 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
664
972
  alignItems: 'center',
665
973
  cursor: 'pointer',
666
974
  userSelect: 'none',
667
- opacity: item.isZero ? 0.4 : 1,
668
- transition: 'opacity 0.2s ease-in-out'
975
+ opacity: item.isZero ? 0.99 : 1,
976
+ transition: 'opacity 0.2s ease-in-out',
977
+ textDecoration: isLogicallyDeleted ? 'line-through' : 'none'
669
978
  };
670
979
  const documentStyle = customDocumentStyle
671
980
  ? { ...defaultDocumentStyle, ...customDocumentStyle(item) }
672
981
  : defaultDocumentStyle;
982
+ const textDecoration = isLogicallyDeleted ? 'line-through' : 'none';
983
+ const textColor = isLogicallyDeleted ? 'gray' : undefined;
673
984
  const defaultMetadataContent = item.values && (_jsx(StyledDivHorizontal, { style: {
674
985
  fontSize: '1rem',
675
986
  overflow: 'hidden',
676
987
  flex: 1,
677
988
  minWidth: 0,
678
- whiteSpace: 'nowrap'
989
+ whiteSpace: 'nowrap',
990
+ textDecoration: textDecoration,
991
+ color: textColor
679
992
  }, children: getDcmtDisplayValue(item.values).map((key, index) => {
680
993
  const md = item.values?.[key]?.md;
681
994
  const value = item.values?.[key]?.value;
@@ -684,12 +997,16 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
684
997
  return (_jsxs(StyledDivHorizontal, { style: {
685
998
  flexShrink: isLast ? 1 : 0,
686
999
  minWidth: isLast ? 0 : 'auto',
687
- overflow: isLast ? 'hidden' : 'visible'
688
- }, children: [index > 0 && _jsx("span", { style: { margin: '0 5px', color: '#999' }, children: "\u2022" }), showMetadataNames && (_jsxs("span", { style: { color: '#666', marginRight: '5px' }, children: [md?.name || key, ":"] })), md?.dataDomain === MetadataDataDomains.DataList ? (_jsx(TMDataListItemViewer, { dataListId: md.dataListID, viewMode: md.dataListViewMode, value: value })) : md?.dataDomain === MetadataDataDomains.UserID ? (_jsx(TMDataUserIdItemViewer, { userId: value, showIcon: true })) : (_jsx("span", { style: {
1000
+ overflow: isLast ? 'hidden' : 'visible',
1001
+ textDecoration: textDecoration,
1002
+ color: textColor
1003
+ }, children: [index > 0 && _jsx("span", { style: { margin: '0 5px', color: textColor || '#999', textDecoration: textDecoration }, children: "\u2022" }), showMetadataNames && (_jsxs("span", { style: { color: textColor || '#666', marginRight: '5px', textDecoration: textDecoration }, children: [md?.name || key, ":"] })), md?.dataDomain === MetadataDataDomains.DataList ? (_jsx("span", { style: { textDecoration: textDecoration, color: textColor }, children: _jsx(TMDataListItemViewer, { dataListId: md.dataListID, viewMode: md.dataListViewMode, value: value }) })) : md?.dataDomain === MetadataDataDomains.UserID ? (_jsx("span", { style: { textDecoration: textDecoration, color: textColor }, children: _jsx(TMDataUserIdItemViewer, { userId: value, showIcon: true }) })) : (_jsx("span", { style: {
689
1004
  fontWeight: 500,
690
1005
  overflow: isLast ? 'hidden' : 'visible',
691
1006
  textOverflow: isLast ? 'ellipsis' : 'clip',
692
- whiteSpace: 'nowrap'
1007
+ whiteSpace: 'nowrap',
1008
+ textDecoration: textDecoration,
1009
+ color: textColor
693
1010
  }, children: value }))] }, `${key}_${index}`));
694
1011
  }) }));
695
1012
  const metadataContent = customDocumentContent
@@ -744,7 +1061,7 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
744
1061
  if (mergedTreeData.length === 0) {
745
1062
  return _jsx("div", { style: { padding: '20px', textAlign: 'center', color: '#666' }, children: "Nessuna relazione disponibile." });
746
1063
  }
747
- return (_jsxs(_Fragment, { children: [_jsx(TMTreeView, { dataSource: mergedTreeData, itemRender: finalItemRender, calculateItemsForNode: calculateItemsForNode, onDataChanged: handleDataChanged, focusedItem: focusedItem, onFocusedItemChanged: handleFocusedItemChanged, allowMultipleSelection: allowMultipleSelection, selectedItems: selectedItems, onSelectionChanged: handleSelectedItemsChanged }), showExpansionWaitPanel && (_jsx(TMWaitPanel, { title: isForMaster ? 'Caricamento documenti master' : 'Caricamento documenti dettaglio', showPrimary: true, textPrimary: expansionWaitPanelText, valuePrimary: expansionWaitPanelValue, maxValuePrimary: expansionWaitPanelMaxValue, isCancelable: true, abortController: expansionAbortController, onAbortClick: (abortController) => {
1064
+ return (_jsxs(_Fragment, { children: [_jsx(TMTreeView, { dataSource: mergedTreeData, itemRender: finalItemRender, calculateItemsForNode: calculateItemsForNode, onDataChanged: handleDataChanged, focusedItem: focusedItem, onFocusedItemChanged: handleFocusedItemChanged, allowMultipleSelection: allowMultipleSelection, selectedItems: selectedItems, itemsPerPage: 100, onSelectionChanged: handleSelectedItemsChanged }), showExpansionWaitPanel && (_jsx(TMWaitPanel, { title: isForMaster ? 'Caricamento documenti master' : 'Caricamento documenti dettaglio', showPrimary: true, textPrimary: expansionWaitPanelText, valuePrimary: expansionWaitPanelValue, maxValuePrimary: expansionWaitPanelMaxValue, isCancelable: true, abortController: expansionAbortController, onAbortClick: (abortController) => {
748
1065
  setTimeout(() => {
749
1066
  abortController?.abort();
750
1067
  }, 100);