@topconsultnpm/sdkui-react 6.20.0-dev2.5 → 6.20.0-dev2.51

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 (67) hide show
  1. package/lib/components/base/Styled.d.ts +1 -0
  2. package/lib/components/base/Styled.js +10 -2
  3. package/lib/components/base/TMTreeView.d.ts +3 -1
  4. package/lib/components/base/TMTreeView.js +63 -20
  5. package/lib/components/choosers/TMDataListItemEditor.d.ts +11 -0
  6. package/lib/components/choosers/TMDataListItemEditor.js +130 -0
  7. package/lib/components/choosers/TMDataListItemFields.d.ts +11 -0
  8. package/lib/components/choosers/TMDataListItemFields.js +61 -0
  9. package/lib/components/choosers/TMDataListItemPicker.d.ts +1 -0
  10. package/lib/components/choosers/TMDataListItemPicker.js +178 -18
  11. package/lib/components/choosers/TMDynDataListItemChooser.js +11 -6
  12. package/lib/components/choosers/TMImageIDChooser.d.ts +16 -0
  13. package/lib/components/choosers/TMImageIDChooser.js +53 -0
  14. package/lib/components/choosers/TMMetadataChooser.js +1 -1
  15. package/lib/components/editors/TMLocalizedTextBox.d.ts +1 -0
  16. package/lib/components/editors/TMLocalizedTextBox.js +3 -3
  17. package/lib/components/editors/TMMetadataValues.js +3 -1
  18. package/lib/components/editors/TMTextBox.js +8 -9
  19. package/lib/components/features/archive/TMArchive.js +29 -42
  20. package/lib/components/features/documents/TMDcmtForm.js +165 -42
  21. package/lib/components/features/documents/TMDcmtPreview.js +2 -1
  22. package/lib/components/features/documents/TMMasterDetailDcmts.js +67 -6
  23. package/lib/components/features/documents/TMRelationViewer.d.ts +7 -1
  24. package/lib/components/features/documents/TMRelationViewer.js +389 -76
  25. package/lib/components/features/search/TMSearchResult.d.ts +1 -0
  26. package/lib/components/features/search/TMSearchResult.js +44 -82
  27. package/lib/components/features/search/TMSearchResultsMenuItems.js +2 -2
  28. package/lib/components/features/tasks/TMTaskForm.js +35 -187
  29. package/lib/components/features/tasks/TMTaskFormUtils.d.ts +74 -0
  30. package/lib/components/features/tasks/TMTaskFormUtils.js +538 -0
  31. package/lib/components/features/tasks/TMTasksUtils.d.ts +2 -0
  32. package/lib/components/features/tasks/TMTasksUtils.js +38 -13
  33. package/lib/components/features/tasks/TMTasksUtilsView.d.ts +0 -7
  34. package/lib/components/features/tasks/TMTasksUtilsView.js +7 -14
  35. package/lib/components/features/tasks/TMTasksView.js +2 -2
  36. package/lib/components/features/workflow/TMWorkflowPopup.d.ts +2 -1
  37. package/lib/components/features/workflow/TMWorkflowPopup.js +2 -1
  38. package/lib/components/features/workflow/diagram/DiagramItemForm.js +1 -1
  39. package/lib/components/forms/Login/TMLoginForm.js +1 -1
  40. package/lib/components/forms/TMSaveForm.js +61 -13
  41. package/lib/components/grids/TMBlogsPost.js +2 -2
  42. package/lib/components/index.d.ts +2 -0
  43. package/lib/components/index.js +2 -0
  44. package/lib/components/layout/panelManager/TMPanelManagerContainer.js +3 -2
  45. package/lib/components/pages/TMPage.js +4 -0
  46. package/lib/components/query/TMQueryEditor.d.ts +1 -0
  47. package/lib/components/query/TMQueryEditor.js +2 -2
  48. package/lib/helper/Enum_Localizator.js +5 -0
  49. package/lib/helper/GlobalStyles.js +3 -0
  50. package/lib/helper/SDKUI_Globals.d.ts +4 -0
  51. package/lib/helper/SDKUI_Globals.js +6 -0
  52. package/lib/helper/SDKUI_Localizator.d.ts +11 -3
  53. package/lib/helper/SDKUI_Localizator.js +102 -22
  54. package/lib/helper/TMUtils.d.ts +18 -0
  55. package/lib/helper/TMUtils.js +58 -0
  56. package/lib/helper/helpers.d.ts +6 -2
  57. package/lib/helper/helpers.js +23 -8
  58. package/lib/helper/index.d.ts +1 -0
  59. package/lib/helper/index.js +1 -0
  60. package/lib/helper/queryHelper.js +1 -1
  61. package/lib/hooks/useBetaFeatures.d.ts +1 -0
  62. package/lib/hooks/useBetaFeatures.js +41 -0
  63. package/lib/hooks/useDcmtOperations.js +14 -2
  64. package/lib/hooks/useRelatedDocuments.js +34 -11
  65. package/lib/index.d.ts +1 -0
  66. package/lib/index.js +1 -0
  67. package/package.json +11 -11
@@ -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([]);
@@ -156,6 +156,8 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
156
156
  const lastLoadedInputRef = React.useRef('');
157
157
  // Ref to track if user has manually expanded/collapsed static items
158
158
  const userInteractedWithStaticItemsRef = React.useRef(false);
159
+ // Ref to track if we've already set the initial focused item
160
+ const initialFocusSetRef = React.useRef(false);
159
161
  /**
160
162
  * Generate a stable key from inputDcmts to detect real changes
161
163
  */
@@ -219,20 +221,27 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
219
221
  const source = await searchResultToDataSource(searchResult);
220
222
  if (source && source.length > 0) {
221
223
  const dcmtDetails = [];
224
+ // Check once if this document type can have detail relations
225
+ // (optimization: avoid checking for each document)
226
+ const canHaveDetails = await hasDetailRelations(searchResult.fromTID);
222
227
  for (const row of source) {
223
228
  const rowGUID = genUniqueId();
224
229
  const tid = row?.TID?.value;
225
230
  const did = row?.DID?.value;
231
+ const isLogDel = row?.ISLOGDEL?.value;
226
232
  dcmtDetails.push({
227
233
  tid: tid,
228
234
  did: did,
235
+ isLogDel: isLogDel,
229
236
  key: `${tid}_${did}_${searchResult.relationID}_${mTID}_${mDID}_${rowGUID}`,
230
237
  isDcmt: true,
231
238
  isContainer: false,
232
239
  isZero: false, // Documents are never zero (they exist)
240
+ isExpandible: canHaveDetails, // Can this doc be expanded?
233
241
  values: row,
234
242
  searchResult: [searchResult],
235
- itemsCount: 0,
243
+ // Leave items and itemsCount undefined so TMTreeView shows expand arrow based on isExpandible
244
+ // Children will be loaded lazily by calculateItemsForNode when expanded
236
245
  expanded: false,
237
246
  hidden: false,
238
247
  name: row?.SYS_Abstract?.value || row?.SYS_SUBJECT?.value || `Documento ${did}`
@@ -293,20 +302,30 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
293
302
  const source = await searchResultToDataSource(searchResult);
294
303
  if (source && source.length > 0) {
295
304
  const dcmtMasters = [];
305
+ // Check once if this document type can have master relations (for inverted mode)
306
+ // or detail relations (for standard mode when expanding masters)
307
+ // (optimization: avoid checking for each document)
308
+ const canExpand = isForMaster && invertMasterNavigation
309
+ ? await hasMasterRelations(searchResult.fromTID)
310
+ : await hasDetailRelations(searchResult.fromTID);
296
311
  for (const row of source) {
297
312
  const rowGUID = genUniqueId();
298
313
  const tid = row?.TID?.value;
299
314
  const did = row?.DID?.value;
315
+ const isLogDel = row?.ISLOGDEL?.value;
300
316
  dcmtMasters.push({
301
317
  tid: tid,
302
318
  did: did,
319
+ isLogDel: isLogDel,
303
320
  key: `${tid}_${did}_${searchResult.relationID}_${dTID}_${dDID}_${rowGUID}`,
304
321
  isDcmt: true,
305
322
  isContainer: false,
306
323
  isZero: false, // Documents are never zero (they exist)
324
+ isExpandible: canExpand, // Can this doc be expanded?
307
325
  values: row,
308
326
  searchResult: [searchResult],
309
- itemsCount: 0,
327
+ // Leave items and itemsCount undefined so TMTreeView shows expand arrow based on isExpandible
328
+ // Children will be loaded lazily by calculateItemsForNode when expanded
310
329
  expanded: false,
311
330
  hidden: false,
312
331
  name: row?.SYS_Abstract?.value || row?.SYS_SUBJECT?.value || `Documento ${did}`
@@ -324,17 +343,128 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
324
343
  return items;
325
344
  }, [allowedTIDs]);
326
345
  /**
327
- * Update hidden property based on showZeroDcmts (original simple logic)
346
+ * Update hidden property based on showZeroDcmts
347
+ * Updates items in-place without resetting isLoaded (no API calls needed)
328
348
  */
329
349
  const updateHiddenProperty = useCallback((nodes) => {
350
+ // Safety check: if nodes is undefined or not an array, return empty array
351
+ if (!nodes || !Array.isArray(nodes)) {
352
+ return [];
353
+ }
330
354
  return nodes.map(node => {
331
- const updatedNode = { ...node, hidden: !showZeroDcmts && node.isZero };
332
- if (node.items) {
333
- updatedNode.items = updateHiddenProperty(node.items);
355
+ let shouldHide = false;
356
+ let updatedItems = undefined;
357
+ if (node.items && Array.isArray(node.items)) {
358
+ // Recursively update children first - this creates a new array
359
+ let updatedChildren = updateHiddenProperty(node.items);
360
+ // Remove any existing auto-generated info messages (with key starting with __info__)
361
+ const filteredChildren = updatedChildren.filter(child => !(child.isInfoMessage && child.key?.startsWith('__info__')));
362
+ // Check if all real children (excluding info messages) are hidden
363
+ const realChildren = filteredChildren.filter(child => !child.isInfoMessage);
364
+ const allChildrenHidden = realChildren.length > 0 && realChildren.every(child => child.hidden);
365
+ // If node is expanded AND showZeroDcmts is false AND all children are hidden, add info message
366
+ // This applies to both containers and document nodes with children
367
+ if (node.expanded && !showZeroDcmts && allChildrenHidden) {
368
+ // Add info message at the beginning, keep original children (hidden but preserved)
369
+ updatedItems = [
370
+ {
371
+ key: `__info__${node.key}`,
372
+ name: 'Nessun documento correlato da visualizzare',
373
+ isContainer: false,
374
+ isDcmt: false,
375
+ isInfoMessage: true,
376
+ isExpandible: false
377
+ },
378
+ ...filteredChildren
379
+ ];
380
+ // Don't hide the node itself if it's showing an info message
381
+ shouldHide = false;
382
+ }
383
+ else {
384
+ updatedItems = filteredChildren;
385
+ // Hide zero-item containers when showZeroDcmts is false (only if not showing info message)
386
+ shouldHide = !showZeroDcmts && (node.isZero ?? false);
387
+ }
388
+ }
389
+ else {
390
+ // No items, apply normal hide logic
391
+ shouldHide = !showZeroDcmts && (node.isZero ?? false);
334
392
  }
335
- return updatedNode;
393
+ // IMPORTANT: Always create new object to trigger React re-render
394
+ return { ...node, hidden: shouldHide, items: updatedItems };
336
395
  });
337
396
  }, [showZeroDcmts]);
397
+ /**
398
+ * Helper function to set up initial tree expansion state.
399
+ * Called after all data is loaded but before setTreeData.
400
+ * Ensures: root container expanded, first document expanded with isRoot=true (for focus), first correlation folder expanded
401
+ * Returns a NEW tree with the modifications (immutable approach)
402
+ */
403
+ const setupInitialTreeExpansion = (tree) => {
404
+ if (tree.length === 0)
405
+ return tree;
406
+ // ALWAYS expand the first container (even if empty, to show infoMessage)
407
+ const firstRootContainer = tree[0];
408
+ if (!firstRootContainer)
409
+ return tree;
410
+ // Create a deep copy of the first container with expanded=true, isRoot=true
411
+ let newFirstContainer = {
412
+ ...firstRootContainer,
413
+ expanded: true,
414
+ isRoot: true
415
+ };
416
+ // 2. Find first document/container and expand it
417
+ const firstRootItems = newFirstContainer.items;
418
+ if (firstRootItems && firstRootItems.length > 0) {
419
+ const firstDocOrContainer = firstRootItems[0];
420
+ if (firstDocOrContainer.isDcmt) {
421
+ // First item is a document - expand it and mark as root for focus
422
+ let newFirstDoc = {
423
+ ...firstDocOrContainer,
424
+ expanded: true,
425
+ isRoot: true
426
+ };
427
+ // 3. Expand first correlation folder (child of the document)
428
+ const docItems = newFirstDoc.items;
429
+ if (docItems && docItems.length > 0 && docItems[0].isContainer) {
430
+ const newFirstCorrelation = { ...docItems[0], expanded: true };
431
+ newFirstDoc = {
432
+ ...newFirstDoc,
433
+ items: [newFirstCorrelation, ...docItems.slice(1)]
434
+ };
435
+ }
436
+ // Update the container's items
437
+ newFirstContainer = {
438
+ ...newFirstContainer,
439
+ items: [newFirstDoc, ...firstRootItems.slice(1)]
440
+ };
441
+ }
442
+ else if (firstDocOrContainer.isContainer) {
443
+ // First item is a container (correlation folder) - expand it
444
+ let newFirstCorrelation = {
445
+ ...firstDocOrContainer,
446
+ expanded: true
447
+ };
448
+ // Find first document inside this container and mark for focus
449
+ const containerItems = newFirstCorrelation.items;
450
+ if (containerItems && containerItems.length > 0 && containerItems[0].isDcmt) {
451
+ const newFirstDoc = { ...containerItems[0], isRoot: true };
452
+ newFirstCorrelation = {
453
+ ...newFirstCorrelation,
454
+ items: [newFirstDoc, ...containerItems.slice(1)]
455
+ };
456
+ }
457
+ // Update the container's items
458
+ newFirstContainer = {
459
+ ...newFirstContainer,
460
+ items: [newFirstCorrelation, ...firstRootItems.slice(1)]
461
+ };
462
+ }
463
+ }
464
+ // If firstRootItems is empty/undefined, the container is still expanded (will show infoMessage)
465
+ // Return new tree with the modified first container
466
+ return [newFirstContainer, ...tree.slice(1)];
467
+ };
338
468
  /**
339
469
  * Main data loading function
340
470
  */
@@ -405,13 +535,12 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
405
535
  tid: dcmt.TID,
406
536
  dtd,
407
537
  isContainer: true,
408
- isRoot: true,
409
538
  isLoaded: true,
410
539
  isZero: false,
411
540
  searchResult: result ? [result] : [],
412
541
  items: relatedDocs, // Directly show detail containers
413
542
  itemsCount: relatedDocs.length,
414
- expanded: tree.length === 0,
543
+ expanded: false,
415
544
  hidden: false
416
545
  };
417
546
  tree.push(typeContainer);
@@ -426,10 +555,9 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
426
555
  did: dcmt.DID,
427
556
  isDcmt: true,
428
557
  isContainer: false,
429
- expanded: tree.length === 0,
558
+ expanded: false,
430
559
  isZero: dcmt.DID === 0,
431
560
  isMaster: !isForMaster,
432
- isRoot: true,
433
561
  isLoaded: true,
434
562
  hidden: false,
435
563
  values: docRow,
@@ -444,13 +572,12 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
444
572
  tid: dcmt.TID,
445
573
  dtd,
446
574
  isContainer: true,
447
- isRoot: true,
448
575
  isLoaded: true,
449
576
  isZero: false, // Type container is never zero (contains documents)
450
577
  searchResult: result ? [result] : [],
451
578
  items: [docNode],
452
579
  itemsCount: 1,
453
- expanded: tree.length === 0,
580
+ expanded: false,
454
581
  hidden: false
455
582
  };
456
583
  tree.push(typeContainer);
@@ -460,8 +587,55 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
460
587
  processedCount++;
461
588
  setWaitPanelValuePrimary(processedCount);
462
589
  }
463
- setTreeData(updateHiddenProperty(tree));
464
- }, [inputDcmts, dcmtTypes, maxDepthLevel, isForMaster, invertMasterNavigation, getDetailDcmtsAsync, getMasterDcmtsAsync, abortController, updateHiddenProperty, showMainDocument, labelMainContainer]);
590
+ /**
591
+ * Helper function to check if a node has any actual correlated documents
592
+ * Returns true if there are NO documents (relations are empty)
593
+ */
594
+ const hasNoActualDocuments = (items) => {
595
+ if (!items || items.length === 0)
596
+ return true;
597
+ // Check each item
598
+ return items.every(item => {
599
+ // If it's a container, check if it's zero or has no documents
600
+ if (item.isContainer) {
601
+ // Container with isZero=true has no documents
602
+ if (item.isZero)
603
+ return true;
604
+ // Container with no items has no documents
605
+ if (!item.items || item.items.length === 0)
606
+ return true;
607
+ // Otherwise, it has documents
608
+ return false;
609
+ }
610
+ // If it's a document node, check its children (relation containers)
611
+ if (item.isDcmt) {
612
+ // Check if all child containers are empty
613
+ return hasNoActualDocuments(item.items);
614
+ }
615
+ // For other types, assume no documents
616
+ return true;
617
+ });
618
+ };
619
+ // Check if there are no relations for any document
620
+ const hasNoRelations = tree.length === 0 || tree.every(container => {
621
+ // Check if container has no items
622
+ if (!container.items || container.items.length === 0)
623
+ return true;
624
+ // Check recursively if there are any actual documents
625
+ return hasNoActualDocuments(container.items);
626
+ });
627
+ // If no relations found, notify parent
628
+ if (hasNoRelations && onNoRelationsFound) {
629
+ onNoRelationsFound();
630
+ }
631
+ // FIRST setup initial expansion state (root container expanded, first document expanded with focus, first correlation folder expanded)
632
+ // This must run BEFORE updateHiddenProperty so that infoMessage logic sees expanded=true
633
+ const expandedTree = setupInitialTreeExpansion(tree);
634
+ // THEN apply hidden property transformations (creates new objects, adds infoMessage where needed)
635
+ // Now it will correctly detect expanded nodes and add infoMessage if all children are hidden
636
+ const processedTree = updateHiddenProperty(expandedTree);
637
+ setTreeData(processedTree);
638
+ }, [inputDcmts, dcmtTypes, maxDepthLevel, isForMaster, invertMasterNavigation, getDetailDcmtsAsync, getMasterDcmtsAsync, abortController, updateHiddenProperty, showMainDocument, labelMainContainer, onNoRelationsFound]);
465
639
  /**
466
640
  * Merge main tree data with additional static items
467
641
  */
@@ -509,10 +683,56 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
509
683
  }, [inputDcmts, dcmtTypes, maxDepthLevel, getInputKey, loadData, treeData.length]);
510
684
  /**
511
685
  * Update tree when showZeroDcmts changes
686
+ * Updates the visualization without re-fetching data (isLoaded stays true)
512
687
  */
513
688
  useEffect(() => {
514
689
  setTreeData(prevData => updateHiddenProperty(prevData));
515
690
  }, [showZeroDcmts, updateHiddenProperty]);
691
+ /**
692
+ * Set initial focused item when showMainDocument = true
693
+ * Focuses on the main document (first inputDcmts) when data is loaded
694
+ * For master mode with invertMasterNavigation=false, focuses on the first master document
695
+ */
696
+ useEffect(() => {
697
+ // Only execute if:
698
+ // 1. showMainDocument is true
699
+ // 2. onFocusedItemChanged callback exists
700
+ // 3. We have tree data
701
+ // 4. We have input documents
702
+ // 5. We haven't already set the initial focus
703
+ if (!showMainDocument || !onFocusedItemChanged || !treeData.length || !inputDcmts?.length || initialFocusSetRef.current) {
704
+ return;
705
+ }
706
+ // Helper function to recursively find the first document with isRoot=true
707
+ const findFirstRootDocument = (items) => {
708
+ for (const item of items) {
709
+ // Check if this item is a document with isRoot=true
710
+ if (item.isDcmt && item.isRoot) {
711
+ return item;
712
+ }
713
+ // Recursively search in children
714
+ if (item.items && Array.isArray(item.items)) {
715
+ const found = findFirstRootDocument(item.items);
716
+ if (found)
717
+ return found;
718
+ }
719
+ }
720
+ return null;
721
+ };
722
+ // Find the first document marked as root (set by setupInitialTreeExpansion)
723
+ const docNode = findFirstRootDocument(treeData);
724
+ if (docNode) {
725
+ // Set the focused item
726
+ onFocusedItemChanged(docNode);
727
+ initialFocusSetRef.current = true;
728
+ }
729
+ }, [treeData, showMainDocument, onFocusedItemChanged, inputDcmts]);
730
+ /**
731
+ * Reset initial focus flag when input documents change
732
+ */
733
+ useEffect(() => {
734
+ initialFocusSetRef.current = false;
735
+ }, [getInputKey()]);
516
736
  /**
517
737
  * Sync static items state when additionalStaticItems change
518
738
  * IMPORTANT: Only update if user hasn't manually interacted with the tree,
@@ -548,6 +768,10 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
548
768
  /**
549
769
  * Calculate items for node when expanded (lazy loading)
550
770
  * Note: additionalStaticItems are already fully loaded, so skip calculation for them
771
+ *
772
+ * PERFORMANCE OPTIMIZATION:
773
+ * - Containers: Return items immediately (no API calls) - items are already loaded
774
+ * - Documents: Load children lazily only when the specific document is expanded (1 API call)
551
775
  */
552
776
  const calculateItemsForNode = useCallback(async (node) => {
553
777
  // Skip calculation for separator (it has no children)
@@ -558,62 +782,129 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
558
782
  if (node.isStaticItem || node.isAdditionalContainer || node.isAdditional) {
559
783
  return node.items;
560
784
  }
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;
785
+ // If already loaded, apply current visualization logic without re-fetching
786
+ if (node.isLoaded && node.items) {
787
+ // Apply current show/hide logic based on showZeroDcmts
788
+ const updatedItems = updateHiddenProperty(node.items);
789
+ // If this is a container, check if we need to show info message
790
+ if (node.isContainer) {
791
+ // Remove any existing info messages
792
+ const filteredItems = updatedItems.filter(child => !(child.isInfoMessage && child.key?.startsWith('__info__')));
793
+ // Check if all real children are hidden
794
+ const realChildren = filteredItems.filter(child => !child.isInfoMessage);
795
+ const allChildrenHidden = realChildren.length > 0 && realChildren.every(child => child.hidden);
796
+ // If showZeroDcmts is false and all items are hidden, show info message + keep hidden containers
797
+ if (!showZeroDcmts && allChildrenHidden) {
798
+ return [
799
+ {
800
+ key: `__info__${node.key}`,
801
+ name: 'Nessun documento correlato da visualizzare',
802
+ isContainer: false,
803
+ isDcmt: false,
804
+ isInfoMessage: true,
805
+ isExpandible: false
806
+ },
807
+ ...filteredItems // Keep hidden containers so they can be shown when toggling
808
+ ];
603
809
  }
604
- newItems.push(item);
605
810
  }
606
- return newItems;
811
+ return updatedItems;
607
812
  }
608
- catch (error) {
609
- console.error('Error loading folder contents:', error);
610
- return node.items;
813
+ // ============================================
814
+ // CONTAINER: Show items immediately (no API calls)
815
+ // Items are already loaded from initial load or parent expansion
816
+ // ============================================
817
+ if (node.isContainer) {
818
+ // No API calls needed - just return existing items
819
+ // Children of these items will be loaded lazily when user expands each document
820
+ // Apply updateHiddenProperty to respect current showZeroDcmts setting
821
+ // If node has no items, return empty array
822
+ if (!node.items)
823
+ return [];
824
+ const updatedItems = updateHiddenProperty(node.items);
825
+ // If showZeroDcmts is false and all items are hidden, add info message + keep hidden containers
826
+ if (!showZeroDcmts && updatedItems.length > 0 && updatedItems.every(item => item.hidden)) {
827
+ return [
828
+ {
829
+ key: `__info__${node.key}`,
830
+ name: 'Nessun documento correlato da visualizzare',
831
+ isContainer: false,
832
+ isDcmt: false,
833
+ isInfoMessage: true,
834
+ isExpandible: false
835
+ },
836
+ ...updatedItems // Keep hidden containers so they can be shown when toggling
837
+ ];
838
+ }
839
+ return updatedItems;
611
840
  }
612
- finally {
613
- setShowExpansionWaitPanel(false);
614
- setExpansionAbortController(undefined);
841
+ // ============================================
842
+ // DOCUMENT: Load its children (relation containers) lazily
843
+ // Only makes ONE API call for this specific document
844
+ // ============================================
845
+ if (node.isDcmt && node.tid && node.did) {
846
+ const newAbortController = new AbortController();
847
+ setExpansionAbortController(newAbortController);
848
+ setShowExpansionWaitPanel(true);
849
+ setExpansionWaitPanelMaxValue(1);
850
+ setExpansionWaitPanelValue(0);
851
+ setExpansionWaitPanelText(`Caricamento documenti correlati...`);
852
+ try {
853
+ // Check for abort
854
+ if (newAbortController.signal.aborted) {
855
+ return [];
856
+ }
857
+ // Determine which load function to use based on mode
858
+ let loadedItems = [];
859
+ if (isForMaster && !invertMasterNavigation) {
860
+ // Original mode: detail documents load detail documents (natural detail→detail navigation)
861
+ loadedItems = await getDetailDcmtsAsync(node.tid, node.did, 1);
862
+ }
863
+ else if (isForMaster) {
864
+ // Inverted master mode: load master documents
865
+ loadedItems = await getMasterDcmtsAsync(node.tid, node.did, 1);
866
+ }
867
+ else {
868
+ // Standard mode: load detail documents
869
+ loadedItems = await getDetailDcmtsAsync(node.tid, node.did, 1);
870
+ }
871
+ // Espandi automaticamente il primo container se ci sono documenti correlati
872
+ if (loadedItems.length > 0 && loadedItems[0].isContainer) {
873
+ loadedItems[0].expanded = true;
874
+ }
875
+ // Apply updateHiddenProperty to respect current showZeroDcmts setting
876
+ // This ensures that dynamically loaded nodes respect the visibility rules:
877
+ // - Relation type containers are always visible (even if empty)
878
+ // - Other containers with zero items can be hidden based on showZeroDcmts
879
+ const updatedItems = updateHiddenProperty(loadedItems);
880
+ // If showZeroDcmts is false and all items are hidden, add info message + keep hidden containers
881
+ if (!showZeroDcmts && updatedItems.length > 0 && updatedItems.every(item => item.hidden)) {
882
+ return [
883
+ {
884
+ key: `__info__${node.key}`,
885
+ name: 'Nessun documento correlato da visualizzare',
886
+ isContainer: false,
887
+ isDcmt: false,
888
+ isInfoMessage: true,
889
+ isExpandible: false
890
+ },
891
+ ...updatedItems // Keep hidden containers so they can be shown when toggling
892
+ ];
893
+ }
894
+ return updatedItems;
895
+ }
896
+ catch (error) {
897
+ console.error('Error loading document relations:', error);
898
+ return [];
899
+ }
900
+ finally {
901
+ setShowExpansionWaitPanel(false);
902
+ setExpansionAbortController(undefined);
903
+ }
615
904
  }
616
- }, [isForMaster, invertMasterNavigation, getDetailDcmtsAsync, getMasterDcmtsAsync, updateHiddenProperty]);
905
+ // Default: return existing items
906
+ return node.items;
907
+ }, [isForMaster, invertMasterNavigation, getDetailDcmtsAsync, getMasterDcmtsAsync, updateHiddenProperty, showZeroDcmts]);
617
908
  /**
618
909
  * Default item renderer with metadata display (adapted from TMMasterDetailDcmts.tsx)
619
910
  */
@@ -626,6 +917,18 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
626
917
  e.stopPropagation();
627
918
  onDocumentDoubleClick?.(item.tid, item.did, item.name);
628
919
  };
920
+ // Info message rendering
921
+ if (item.isInfoMessage) {
922
+ return (_jsxs("div", { style: {
923
+ display: 'flex',
924
+ alignItems: 'center',
925
+ gap: '10px',
926
+ height: '32px',
927
+ padding: '6px 0',
928
+ color: '#666',
929
+ fontStyle: 'italic'
930
+ }, children: [_jsx(IconCircleInfo, { fontSize: 20, color: TMColors.iconLight }), _jsx("span", { children: item.name })] }));
931
+ }
629
932
  // Container rendering
630
933
  if (item.isContainer || !item.isDcmt) {
631
934
  const defaultContainerStyle = {
@@ -634,7 +937,7 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
634
937
  gap: '10px',
635
938
  height: '32px',
636
939
  padding: '6px 0',
637
- opacity: item.isZero ? 0.4 : 1,
940
+ opacity: item.isZero ? 0.99 : 1,
638
941
  transition: 'opacity 0.2s ease-in-out'
639
942
  };
640
943
  // Se è il container principale (root) e showMainDocument è false,
@@ -653,6 +956,7 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
653
956
  return (_jsx("div", { style: defaultContainerStyle, children: content }));
654
957
  }
655
958
  // Document rendering with full metadata display
959
+ const isLogicallyDeleted = Number(item.isLogDel) === 1;
656
960
  const defaultDocumentStyle = {
657
961
  minWidth: '90px',
658
962
  width: '100%',
@@ -664,18 +968,23 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
664
968
  alignItems: 'center',
665
969
  cursor: 'pointer',
666
970
  userSelect: 'none',
667
- opacity: item.isZero ? 0.4 : 1,
668
- transition: 'opacity 0.2s ease-in-out'
971
+ opacity: item.isZero ? 0.99 : 1,
972
+ transition: 'opacity 0.2s ease-in-out',
973
+ textDecoration: isLogicallyDeleted ? 'line-through' : 'none'
669
974
  };
670
975
  const documentStyle = customDocumentStyle
671
976
  ? { ...defaultDocumentStyle, ...customDocumentStyle(item) }
672
977
  : defaultDocumentStyle;
978
+ const textDecoration = isLogicallyDeleted ? 'line-through' : 'none';
979
+ const textColor = isLogicallyDeleted ? 'gray' : undefined;
673
980
  const defaultMetadataContent = item.values && (_jsx(StyledDivHorizontal, { style: {
674
981
  fontSize: '1rem',
675
982
  overflow: 'hidden',
676
983
  flex: 1,
677
984
  minWidth: 0,
678
- whiteSpace: 'nowrap'
985
+ whiteSpace: 'nowrap',
986
+ textDecoration: textDecoration,
987
+ color: textColor
679
988
  }, children: getDcmtDisplayValue(item.values).map((key, index) => {
680
989
  const md = item.values?.[key]?.md;
681
990
  const value = item.values?.[key]?.value;
@@ -684,12 +993,16 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
684
993
  return (_jsxs(StyledDivHorizontal, { style: {
685
994
  flexShrink: isLast ? 1 : 0,
686
995
  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: {
996
+ overflow: isLast ? 'hidden' : 'visible',
997
+ textDecoration: textDecoration,
998
+ color: textColor
999
+ }, 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
1000
  fontWeight: 500,
690
1001
  overflow: isLast ? 'hidden' : 'visible',
691
1002
  textOverflow: isLast ? 'ellipsis' : 'clip',
692
- whiteSpace: 'nowrap'
1003
+ whiteSpace: 'nowrap',
1004
+ textDecoration: textDecoration,
1005
+ color: textColor
693
1006
  }, children: value }))] }, `${key}_${index}`));
694
1007
  }) }));
695
1008
  const metadataContent = customDocumentContent
@@ -744,7 +1057,7 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
744
1057
  if (mergedTreeData.length === 0) {
745
1058
  return _jsx("div", { style: { padding: '20px', textAlign: 'center', color: '#666' }, children: "Nessuna relazione disponibile." });
746
1059
  }
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) => {
1060
+ 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
1061
  setTimeout(() => {
749
1062
  abortController?.abort();
750
1063
  }, 100);