@topconsultnpm/sdkui-react 6.22.0-dev2.2 → 6.22.0-dev2.21

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 (109) hide show
  1. package/lib/components/NewComponents/ContextMenu/TMContextMenu.js +1 -1
  2. package/lib/components/NewComponents/FloatingMenuBar/TMFloatingMenuBar.js +1 -1
  3. package/lib/components/base/TMButton.d.ts +13 -0
  4. package/lib/components/base/TMButton.js +137 -5
  5. package/lib/components/base/TMDataGrid.d.ts +2 -0
  6. package/lib/components/base/TMDataGrid.js +68 -4
  7. package/lib/components/base/TMEditorBase.d.ts +2 -0
  8. package/lib/components/base/TMModal.d.ts +4 -0
  9. package/lib/components/base/TMModal.js +42 -11
  10. package/lib/components/base/TMPanel.d.ts +1 -0
  11. package/lib/components/base/TMPanel.js +79 -20
  12. package/lib/components/base/TMTooltip.d.ts +2 -1
  13. package/lib/components/base/TMTooltip.js +23 -6
  14. package/lib/components/base/TMTreeView.js +108 -67
  15. package/lib/components/choosers/TMDcmtTypeChooser.js +4 -3
  16. package/lib/components/choosers/TMDistinctValues.d.ts +9 -7
  17. package/lib/components/choosers/TMDistinctValues.js +149 -142
  18. package/lib/components/choosers/TMMetadataChooser.js +1 -1
  19. package/lib/components/choosers/TMQuickSearchInfo.d.ts +12 -0
  20. package/lib/components/choosers/TMQuickSearchInfo.js +279 -0
  21. package/lib/components/choosers/TMQuickSearchSettingsForm.d.ts +16 -0
  22. package/lib/components/choosers/TMQuickSearchSettingsForm.js +94 -0
  23. package/lib/components/choosers/TMSelectedValuesSummary.d.ts +13 -0
  24. package/lib/components/choosers/TMSelectedValuesSummary.js +91 -0
  25. package/lib/components/editors/TMDropDown.d.ts +3 -0
  26. package/lib/components/editors/TMDropDown.js +197 -5
  27. package/lib/components/editors/TMMetadataEditor.d.ts +5 -1
  28. package/lib/components/editors/TMMetadataEditor.js +8 -3
  29. package/lib/components/editors/TMTextBox.js +15 -15
  30. package/lib/components/features/documents/TMBatchUpdateForm.js +19 -5
  31. package/lib/components/features/documents/TMDcmtForm.d.ts +7 -0
  32. package/lib/components/features/documents/TMDcmtForm.js +104 -42
  33. package/lib/components/features/documents/TMDcmtFormActionButtons.d.ts +11 -0
  34. package/lib/components/features/documents/TMDcmtFormActionButtons.js +74 -7
  35. package/lib/components/features/documents/TMMasterDetailDcmts.js +95 -33
  36. package/lib/components/features/documents/TMMasterInfoFields.d.ts +19 -0
  37. package/lib/components/features/documents/TMMasterInfoFields.js +172 -0
  38. package/lib/components/features/documents/TMMultiMasterDetailDcmtsForm.d.ts +40 -0
  39. package/lib/components/features/documents/TMMultiMasterDetailDcmtsForm.js +277 -0
  40. package/lib/components/features/documents/TMMultiMasterDetailDcmtsUtils.d.ts +354 -0
  41. package/lib/components/features/documents/TMMultiMasterDetailDcmtsUtils.js +234 -0
  42. package/lib/components/features/documents/TMRelationViewer.d.ts +14 -1
  43. package/lib/components/features/documents/TMRelationViewer.js +69 -20
  44. package/lib/components/features/search/TMSearch.js +29 -16
  45. package/lib/components/features/search/TMSearchQueryEditor.js +5 -3
  46. package/lib/components/features/search/TMSearchQueryPanel.d.ts +1 -1
  47. package/lib/components/features/search/TMSearchQueryPanel.js +28 -7
  48. package/lib/components/features/search/TMSearchResult.d.ts +9 -0
  49. package/lib/components/features/search/TMSearchResult.js +323 -92
  50. package/lib/components/features/search/TMTreeSelector.js +18 -5
  51. package/lib/components/features/workflow/TMWorkflowPopup.d.ts +10 -0
  52. package/lib/components/features/workflow/TMWorkflowPopup.js +8 -4
  53. package/lib/components/layout/panelManager/TMPanelManagerContainer.d.ts +1 -0
  54. package/lib/components/layout/panelManager/TMPanelManagerContainer.js +24 -14
  55. package/lib/components/layout/panelManager/TMPanelWrapper.js +2 -2
  56. package/lib/components/layout/panelManager/types.d.ts +3 -0
  57. package/lib/components/viewers/TMMidViewer.js +4 -1
  58. package/lib/components/viewers/TMTidViewer.js +25 -11
  59. package/lib/helper/SDKUI_Globals.d.ts +55 -0
  60. package/lib/helper/SDKUI_Globals.js +82 -0
  61. package/lib/helper/SDKUI_Localizator.d.ts +24 -0
  62. package/lib/helper/SDKUI_Localizator.js +240 -0
  63. package/lib/helper/TMIcons.d.ts +1 -0
  64. package/lib/helper/TMIcons.js +3 -0
  65. package/lib/helper/dataGridSearchHelper.d.ts +28 -0
  66. package/lib/helper/dataGridSearchHelper.js +51 -0
  67. package/lib/helper/dcmtsHelper.d.ts +20 -0
  68. package/lib/helper/dcmtsHelper.js +58 -1
  69. package/lib/helper/helpers.d.ts +20 -0
  70. package/lib/helper/helpers.js +40 -0
  71. package/lib/helper/index.d.ts +1 -0
  72. package/lib/helper/index.js +1 -0
  73. package/lib/helper/queryHelper.d.ts +2 -0
  74. package/lib/helper/queryHelper.js +3 -1
  75. package/lib/hooks/tmDistinctValuesGridHelper.d.ts +16 -0
  76. package/lib/hooks/tmDistinctValuesGridHelper.js +31 -0
  77. package/lib/hooks/useArchiveListForm.d.ts +39 -0
  78. package/lib/hooks/useArchiveListForm.js +46 -0
  79. package/lib/hooks/useCaseFlowApprove.d.ts +56 -0
  80. package/lib/hooks/useCaseFlowApprove.js +157 -0
  81. package/lib/hooks/useDataUserIdItem.d.ts +1 -0
  82. package/lib/hooks/useDataUserIdItem.js +1 -0
  83. package/lib/hooks/useDocumentOperations.d.ts +2 -0
  84. package/lib/hooks/useDocumentOperations.js +28 -5
  85. package/lib/hooks/useMultiMasterDetailDcmts.d.ts +98 -0
  86. package/lib/hooks/useMultiMasterDetailDcmts.js +567 -0
  87. package/lib/hooks/useRelatedDocuments.js +2 -26
  88. package/lib/hooks/useTMDistinctValuesMetadataDisplay.d.ts +18 -0
  89. package/lib/hooks/useTMDistinctValuesMetadataDisplay.js +103 -0
  90. package/lib/hooks/useTMDistinctValuesQuickSearch.d.ts +49 -0
  91. package/lib/hooks/useTMDistinctValuesQuickSearch.js +307 -0
  92. package/lib/hooks/useTMDistinctValuesSelection.d.ts +42 -0
  93. package/lib/hooks/useTMDistinctValuesSelection.js +103 -0
  94. package/lib/hooks/useTMDistinctValuesSource.d.ts +29 -0
  95. package/lib/hooks/useTMDistinctValuesSource.js +105 -0
  96. package/lib/index.d.ts +1 -0
  97. package/lib/index.js +1 -0
  98. package/lib/services/caseflow/CaseFlowService.d.ts +236 -0
  99. package/lib/services/caseflow/CaseFlowService.js +693 -0
  100. package/lib/services/caseflow/CaseFlowTemplateCacheService.d.ts +28 -0
  101. package/lib/services/caseflow/CaseFlowTemplateCacheService.js +121 -0
  102. package/lib/services/caseflow/caseFlowNameCache.d.ts +32 -0
  103. package/lib/services/caseflow/caseFlowNameCache.js +116 -0
  104. package/lib/services/caseflow/caseFlowWorkItemUtils.d.ts +61 -0
  105. package/lib/services/caseflow/caseFlowWorkItemUtils.js +113 -0
  106. package/lib/services/index.d.ts +4 -0
  107. package/lib/services/index.js +4 -0
  108. package/lib/services/platform_services.d.ts +1 -1
  109. package/package.json +66 -66
@@ -3,11 +3,11 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
3
3
  import styled from 'styled-components';
4
4
  import { LoadIndicator } from 'devextreme-react';
5
5
  import { AppModules, DataColumnTypes, DataListViewModes, DcmtTypeListCacheService, LayoutCacheService, LayoutModes, MetadataDataDomains, SDK_Globals, SystemMIDsAsNumber, UserListCacheService, } from '@topconsultnpm/sdk-ts';
6
- import { deepCompare, formatDateTimeByMetadataFormat, formatNumberByMetadataFormat, generateUniqueColumnKeys, genUniqueId, getColumnFormatInfo, getSearchToolbarVisibility, IconAll, IconBoard, IconDcmtTypeSys, IconDelete, IconMenuVertical, IconPlatform, IconRefresh, IconSearchCheck, IconShow, isApprovalWorkflowView, isSign4TopEnabled, searchResultDescriptorToSimpleArray, searchResultToMetadataValues, SDKUI_Globals, SDKUI_Localizator } from '../../../helper';
6
+ import { customizeSearchByDisplayText, deepCompare, formatDateTimeByMetadataFormat, formatNumberByMetadataFormat, generateUniqueColumnKeys, genUniqueId, getColumnFormatInfo, getDateDisplayText, getNumberDisplayText, getSearchToolbarVisibility, IconAll, IconBoard, IconDcmtTypeSys, IconDelete, IconDuplicate, IconEdit, IconMenuVertical, IconPlatform, IconRefresh, IconSearchCheck, IconShow, isApprovalWorkflowView, isSign4TopEnabled, searchResultDescriptorToSimpleArray, searchResultToMetadataValues, SDKUI_Globals, SDKUI_Localizator, IconCloseOutline } from '../../../helper';
7
7
  import { CounterItemKey } from '../../base/TMCounterContainer';
8
8
  import { getDcmtCicoStatus } from '../../../helper/checkinCheckoutManager';
9
9
  import { DcmtOperationTypes, SearchResultContext, } from '../../../ts';
10
- import { Gutters } from '../../../utils/theme';
10
+ import { Gutters, TMColors } from '../../../utils/theme';
11
11
  import { useDataListItem } from '../../../hooks/useDataListItem';
12
12
  import { useDataUserIdItem } from '../../../hooks/useDataUserIdItem';
13
13
  import { getSelectedDcmtsOrFocused, useDocumentOperations } from '../../../hooks/useDocumentOperations';
@@ -55,9 +55,11 @@ const TMSearchResult = ({
55
55
  // Data
56
56
  groupId, searchResults = [], context = SearchResultContext.METADATA_SEARCH, title, selectedSearchResultTID, floatingActionConfig, workingGroupContext = undefined, inputDID,
57
57
  // Boolean flags to enable/disable features
58
- autoFocusFirstRow = true, formAutoOpen, isVisible = true, allowRelations = true, openDcmtFormAsModal = false, showSearchResultSidebar = true, showDcmtFormSidebar = true, showSelector = false, isClosable = false, allowFloatingBar = true, showToolbarHeader = true, showBackButton = true, disableAccordionIfSingleCategory = false, editPdfForm = false, openS4TViewer = false, showTodoDcmtForm = false, showToppyDraggableHelpCenter = true, toppyHelpCenterUsePortal = false, showNoDcmtFoundMessage = true, enablePinIcons = true,
58
+ autoFocusFirstRow = true, formAutoOpen, isVisible = true, allowRelations = true, openDcmtFormAsModal = false, showSearchResultSidebar = true, showDcmtFormSidebar = true, showSelector = false, isClosable = false, allowFloatingBar = true, showToolbarHeader = true, showBackButton = true, showToolbarCloseButton = false, disableAccordionIfSingleCategory = false, editPdfForm = false, openS4TViewer = false, showTodoDcmtForm = false, showToppyDraggableHelpCenter = true, toppyHelpCenterUsePortal = false, showNoDcmtFoundMessage = true, enablePinIcons = true, showCounterConfig = true, showActionsColumn = false, compactPager = false,
59
+ // Counters
60
+ counterConfig,
59
61
  // Callbacks (optional)
60
- openInOffice, onRefreshAfterAddDcmtToFavs, onRefreshSearchAsyncDatagrid, onSelectedTIDChanged, onWFOperationCompleted, onClose, onFileOpened, onTaskCreateRequest, openWGsCopyMoveForm, openCommentFormCallback, openAddDocumentForm, onOpenS4TViewerRequest, onOpenPdfEditorRequest, openFileUploaderPdfEditor, passToArchiveCallback, onReferenceClick,
62
+ openInOffice, onRefreshAfterAddDcmtToFavs, onRefreshSearchAsyncDatagrid, onSelectedTIDChanged, onSelectedTIDCleared, onWFOperationCompleted, onClose, onFileOpened, onTaskCreateRequest, openWGsCopyMoveForm, openCommentFormCallback, openAddDocumentForm, onOpenS4TViewerRequest, onOpenPdfEditorRequest, openFileUploaderPdfEditor, passToArchiveCallback, onReferenceClick,
61
63
  // Tasks
62
64
  allTasks = [], getAllTasks, deleteTaskByIdsCallback, addTaskCallback, editTaskCallback,
63
65
  // Navigation
@@ -69,13 +71,19 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
69
71
  // Quando l'utente seleziona manualmente, il ref viene settato a false e il focus automatico
70
72
  // torna al comportamento normale.
71
73
  const isFirstAutoFocusRef = useRef(true);
74
+ // Mostra il pannello lista. È una ref perché il TMPanelManagerProvider sta dentro al JSX di
75
+ // questo componente, quindi qui il context non si può usare: la riempie ShowMainPanelBridge.
76
+ const showMainPanelRef = useRef(null);
72
77
  // ID state to force remount of components
73
78
  const [id, setID] = useState('');
74
79
  // Document type descriptor of the currently document
75
80
  const [fromDTD, setFromDTD] = useState();
76
81
  const [currentDcmt, setCurrentDcmt] = useState();
77
- // State to specify if the details form is open or not
78
- const [splitterSize, setSplitterSize] = useState(['100%', '0']);
82
+ // Passo corrente. Su mobile decide anche quale pannello è a schermo (vedi isSteppedLayout).
83
+ // 'auto' -> l'utente non ha ancora scelto: si può selezionare il primo tipo documento
84
+ // 'selector' -> è tornato indietro col back: non selezionare niente al posto suo
85
+ // 'results' -> è sulla griglia del tipo scelto
86
+ const [navStep, setNavStep] = useState('auto');
79
87
  // State to control the selected search result
80
88
  const [selectedSearchResult, setSelectedSearchResult] = useState();
81
89
  // State to control the search results to show in the datagrid
@@ -303,7 +311,7 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
303
311
  createTaskFromDocumentOrWorkItem();
304
312
  }
305
313
  };
306
- const { operationItems, renderFloatingBar, renderDcmtOperations, features, } = useDocumentOperations({
314
+ const { operationItems, selectedDcmtInfos, renderFloatingBar, renderDcmtOperations, features, } = useDocumentOperations({
307
315
  context: context,
308
316
  documentData: {
309
317
  dtd: fromDTD,
@@ -377,9 +385,60 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
377
385
  openTaskFormHandler,
378
386
  },
379
387
  });
380
- const { isOpenDcmtForm, openFormHandler, dcmtFormLayoutMode, onDcmtFormOpenChange, showSearchTMDatagrid, showExportForm, isOpenBatchUpdate, isModifiedBatchUpdate, updateBatchUpdateForm, closeDcmtFormHandler, handleSignApprove, checkoutInfo: { showCicoWaitPanel, cicoWaitPanelTitle, showCicoPrimaryProgress, cicoPrimaryProgressText, cicoPrimaryProgressValue, cicoPrimaryProgressMax, }, dcmtOperations: { abortController, showWaitPanel, showPrimary, waitPanelTitle, waitPanelTextPrimary, waitPanelValuePrimary, waitPanelMaxValuePrimary, showSecondary, waitPanelTextSecondary, waitPanelValueSecondary, waitPanelMaxValueSecondary, downloadDcmtsAsync, runOperationAsync, }, relatedDocumentsInfo: { isOpenDetails, isOpenMaster, checkRelatedDcmtsArchiveCapability, checkManyToManyCapability, }, toppyOperations: { showApprovePopup, showRejectPopup, showReAssignPopup, showMoreInfoPopup, updateShowApprovePopup, updateShowRejectPopup, updateShowReAssignPopup, updateShowMoreInfoPopup } } = features;
388
+ const { isOpenDcmtForm, openFormHandler, dcmtFormLayoutMode, onDcmtFormOpenChange, showSearchTMDatagrid, showDcmtTypesSelection, showExportForm, updateBatchUpdateForm, closeDcmtFormHandler, handleSignApprove, checkoutInfo: { showCicoWaitPanel, cicoWaitPanelTitle, showCicoPrimaryProgress, cicoPrimaryProgressText, cicoPrimaryProgressValue, cicoPrimaryProgressMax, }, dcmtOperations: { abortController, showWaitPanel, showPrimary, waitPanelTitle, waitPanelTextPrimary, waitPanelValuePrimary, waitPanelMaxValuePrimary, showSecondary, waitPanelTextSecondary, waitPanelValueSecondary, waitPanelMaxValueSecondary, downloadDcmtsAsync, runOperationAsync, }, relatedDocumentsInfo: { isOpenDetails, isOpenMaster, checkRelatedDcmtsArchiveCapability, checkManyToManyCapability, }, toppyOperations: { showApprovePopup, showRejectPopup, showReAssignPopup, showMoreInfoPopup, updateShowApprovePopup, updateShowRejectPopup, updateShowReAssignPopup, updateShowMoreInfoPopup } } = features;
389
+ // ----- Azioni della colonna di riga (showActionsColumn) -----------------------------------------
390
+ // Le voci di operationItems agiscono sui documenti selezionati o, in mancanza, su quello a fuoco:
391
+ // non accettano una riga come argomento. Il click sull'icona quindi azzera la selezione e porta il
392
+ // fuoco sulla riga cliccata, poi attende. L'attesa non è sul fuoco ma su selectedDcmtInfos, che
393
+ // l'hook ricalcola in un effect (un render più tardi): lanciare l'operazione prima significherebbe
394
+ // eseguirla sulla riga precedente.
395
+ const pendingRowActionRef = useRef(undefined);
396
+ const requestRowAction = useCallback((operationId, rowData) => {
397
+ if (!showActionsColumn)
398
+ return;
399
+ if (rowData?.TID === undefined || rowData?.DID === undefined) {
400
+ ShowAlert({ message: SDKUI_Localizator.InvalidDcmt, mode: "warning", duration: 3000 });
401
+ return;
402
+ }
403
+ pendingRowActionRef.current = { operationId, TID: rowData.TID, DID: rowData.DID };
404
+ isFirstAutoFocusRef.current = false;
405
+ setSelectedItems([]);
406
+ setFocusedItem(rowData);
407
+ }, [showActionsColumn]);
408
+ // La richiesta viene scartata se il documento sparisce dai risultati (es. refresh dopo
409
+ // un'eliminazione): senza questo resterebbe appesa e partirebbe al prossimo cambio di fuoco.
410
+ useEffect(() => { pendingRowActionRef.current = undefined; }, [selectedSearchResult, lastUpdateSearchTime]);
411
+ useEffect(() => {
412
+ if (!showActionsColumn)
413
+ return;
414
+ const pending = pendingRowActionRef.current;
415
+ if (!pending)
416
+ return;
417
+ // L'hook non ha ancora recepito la riga richiesta: si riproverà al prossimo aggiornamento
418
+ if (selectedDcmtInfos.length !== 1)
419
+ return;
420
+ if (selectedDcmtInfos[0].TID !== pending.TID || selectedDcmtInfos[0].DID !== pending.DID)
421
+ return;
422
+ pendingRowActionRef.current = undefined;
423
+ const operation = findOperationItemById(operationItems, pending.operationId);
424
+ // Ora che il fuoco è sulla riga, `disabled` riflette i permessi per quel documento
425
+ if (!operation || operation.disabled || operation.visible === false)
426
+ return;
427
+ operation.onClick?.();
428
+ }, [showActionsColumn, selectedDcmtInfos, operationItems]);
381
429
  const deviceType = useDeviceType();
382
430
  const isMobile = deviceType === DeviceType.MOBILE;
431
+ const hasSingleDcmtType = currentSearchResults.length === 1;
432
+ // Su mobile si vede un pannello per volta; su desktop selettore e griglia sono affiancati
433
+ const isSteppedLayout = showSelector && isMobile;
434
+ // Un solo tipo documento su desktop: non c'è niente da scegliere, quindi il selettore sparisce
435
+ // (larghezza 0, nessun separatore) e lo spazio va tutto alla griglia. Il menu "Altro" permette
436
+ // di riaprirlo (vedi canToggleDcmtTypesSelection nell'hook).
437
+ const isSelectorCollapsed = showSelector && !isSteppedLayout && hasSingleDcmtType && !showDcmtTypesSelection;
438
+ // Senza selettore lo spazio va tutto alla griglia; altrimenti a step su mobile, affiancati su desktop
439
+ const splitterSizes = (!showSelector || isSelectorCollapsed) ? ['0%', '100%']
440
+ : isSteppedLayout ? (navStep === 'results' ? ['0', '100%'] : ['100%', '0'])
441
+ : ['30%', '70%'];
383
442
  const selectedDocs = getSelectedDcmtsOrFocused(selectedItems, focusedItem);
384
443
  // Disable the "Sign/Approve" button if:
385
444
  // 1. No document or multiple documents are selected, OR
@@ -400,8 +459,14 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
400
459
  setSelectedSearchResult(undefined);
401
460
  return;
402
461
  }
462
+ // Un solo tipo documento: selezionalo e vai ai risultati, a meno che l'utente sia tornato indietro
403
463
  if (searchResults.length === 1) {
464
+ if (navStep === 'selector') {
465
+ setSelectedSearchResult(undefined);
466
+ return;
467
+ }
404
468
  setSelectedSearchResult(searchResults[0]);
469
+ setNavStep('results');
405
470
  return;
406
471
  }
407
472
  // Se il TID precedentemente selezionato esiste ancora nei nuovi risultati, mantienilo.
@@ -415,6 +480,11 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
415
480
  return;
416
481
  }
417
482
  }
483
+ // L'utente è sul selettore
484
+ if (isMobile && navStep !== 'results') {
485
+ setSelectedSearchResult(undefined);
486
+ return;
487
+ }
418
488
  // Altrimenti seleziona il primo risultato ordinato
419
489
  setSelectedSearchResult(orderByName(searchResults)[0]);
420
490
  }, [searchResults]);
@@ -471,7 +541,9 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
471
541
  return;
472
542
  if (focusedItem?.DID != inputDID)
473
543
  return;
474
- openFormHandler(LayoutModes.Update);
544
+ if (focusedItem?.TID === undefined)
545
+ return;
546
+ onDcmtFormOpenChange(true, LayoutModes.Update);
475
547
  }, [inputDID, formAutoOpen, focusedItem]);
476
548
  // Quando openS4TViewer è true, apri automaticamente il viewer S4T
477
549
  useEffect(() => {
@@ -531,47 +603,75 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
531
603
  let counters = (showSelector && disableAccordionIfSingleCategory && searchResults.length > 1) ? getSearchResultCountersSingleCategory(searchResults) : "";
532
604
  if (title)
533
605
  return title + counters;
534
- let titleHeader = 'Ricerca per metadati';
606
+ // Nome del tipo documento selezionato, accodato al titolo
607
+ const selectedDcmtTypeName = selectedSearchResult?.fromName ?? (searchResults?.length === 1 ? searchResults[0]?.fromName : undefined);
608
+ const withSelectedDcmtTypeName = (baseTitle) => selectedDcmtTypeName ? `${baseTitle} - ${selectedDcmtTypeName}` : baseTitle;
609
+ let titleHeader = withSelectedDcmtTypeName('Ricerca per metadati');
535
610
  switch (context) {
536
611
  case SearchResultContext.MASTER_DETAIL:
537
612
  case SearchResultContext.METADATA_SEARCH:
538
613
  titleHeader = `${searchResults?.length > 1 ? selectedSearchResult?.fromName ?? 'Ricerca per metadati' : searchResults[0]?.fromName ?? 'Ricerca per metadati'}`;
539
614
  break;
540
615
  case SearchResultContext.FAVORITES_AND_RECENTS:
541
- titleHeader = SDKUI_Localizator.FavoritesAndRecentDcmts;
616
+ titleHeader = withSelectedDcmtTypeName(SDKUI_Localizator.FavoritesAndRecentDcmts);
542
617
  break;
543
618
  case SearchResultContext.WORKFLOW_APPROVE:
544
- titleHeader = SDKUI_Localizator.WorkflowApproval;
619
+ titleHeader = withSelectedDcmtTypeName(SDKUI_Localizator.WorkflowApproval);
545
620
  break;
546
621
  case SearchResultContext.FREE_SEARCH:
547
- titleHeader = `Risultato della ricerca full text`;
622
+ titleHeader = withSelectedDcmtTypeName('Risultato della ricerca full text');
548
623
  break;
549
624
  }
550
625
  return titleHeader + counters;
551
626
  };
552
- const onBack = () => {
553
- if (isOpenBatchUpdate && isModifiedBatchUpdate) {
554
- TMMessageBoxManager.show({
555
- title: SDKUI_Localizator.Cancel, message: SDKUI_Localizator.ConfirmOnCancel, buttons: [ButtonNames.YES, ButtonNames.NO],
556
- onButtonClick: (e) => {
557
- if (e !== ButtonNames.YES)
558
- return;
559
- setSplitterSize(['100%', '0']);
560
- onClose?.();
561
- }
562
- });
563
- }
564
- else if (isOpenDcmtForm) {
565
- onDcmtFormOpenChange(false, LayoutModes.Update);
566
- }
567
- else {
568
- if (isMobile && currentSearchResults.length > 1) {
569
- setSelectedSearchResult(undefined);
570
- }
571
- setSplitterSize(['100%', '0']);
572
- onClose?.();
627
+ // Dai risultati si risale al selettore dei tipi documento
628
+ const canGoBackToSelector = isSteppedLayout && navStep === 'results' && selectedSearchResult !== undefined;
629
+ /**
630
+ * Back di tutti i pannelli (e X di chiusura se isClosable). Su mobile risale un livello alla volta:
631
+ * pannello secondario -> lista dei risultati -> selettore dei tipi documento -> uscita.
632
+ * Su desktop i pannelli sono affiancati, quindi resta solo l'uscita.
633
+ *
634
+ * @param fromSecondaryPanel - true se il back arriva da un pannello secondario
635
+ */
636
+ const onBack = useCallback((fromSecondaryPanel = false) => {
637
+ // Non è previsto nessun modo di uscire: non fare niente
638
+ if (!showBackButton && !isClosable)
639
+ return;
640
+ // Torna alla lista senza toccare gli stati: l'utente deve ritrovare la sua selezione
641
+ if (fromSecondaryPanel) {
642
+ showMainPanelRef.current?.();
643
+ return;
573
644
  }
574
- };
645
+ // Sia il ritorno al selettore sia l'uscita ripartono da una lista pulita
646
+ setSelectedSearchResult(undefined);
647
+ setFocusedItem(undefined);
648
+ setSelectedItems([]);
649
+ setSelectedRowKeysFromDataGrid([]);
650
+ setCurrentDcmt(undefined);
651
+ setCurrentMetadataValues([]);
652
+ setShowIndexingInfo(false);
653
+ isFirstAutoFocusRef.current = true;
654
+ // Il chiamante può tenere allineato il suo TID selezionato (es. selectedSearchResultTID)
655
+ onSelectedTIDCleared?.();
656
+ // [2] l'utente ha chiesto la lista: resta lì finché non sceglie lui, niente auto-selezione.
657
+ // [1] si esce: si riparte da capo, quindi l'auto-selezione torna permessa.
658
+ setNavStep(canGoBackToSelector ? 'selector' : 'auto');
659
+ // [2] C'è ancora un livello sopra: restiamo dentro, sul selettore
660
+ if (canGoBackToSelector)
661
+ return;
662
+ // [1] Si esce e si avvisa il chiamante
663
+ onClose?.();
664
+ }, [canGoBackToSelector, showBackButton, isClosable, onClose, onSelectedTIDCleared]);
665
+ /**
666
+ * Back del pannello lista. `undefined` = freccia non renderizzata, quindi la mostriamo solo se
667
+ * ha dove tornare: uno step intermedio, o un onClose per uscire. Con `isClosable` basta la X.
668
+ */
669
+ const backHandler = (showBackButton && !isClosable && (canGoBackToSelector || onClose !== undefined)) ? onBack : undefined;
670
+ /**
671
+ * Back dei pannelli secondari: la stessa onBack marcata come livello [3]. Solo su mobile:
672
+ * su desktop stanno affiancati alla lista e non c'è niente da cui tornare.
673
+ */
674
+ const backHandlerSecondary = useMemo(() => (isMobile && backHandler) ? () => backHandler(true) : undefined, [isMobile, backHandler]);
575
675
  const refreshDataGridAfterRemoveAsync = async () => {
576
676
  let index = selectedSearchResult?.dtdResult?.columns?.findIndex(col => col.caption === 'DID');
577
677
  let selectedRows = [];
@@ -603,21 +703,6 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
603
703
  return newResults;
604
704
  });
605
705
  };
606
- /**
607
- * Restituisce l'handler onBack per i pannelli, evitando duplicazione di codice.
608
- * @param isMainPanel - Se true, è il pannello principale dei risultati di ricerca
609
- */
610
- const getOnBackHandler = (isMainPanel = false) => {
611
- // Se showBackButton è false, non mostrare mai il back
612
- if (showBackButton === false)
613
- return undefined;
614
- // Mobile: mostra back su tutti i pannelli (se showBackButton è definito e non è closable)
615
- if (isMobile) {
616
- return (showBackButton !== undefined && !isClosable) ? onBack : undefined;
617
- }
618
- // Desktop: mostra back solo sul pannello principale dei risultati (se showBackButton è definito e non è closable)
619
- return (showBackButton !== undefined && isMainPanel && !isClosable) ? onBack : undefined;
620
- };
621
706
  const removeDcmtFromFavsOrRecents = async () => {
622
707
  switch (selectedSearchResult?.category) {
623
708
  case "Favorites":
@@ -634,9 +719,9 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
634
719
  };
635
720
  const onSearchResultSelectionChanged = (e) => {
636
721
  setSelectedSearchResult(e);
637
- if (deviceType === DeviceType.MOBILE) {
638
- setSplitterSize(['0', '100%']);
639
- }
722
+ // Scelto il tipo documento si passa ai risultati (a livello di layout è ignorato se i due
723
+ // step sono affiancati, ma vale comunque come "l'utente non è più fermo sul selettore")
724
+ setNavStep('results');
640
725
  onSelectedTIDChanged?.(e.fromTID);
641
726
  };
642
727
  const showToppyForApprove = useMemo(() => {
@@ -660,14 +745,15 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
660
745
  && !showTodoDcmtForm);
661
746
  }, [showToppyForApprove, showToppyDraggableHelpCenter, selectedDocs, showApprovePopup, showRejectPopup, showReAssignPopup, showMoreInfoPopup, editPdfForm, openS4TViewer, showTodoDcmtForm]);
662
747
  const searchResutlToolbar = _jsxs(_Fragment, { children: [context === SearchResultContext.FAVORITES_AND_RECENTS &&
663
- _jsx("div", { style: { display: 'flex', alignItems: 'center', gap: '5px' }, children: _jsx(TMButton, { btnStyle: 'icon', icon: _jsx(IconDelete, { color: 'white' }), caption: "Rimuovi da " + (selectedSearchResult?.category === "Favorites" ? '"Preferiti"' : '"Recenti"'), disabled: getSelectedDcmtsOrFocused(selectedItems, focusedItem).length <= 0, onClick: removeDcmtFromFavsOrRecents }) }), _jsx(TMButton, { btnStyle: 'icon', icon: _jsx(IconRefresh, { color: 'white' }), caption: SDKUI_Localizator.Refresh, onClick: onRefreshSearchAsyncDatagrid }), _jsx(TMContextMenu, { items: operationItems, trigger: "left", children: _jsx(IconMenuVertical, { color: 'white', cursor: 'pointer' }) })] });
748
+ _jsx("div", { style: { display: 'flex', alignItems: 'center', gap: '5px' }, children: _jsx(TMButton, { btnStyle: 'icon', icon: _jsx(IconDelete, { color: 'white' }), caption: "Rimuovi da " + (selectedSearchResult?.category === "Favorites" ? '"Preferiti"' : '"Recenti"'), disabled: getSelectedDcmtsOrFocused(selectedItems, focusedItem).length <= 0, onClick: removeDcmtFromFavsOrRecents }) }), _jsx(TMButton, { btnStyle: 'icon', icon: _jsx(IconRefresh, { color: 'white' }), caption: SDKUI_Localizator.Refresh, onClick: onRefreshSearchAsyncDatagrid }), _jsx(TMContextMenu, { items: operationItems, trigger: "left", children: _jsx(IconMenuVertical, { color: 'white', cursor: 'pointer' }) }), showToolbarCloseButton && onClose &&
749
+ _jsx(TMButton, { btnStyle: 'icon', icon: _jsx(IconCloseOutline, { color: 'white' }), caption: SDKUI_Localizator.Close, onClick: onClose })] });
664
750
  const tmSearchResult = useMemo(() => (!searchResults || searchResults.length <= 0)
665
751
  ? _jsxs("div", { style: { display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', height: '100%', width: '100%' }, children: [_jsx(IconBoard, { fontSize: 96 }), showNoDcmtFoundMessage && _jsx("div", { style: { fontSize: "15px", marginTop: "10px" }, children: SDKUI_Localizator.NoDcmtFound }), openAddDocumentForm && _jsx("div", { style: { marginTop: "10px" }, children: _jsx(TMButton, { fontSize: "15px", icon: _jsx("i", { className: 'dx-icon-share' }), caption: SDKUI_Localizator.Share, onClick: openAddDocumentForm }) })] })
666
752
  :
667
- _jsxs(_Fragment, { children: [_jsxs(TMLayoutItem, { height: '100%', children: [_jsxs("div", { ref: floatingBarContainerRef, style: { position: 'relative', height: '100%', width: '100%' }, children: [_jsxs(TMSplitterLayout, { direction: 'horizontal', overflow: 'visible', separatorSize: Gutters.getGutters(), separatorActiveColor: 'transparent', separatorColor: 'transparent', min: ['0', '0'], showSeparator: showSelector && deviceType !== DeviceType.MOBILE, start: showSelector ? deviceType !== DeviceType.MOBILE ? ['30%', '70%'] : splitterSize : ['0%', '100%'], children: [showSelector ?
668
- _jsx(TMLayoutItem, { children: _jsx(TMSearchResultSelector, { searchResults: currentSearchResults, disableAccordionIfSingleCategory: disableAccordionIfSingleCategory, selectedTID: selectedSearchResultTID, selectedSearchResult: selectedSearchResult, autoSelectFirst: !isMobile || currentSearchResults.length === 1, onSelectionChanged: onSearchResultSelectionChanged }) })
753
+ _jsxs(_Fragment, { children: [_jsxs(TMLayoutItem, { height: '100%', children: [_jsxs("div", { ref: floatingBarContainerRef, style: { position: 'relative', height: '100%', width: '100%' }, children: [_jsxs(TMSplitterLayout, { direction: 'horizontal', overflow: 'visible', separatorSize: Gutters.getGutters(), separatorActiveColor: 'transparent', separatorColor: 'transparent', min: ['0', '0'], showSeparator: showSelector && !isSteppedLayout && !isSelectorCollapsed, start: splitterSizes, children: [showSelector ?
754
+ _jsx(TMLayoutItem, { children: _jsx(TMSearchResultSelector, { searchResults: currentSearchResults, disableAccordionIfSingleCategory: disableAccordionIfSingleCategory, selectedTID: selectedSearchResultTID, selectedSearchResult: selectedSearchResult, autoSelectFirst: !isMobile || hasSingleDcmtType, onSelectionChanged: onSearchResultSelectionChanged }) })
669
755
  :
670
- _jsx(_Fragment, {}), _jsx(TMLayoutItem, { children: _jsx(TMSearchResultGrid, { openInOffice: openInOffice, fromDTD: fromDTD, operationItems: operationItems, allUsers: allUsers, inputFocusedItem: focusedItem, inputSelectedItems: selectedItems, showExportForm: showExportForm, onFocusedItemChanged: handleFocusedItemChangedFromGrid, onDownloadDcmtsAsync: async (inputDcmts, downloadType, downloadMode, _y, confirmAttachments) => await downloadDcmtsAsync({ inputDcmts, downloadType, downloadMode, onFileDownloaded: onFileOpened, confirmAttachments }), lastUpdateSearchTime: lastUpdateSearchTime, searchResult: searchResults.length > 1 ? selectedSearchResult : searchResults[0], onSelectionChanged: (items) => { setSelectedItems(items); }, onDblClick: () => openFormHandler(LayoutModes.Update), showSearchTMDatagrid: showSearchTMDatagrid, onVisibleItemChanged: setVisibleItems, updateDataColumnsFromDataGrid: updateDataColumnsFromDataGrid, updateDataSourceFromDataGrid: updateDataSourceFromDataGrid, updateSelectedRowKeysFromDataGrid: updateSelectedRowKeysFromDataGrid, disableAutoFocus: autoFocusFirstRow === false && isFirstAutoFocusRef.current, dcmtsReturned: dcmtsReturned, dcmtsFound: dcmtsFound }) })] }), renderFloatingBar] }), _jsx(TMToppyDraggableHelpCenter, { usePortal: toppyHelpCenterUsePortal, isVisible: isToppyHelpCenterVisible, content: _jsx("div", { style: { display: 'flex', flexDirection: 'column', gap: '10px' }, children: _jsx(WorkFlowOperationButtons, { dtd: fromDTD, deviceType: deviceType, onApprove: () => {
756
+ _jsx(_Fragment, {}), _jsx(TMLayoutItem, { children: _jsx(TMSearchResultGrid, { openInOffice: openInOffice, fromDTD: fromDTD, operationItems: operationItems, allUsers: allUsers, inputFocusedItem: focusedItem, inputSelectedItems: selectedItems, showExportForm: showExportForm, onFocusedItemChanged: handleFocusedItemChangedFromGrid, onDownloadDcmtsAsync: async (inputDcmts, downloadType, downloadMode, _y, confirmAttachments) => await downloadDcmtsAsync({ inputDcmts, downloadType, downloadMode, onFileDownloaded: onFileOpened, confirmAttachments }), lastUpdateSearchTime: lastUpdateSearchTime, searchResult: searchResults.length > 1 ? selectedSearchResult : searchResults[0], onSelectionChanged: (items) => { setSelectedItems(items); }, onDblClick: () => openFormHandler(LayoutModes.Update), showSearchTMDatagrid: showSearchTMDatagrid, onVisibleItemChanged: setVisibleItems, updateDataColumnsFromDataGrid: updateDataColumnsFromDataGrid, updateDataSourceFromDataGrid: updateDataSourceFromDataGrid, updateSelectedRowKeysFromDataGrid: updateSelectedRowKeysFromDataGrid, disableAutoFocus: autoFocusFirstRow === false && isFirstAutoFocusRef.current, dcmtsReturned: dcmtsReturned, dcmtsFound: dcmtsFound, showCounterConfig: showCounterConfig, counterConfig: counterConfig, showActionsColumn: showActionsColumn, compactPager: compactPager, onRowActionRequest: requestRowAction }) })] }), renderFloatingBar] }), _jsx(TMToppyDraggableHelpCenter, { usePortal: toppyHelpCenterUsePortal, isVisible: isToppyHelpCenterVisible, content: _jsx("div", { style: { display: 'flex', flexDirection: 'column', gap: '10px' }, children: _jsx(WorkFlowOperationButtons, { dtd: fromDTD, deviceType: deviceType, onApprove: () => {
671
757
  updateShowApprovePopup(true);
672
758
  }, onSignApprove: () => {
673
759
  handleSignApprove();
@@ -687,7 +773,6 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
687
773
  showApprovePopup,
688
774
  showRejectPopup,
689
775
  showReAssignPopup,
690
- isOpenBatchUpdate,
691
776
  isOpenDetails,
692
777
  isOpenMaster,
693
778
  isOpenDcmtForm,
@@ -698,6 +783,10 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
698
783
  openS4TViewer,
699
784
  showBackButton,
700
785
  isMobile,
786
+ navStep,
787
+ isSteppedLayout,
788
+ isSelectorCollapsed,
789
+ currentSearchResults,
701
790
  currentMetadataValues,
702
791
  onRefreshSearchAsyncDatagrid,
703
792
  handleNavigateToWGs,
@@ -707,7 +796,7 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
707
796
  ]);
708
797
  const tmBlog = useMemo(() => _jsx(TMDcmtBlog, { tid: focusedItem?.TID, did: focusedItem?.DID, fetchBlogDataTrigger: refreshBlogTrigger, allTasks: allTasks, getAllTasks: getAllTasks, deleteTaskByIdsCallback: deleteTaskByIdsCallback, addTaskCallback: addTaskCallback, editTaskCallback: editTaskCallback, handleNavigateToWGs: handleNavigateToWGs, handleNavigateToDossiers: handleNavigateToDossiers }), [focusedItem, allTasks, refreshBlogTrigger, handleNavigateToWGs, handleNavigateToDossiers, onRefreshBlogDatagrid]);
709
798
  const tmSysMetadata = useMemo(() => _jsx(TMMetadataValues, { layoutMode: LayoutModes.Update, openChooserBySingleClick: true, TID: focusedItem?.TID, isReadOnly: true, deviceType: deviceType, metadataValues: currentMetadataValues.filter(o => (o.mid != undefined && o.mid <= 100)), metadataValuesOrig: currentMetadataValues.filter(o => (o.mid != undefined && o.mid <= 100)), validationItems: [] }), [focusedItem, currentMetadataValues, deviceType]);
710
- const tmDcmtPreview = useMemo(() => _jsx(TMDcmtPreviewWrapper, { refreshPreviewTrigger: refreshPreviewTrigger, currentDcmt: currentDcmt, onBack: getOnBackHandler() }), [currentDcmt, refreshPreviewTrigger]);
799
+ const tmDcmtPreview = useMemo(() => _jsx(TMDcmtPreviewWrapper, { refreshPreviewTrigger: refreshPreviewTrigger, currentDcmt: currentDcmt, onBack: backHandlerSecondary }), [currentDcmt, refreshPreviewTrigger, backHandlerSecondary]);
711
800
  // Auto-fetch indexing info when drawer is open and focusedItem changes
712
801
  useEffect(() => {
713
802
  if (!focusedItem || !showIndexingInfo)
@@ -786,9 +875,10 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
786
875
  component: tmSearchResult,
787
876
  panelContainer: {
788
877
  title: getTitleHeader(),
878
+ enableTitleMarquee: true,
789
879
  showHeader: showToolbarHeader,
790
880
  allowMaximize: !isMobile,
791
- onBack: getOnBackHandler(true),
881
+ onBack: backHandler,
792
882
  onClose: isClosable ? onBack : undefined,
793
883
  toolbar: searchResutlToolbar
794
884
  },
@@ -803,7 +893,7 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
803
893
  panelContainer: {
804
894
  title: SDKUI_Localizator.BlogCase,
805
895
  allowMaximize: !isMobile,
806
- onBack: getOnBackHandler(),
896
+ onBack: backHandlerSecondary,
807
897
  }
808
898
  },
809
899
  toolbarOptions: { icon: _jsx(IconBoard, { fontSize: 24 }), visible: getSearchToolbarVisibility(SDK_Globals.tmSession?.SessionDescr?.appModuleID ?? AppModules.SURFER).tmBlog, orderNumber: 2, isActive: allInitialPanelVisibility['tmBlog'] }
@@ -816,7 +906,7 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
816
906
  panelContainer: {
817
907
  title: SDKUI_Localizator.MetadataSystem,
818
908
  allowMaximize: !isMobile,
819
- onBack: getOnBackHandler(),
909
+ onBack: backHandlerSecondary,
820
910
  }
821
911
  },
822
912
  toolbarOptions: { icon: _jsx(IconDcmtTypeSys, { fontSize: 24 }), visible: getSearchToolbarVisibility(SDK_Globals.tmSession?.SessionDescr?.appModuleID ?? AppModules.SURFER).tmSysMetadata, orderNumber: 3, isActive: allInitialPanelVisibility['tmSysMetadata'] }
@@ -830,7 +920,7 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
830
920
  panelContainer: {
831
921
  title: SDKUI_Localizator.ResultDetails,
832
922
  allowMaximize: !isMobile,
833
- onBack: getOnBackHandler(),
923
+ onBack: backHandlerSecondary,
834
924
  }
835
925
  },
836
926
  toolbarOptions: { icon: _jsx(IconPlatform, { fontSize: 20 }), visible: getSearchToolbarVisibility(SDK_Globals.tmSession?.SessionDescr?.appModuleID ?? AppModules.SURFER).tmFullTextSearch, orderNumber: 4, isActive: allInitialPanelVisibility['tmFullTextSearch'] }
@@ -844,7 +934,7 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
844
934
  },
845
935
  toolbarOptions: { icon: _jsx(IconShow, { fontSize: 24 }), visible: getSearchToolbarVisibility(SDK_Globals.tmSession?.SessionDescr?.appModuleID ?? AppModules.SURFER).tmDcmtPreview, orderNumber: context === SearchResultContext.FREE_SEARCH ? 5 : 4, isActive: allInitialPanelVisibility['tmDcmtPreview'] }
846
936
  }
847
- ], [tmSearchResult, tmBlog, tmSysMetadata, tmDcmtPreview, tmFullTextSearch, showToolbarHeader, context, isMobile]);
937
+ ], [tmSearchResult, tmBlog, tmSysMetadata, tmDcmtPreview, tmFullTextSearch, showToolbarHeader, context, isMobile, backHandler, backHandlerSecondary, isClosable, onBack]);
848
938
  return (_jsxs(StyledMultiViewPanel, { "$isVisible": isVisible, children: [_jsx(StyledMultiViewPanel, { "$isVisible": !isOpenDcmtForm && !isOpenDetails && !isOpenMaster, style: {
849
939
  display: 'flex',
850
940
  flexDirection: isMobile ? 'column' : 'row',
@@ -853,12 +943,38 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
853
943
  width: '100%',
854
944
  height: '100%',
855
945
  }, children: _jsx(TMLayoutWaitingContainer, { direction: 'vertical', showWaitPanel: showWaitPanel, showWaitPanelPrimary: showPrimary, showWaitPanelSecondary: showSecondary, waitPanelTitle: waitPanelTitle, waitPanelTextPrimary: waitPanelTextPrimary, waitPanelValuePrimary: waitPanelValuePrimary, waitPanelMaxValuePrimary: waitPanelMaxValuePrimary, waitPanelTextSecondary: waitPanelTextSecondary, waitPanelValueSecondary: waitPanelValueSecondary, waitPanelMaxValueSecondary: waitPanelMaxValueSecondary, isCancelable: true, abortController: abortController, children: _jsx(TMLayoutWaitingContainer, { direction: 'vertical', showWaitPanel: showCicoWaitPanel, showWaitPanelPrimary: showCicoPrimaryProgress, waitPanelTitle: cicoWaitPanelTitle, waitPanelTextPrimary: cicoPrimaryProgressText, waitPanelValuePrimary: cicoPrimaryProgressValue, waitPanelMaxValuePrimary: cicoPrimaryProgressMax, isCancelable: true, abortController: abortControllerLocal, children: (groupId && groupId.length > 0) ?
856
- _jsxs(_Fragment, { children: [_jsx(PanelDisabledStateHandler, { isBoardDisabled: isBoardDisabled }), _jsx(TMPanelManagerContainer, { panels: initialPanels, direction: "horizontal", parentId: groupId, showToolbar: showSearchResultSidebar })] })
946
+ _jsxs(_Fragment, { children: [_jsx(PanelDisabledStateHandler, { isBoardDisabled: isBoardDisabled }), _jsx(ShowMainPanelBridge, { showMainPanelRef: showMainPanelRef }), _jsx(TMPanelManagerContainer, { panels: initialPanels, direction: "horizontal", parentId: groupId, showToolbar: showSearchResultSidebar })] })
857
947
  :
858
- _jsxs(TMPanelManagerProvider, { panels: initialPanels, initialVisibility: allInitialPanelVisibility, defaultDimensions: initialPanelDimensions, initialDimensions: initialPanelDimensions, initialMobilePanelId: 'tmSearchResult', children: [_jsx(PanelDisabledStateHandler, { isBoardDisabled: isBoardDisabled }), _jsx(TMPanelManagerContainer, { panels: initialPanels, direction: "horizontal", parentId: groupId, showToolbar: showSearchResultSidebar })] }) }) }) }), renderDcmtOperations] }));
948
+ _jsxs(TMPanelManagerProvider, { panels: initialPanels, initialVisibility: allInitialPanelVisibility, defaultDimensions: initialPanelDimensions, initialDimensions: initialPanelDimensions, initialMobilePanelId: 'tmSearchResult', children: [_jsx(PanelDisabledStateHandler, { isBoardDisabled: isBoardDisabled }), _jsx(ShowMainPanelBridge, { showMainPanelRef: showMainPanelRef }), _jsx(TMPanelManagerContainer, { panels: initialPanels, direction: "horizontal", parentId: groupId, showToolbar: showSearchResultSidebar })] }) }) }) }), renderDcmtOperations] }));
859
949
  };
860
950
  export default TMSearchResult;
861
- const TMSearchResultGrid = ({ openInOffice, fromDTD, operationItems, allUsers, inputFocusedItem, allowMultipleSelection = true, showExportForm = false, onFocusedItemChanged, onDownloadDcmtsAsync, onVisibleItemChanged, inputSelectedItems = [], lastUpdateSearchTime, searchResult, onSelectionChanged, onDblClick, showSearchTMDatagrid, updateDataColumnsFromDataGrid, updateDataSourceFromDataGrid, updateSelectedRowKeysFromDataGrid, disableAutoFocus = false, dcmtsReturned, dcmtsFound }) => {
951
+ /** Cerca una voce di operazione per id, anche dentro i sottomenu */
952
+ const findOperationItemById = (items, id) => {
953
+ for (const item of items) {
954
+ if (item.id === id)
955
+ return item;
956
+ const found = item.submenu ? findOperationItemById(item.submenu, id) : undefined;
957
+ if (found)
958
+ return found;
959
+ }
960
+ return undefined;
961
+ };
962
+ /**
963
+ * Azioni della colonna di riga. Gli id sono quelli di useDocumentOperations, così il comportamento è
964
+ * lo stesso del menu contestuale; icone e colori sono allineati alle altre griglie mentre il tooltip
965
+ * resta il nome dell'operazione. "Cancella" è la cancellazione logica: ripristino ed eliminazione
966
+ * fisica restano nel menu contestuale, dove la scelta è esplicita.
967
+ */
968
+ const rowActions = [
969
+ { operationId: 'open-form', icon: _jsx(IconEdit, { fontSize: 18, color: TMColors.primary }) },
970
+ { operationId: 'dup', icon: _jsx(IconDuplicate, { fontSize: 18, color: TMColors.tertiary }) },
971
+ { operationId: 'del-log', icon: _jsx(IconDelete, { fontSize: 18, color: TMColors.error }) },
972
+ ];
973
+ const rowActionsStyles = {
974
+ cell: { display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '14px' },
975
+ icon: { cursor: 'pointer', display: 'flex' },
976
+ };
977
+ const TMSearchResultGrid = ({ openInOffice, fromDTD, operationItems, allUsers, inputFocusedItem, allowMultipleSelection = true, showExportForm = false, onFocusedItemChanged, onDownloadDcmtsAsync, onVisibleItemChanged, inputSelectedItems = [], lastUpdateSearchTime, searchResult, onSelectionChanged, onDblClick, showSearchTMDatagrid, updateDataColumnsFromDataGrid, updateDataSourceFromDataGrid, updateSelectedRowKeysFromDataGrid, disableAutoFocus = false, dcmtsReturned, dcmtsFound, showCounterConfig = true, counterConfig, showActionsColumn = false, compactPager = false, onRowActionRequest, }) => {
862
978
  const [dataSource, setDataSource] = useState();
863
979
  const [columns, setColumns] = useState([]);
864
980
  const [selectedRowKeys, setSelectedRowKeys] = useState([]);
@@ -879,8 +995,8 @@ const TMSearchResultGrid = ({ openInOffice, fromDTD, operationItems, allUsers, i
879
995
  useEffect(() => {
880
996
  updateDataSourceFromDataGrid(dataSource ?? []);
881
997
  }, [dataSource]);
882
- const { loadDataListsAsync, renderDataListCell, dataListsCache } = useDataListItem();
883
- const { loadUsersAsync, renderUserIdViewer, usersCache } = useDataUserIdItem();
998
+ const { loadDataListsAsync, renderDataListCell, dataListsCache, getDataListItem } = useDataListItem();
999
+ const { loadUsersAsync, renderUserIdViewer, usersCache, getUserItem, getCompleteUserName } = useDataUserIdItem();
884
1000
  useEffect(() => {
885
1001
  // Sincronizza focusedItem con inputFocusedItem dal padre
886
1002
  if (deepCompare(inputFocusedItem, focusedItem))
@@ -1112,6 +1228,57 @@ const TMSearchResultGrid = ({ openInOffice, fromDTD, operationItems, allUsers, i
1112
1228
  });
1113
1229
  return cols;
1114
1230
  }, [searchResult, dataType, cellRender, getDisplayFormat, dataListsCache, usersCache]);
1231
+ /**
1232
+ * Colonne in cui il testo mostrato è diverso dal valore archiviato: date e numeri formattati, descrizioni
1233
+ * di liste dati e utenti. Su queste la ricerca della griglia va fatta sul testo mostrato, altrimenti non
1234
+ * trova nulla (es. cercando 29/01/2023 su una data, che nei dati è una stringa ISO).
1235
+ * Le cache sono ref lette al momento della ricerca: a quel punto sono già popolate.
1236
+ */
1237
+ const searchTextGetters = useMemo(() => {
1238
+ const getters = new Map();
1239
+ const columnsOfResult = searchResult?.dtdResult?.columns;
1240
+ if (!columnsOfResult)
1241
+ return getters;
1242
+ const uniqueKeys = generateUniqueColumnKeys(columnsOfResult, searchResult?.fromTID);
1243
+ columnsOfResult.forEach((col, index) => {
1244
+ const dataField = uniqueKeys[index];
1245
+ if (!dataField)
1246
+ return;
1247
+ const dataDomain = MetadataDataDomains[(col.extendedProperties?.["DataDomain"] ?? "None")];
1248
+ const dataListID = Number(col.extendedProperties?.["DataListID"]);
1249
+ if (dataDomain === MetadataDataDomains.DataList && dataListID) {
1250
+ getters.set(dataField, (rowData) => getDataListItem(dataListID, rowData?.[dataField])?.name ?? String(rowData?.[dataField] ?? ''));
1251
+ return;
1252
+ }
1253
+ if (dataDomain === MetadataDataDomains.UserID) {
1254
+ getters.set(dataField, (rowData) => {
1255
+ const rawValue = rowData?.[dataField];
1256
+ // Un campo utente vuoto non mostra nulla: senza questo controllo Number('') darebbe 0, cioè l'utente di sistema
1257
+ if (rawValue === undefined || rawValue === null || rawValue === '')
1258
+ return '';
1259
+ const userId = Number(rawValue);
1260
+ if (Number.isNaN(userId))
1261
+ return String(rawValue);
1262
+ if (userId === 0)
1263
+ return SDKUI_Localizator.SystemUser ?? '';
1264
+ const ud = getUserItem(userId);
1265
+ return (ud ? getCompleteUserName(ud.domain, ud.name) : undefined) ?? String(rawValue);
1266
+ });
1267
+ return;
1268
+ }
1269
+ // Data e numero vengono mostrati con il formato definito sulle proprietà estese della colonna
1270
+ const { format, formatCulture } = getColumnFormatInfo(col);
1271
+ if (col.dataType === DataColumnTypes.DateTime) {
1272
+ getters.set(dataField, (rowData) => getDateDisplayText(rowData?.[dataField], format, formatCulture));
1273
+ return;
1274
+ }
1275
+ if (col.dataType === DataColumnTypes.Number) {
1276
+ getters.set(dataField, (rowData) => getNumberDisplayText(rowData?.[dataField], format, formatCulture));
1277
+ }
1278
+ });
1279
+ return getters;
1280
+ }, [searchResult, getDataListItem, getUserItem, getCompleteUserName]);
1281
+ const customizeColumns = useMemo(() => customizeSearchByDisplayText(searchTextGetters), [searchTextGetters]);
1115
1282
  /**
1116
1283
  * Effect che carica i dati necessari e genera le colonne della griglia.
1117
1284
  * Esegue il caricamento delle cache (DataList e UserID) in parallelo prima di generare le colonne,
@@ -1203,6 +1370,9 @@ const TMSearchResultGrid = ({ openInOffice, fromDTD, operationItems, allUsers, i
1203
1370
  // Era sull'icona, non fare nulla
1204
1371
  return;
1205
1372
  }
1373
+ // Due click rapidi su un'icona della colonna azioni non devono aprire anche il form
1374
+ if (e.event?.target.closest('.tm-row-actions'))
1375
+ return;
1206
1376
  // Verifica che il documento abbia TID e DID validi prima di procedere
1207
1377
  if (!e.data.TID || !e.data.DID) {
1208
1378
  ShowAlert({
@@ -1216,8 +1386,41 @@ const TMSearchResultGrid = ({ openInOffice, fromDTD, operationItems, allUsers, i
1216
1386
  onFocusedItemChanged?.(e.data);
1217
1387
  onDblClick();
1218
1388
  }, [onDblClick]);
1389
+ /**
1390
+ * Colonna delle azioni di riga, in testa alla griglia (subito dopo la checkbox di selezione, che
1391
+ * DevExtreme tiene sempre per prima). Vive solo qui e non nello stato `columns`, che è quello
1392
+ * sincronizzato col padre per l'esportazione: una colonna senza dataField finirebbe in Excel come
1393
+ * colonna vuota.
1394
+ *
1395
+ * Le operazioni sono lette da una ref perché `operationItems` è ricostruito a ogni render del
1396
+ * padre: usarlo come dipendenza renderebbe nuova la colonna, e con essa `dataColumns`, ad ogni
1397
+ * giro. La cellRender le rilegge a ogni ridisegno della griglia, quindi resta comunque aggiornata.
1398
+ */
1399
+ const operationItemsRef = useRef(operationItems);
1400
+ operationItemsRef.current = operationItems;
1401
+ const actionsColumn = useMemo(() => ({
1402
+ caption: '',
1403
+ width: 110,
1404
+ alignment: 'center',
1405
+ allowSorting: false,
1406
+ allowFiltering: false,
1407
+ allowHeaderFiltering: false,
1408
+ allowResizing: false,
1409
+ allowReordering: false,
1410
+ allowHiding: false,
1411
+ allowExporting: false,
1412
+ showInColumnChooser: false,
1413
+ cssClass: 'tm-row-actions',
1414
+ cellRender: (cellData) => (_jsx("div", { style: rowActionsStyles.cell, children: rowActions.map(action => {
1415
+ const operation = findOperationItemById(operationItemsRef.current, action.operationId);
1416
+ if (!operation || operation.visible === false)
1417
+ return null;
1418
+ return (_jsx("span", { style: rowActionsStyles.icon, title: operation.name, onClick: () => onRowActionRequest?.(action.operationId, cellData.data), children: action.icon }, action.operationId));
1419
+ }) })),
1420
+ }), [onRowActionRequest]);
1219
1421
  const dataColumns = useMemo(() => {
1220
1422
  return [
1423
+ ...(showActionsColumn ? [actionsColumn] : []),
1221
1424
  {
1222
1425
  dataType: "object",
1223
1426
  dataField: 'FILEEXT',
@@ -1231,7 +1434,7 @@ const TMSearchResultGrid = ({ openInOffice, fromDTD, operationItems, allUsers, i
1231
1434
  },
1232
1435
  ...columns.filter(col => col.dataField !== 'FILEEXT')
1233
1436
  ];
1234
- }, [columns, onDownloadDcmtsAsync, openInOffice]);
1437
+ }, [columns, onDownloadDcmtsAsync, openInOffice, showActionsColumn, actionsColumn]);
1235
1438
  const onContentReady = useCallback((e) => {
1236
1439
  if (e === undefined)
1237
1440
  return;
@@ -1242,21 +1445,24 @@ const TMSearchResultGrid = ({ openInOffice, fromDTD, operationItems, allUsers, i
1242
1445
  setVisibleItems(visibleData);
1243
1446
  }, []);
1244
1447
  useEffect(() => { onVisibleItemChanged?.(visibleItems); }, [visibleItems]);
1245
- return _jsxs("div", { style: { width: "100%", height: "100%" }, children: [!isDataGridReady && (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', height: '100%', width: '100%', gap: '10px' }, children: [_jsx(LoadIndicator, { height: 60, width: 60 }), _jsx("div", { children: SDKUI_Localizator.Loading })] })), isDataGridReady && _jsx(TMDataGrid, { ref: dataGridRef, id: "tm-search-result", keyExpr: "rowIndex", dataColumns: dataColumns, dataSource: dataSource, repaintChangesOnly: true, selectedRowKeys: selectedRowKeys, focusedRowKey: disableAutoFocus ? undefined : Number(focusedItem?.rowIndex ?? 0), showSearchPanel: showSearchTMDatagrid, showFilterPanel: true, sorting: { mode: "multiple" }, selection: { mode: allowMultipleSelection ? 'multiple' : 'single' }, pageSize: pageSize, onSelectionChanged: handleSelectionChange, onFocusedRowChanged: handleFocusedRowChange, onRowDblClick: onRowDblClick, onContentReady: onContentReady, showHeaderColumnChooser: true, onKeyDown: onKeyDown, customContextMenuItems: operationItems, counterConfig: {
1246
- show: true,
1247
- items: dcmtsReturned !== undefined && dcmtsFound !== undefined
1248
- ? new Map([
1249
- [CounterItemKey.allItems, {
1250
- key: CounterItemKey.allItems,
1251
- show: true,
1252
- caption: `${dcmtsReturned} restituiti su ${dcmtsFound} trovati`,
1253
- value: `${dcmtsReturned}/${dcmtsFound}`,
1254
- icon: _jsx(IconAll, {}),
1255
- order: -1
1256
- }]
1257
- ])
1258
- : undefined
1259
- } })] });
1448
+ const gridCounterConfig = useMemo(() => {
1449
+ if (!showCounterConfig)
1450
+ return { show: false };
1451
+ const defaultItems = dcmtsReturned !== undefined && dcmtsFound !== undefined
1452
+ ? new Map([
1453
+ [CounterItemKey.allItems, {
1454
+ key: CounterItemKey.allItems,
1455
+ show: true,
1456
+ caption: `${dcmtsReturned} restituiti su ${dcmtsFound} trovati`,
1457
+ value: `${dcmtsReturned}/${dcmtsFound}`,
1458
+ icon: _jsx(IconAll, {}),
1459
+ order: -1
1460
+ }]
1461
+ ])
1462
+ : undefined;
1463
+ return { ...counterConfig, show: true, items: counterConfig?.items ?? defaultItems };
1464
+ }, [showCounterConfig, counterConfig, dcmtsReturned, dcmtsFound]);
1465
+ return _jsxs("div", { style: { width: "100%", height: "100%" }, children: [!isDataGridReady && (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', height: '100%', width: '100%', gap: '10px' }, children: [_jsx(LoadIndicator, { height: 60, width: 60 }), _jsx("div", { children: SDKUI_Localizator.Loading })] })), isDataGridReady && _jsx(TMDataGrid, { ref: dataGridRef, id: "tm-search-result", keyExpr: "rowIndex", dataColumns: dataColumns, dataSource: dataSource, customizeColumns: customizeColumns, repaintChangesOnly: true, selectedRowKeys: selectedRowKeys, focusedRowKey: disableAutoFocus ? undefined : Number(focusedItem?.rowIndex ?? 0), showSearchPanel: showSearchTMDatagrid, showFilterPanel: true, sorting: { mode: "multiple" }, selection: { mode: allowMultipleSelection ? 'multiple' : 'single' }, pageSize: pageSize, compactPager: compactPager, onSelectionChanged: handleSelectionChange, onFocusedRowChanged: handleFocusedRowChange, onRowDblClick: onRowDblClick, onContentReady: onContentReady, showHeaderColumnChooser: true, onKeyDown: onKeyDown, customContextMenuItems: operationItems, counterConfig: gridCounterConfig })] });
1260
1466
  };
1261
1467
  //#region TMSearchResultSelector
1262
1468
  const StyledItemTemplate = styled.div `
@@ -1317,14 +1523,29 @@ const TMSearchResultSelector = ({ searchResults = [], disableAccordionIfSingleCa
1317
1523
  }
1318
1524
  }
1319
1525
  }, [sortedCategories.length, autoSelectFirst]);
1526
+ // La preselezione esterna va applicata una volta per valore: riapplicarla a ogni cambio di
1527
+ // searchResults faceva sì che un refresh annullasse la navigazione dell'utente.
1528
+ const appliedSelectedTIDRef = useRef(undefined);
1320
1529
  useEffect(() => {
1321
- if (selectedTID !== undefined) {
1322
- const found = searchResults.find(r => Number(r.fromTID) === selectedTID);
1323
- handleSelect(found);
1530
+ // Richiesta ritirata: dimentichiamo il valore applicato, così se il chiamante richiede di
1531
+ // nuovo lo stesso TID la preselezione riparte
1532
+ if (selectedTID === undefined) {
1533
+ appliedSelectedTIDRef.current = undefined;
1534
+ return;
1324
1535
  }
1536
+ if (appliedSelectedTIDRef.current === selectedTID)
1537
+ return;
1538
+ const found = searchResults.find(r => Number(r.fromTID) === selectedTID);
1539
+ if (!found)
1540
+ return; // risultati non ancora arrivati: riproviamo al prossimo giro
1541
+ appliedSelectedTIDRef.current = selectedTID;
1542
+ handleSelect(found);
1325
1543
  }, [selectedTID, searchResults]);
1326
- const handleSelect = useCallback((result) => {
1327
- if (selectedResult !== result && result !== undefined) {
1544
+ // force = true per i click dell'utente: la selezione va notificata anche se il tipo documento è già quello selezionato
1545
+ const handleSelect = useCallback((result, force = false) => {
1546
+ if (result === undefined)
1547
+ return;
1548
+ if (selectedResult !== result || force) {
1328
1549
  setSelectedResult(result);
1329
1550
  onSelectionChanged?.(result);
1330
1551
  }
@@ -1369,14 +1590,24 @@ const TMSearchResultSelector = ({ searchResults = [], disableAccordionIfSingleCa
1369
1590
  renderItem: (result, isSelected) => (_jsxs("div", { style: { width: '100%', padding: '5px', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }, children: [_jsx(TMTidViewer, { tid: result.fromTID, did: Number(result.dtdResult?.rows?.[0]?.[1]), showIcon: true }), _jsx("div", { style: { padding: 3, display: 'flex', alignItems: 'center', justifyContent: 'center', backgroundColor: 'white', color: 'gray', borderRadius: 3 }, children: result.dcmtsReturned })] })),
1370
1591
  itemHeight: 40
1371
1592
  })), [sortedCategories, groupedResults, searchResults]);
1372
- const renderCategoryItems = (category) => (_jsx("div", { style: { padding: '5px', height: '100%', overflow: 'auto' }, children: groupedResults[category].map((result, index) => (_jsx(MemoizedStyledItemTemplate, { "$isSelected": selectedResult === result, onClick: () => handleSelect(result), children: renderItemTemplate(result) }, index))) }));
1593
+ const renderCategoryItems = (category) => (_jsx("div", { style: { padding: '5px', height: '100%', overflow: 'auto' }, children: groupedResults[category].map((result, index) => (_jsx(MemoizedStyledItemTemplate, { "$isSelected": selectedResult === result, onClick: () => handleSelect(result, true), children: renderItemTemplate(result) }, index))) }));
1373
1594
  if (disableAccordionIfSingleCategory && sortedCategories.length === 1) {
1374
1595
  const category = sortedCategories[0];
1375
1596
  return renderCategoryItems(category);
1376
1597
  }
1377
- return (_jsx(TMAccordionNew, { groups: accordionGroups, selectedItem: selectedResult, onSelectedItemChange: (result) => handleSelect(result) }));
1598
+ return (_jsx(TMAccordionNew, { groups: accordionGroups, selectedItem: selectedResult, onSelectedItemChange: (result) => handleSelect(result, true) }));
1378
1599
  };
1379
1600
  //#endregion TMSearchResultSelector
1601
+ /** Senza UI: vive dentro il provider e passa a onBack, via ref, l'azione "mostra il pannello lista". */
1602
+ const ShowMainPanelBridge = ({ showMainPanelRef }) => {
1603
+ const { setPanelVisibilityById } = useTMPanelManagerContext();
1604
+ useEffect(() => {
1605
+ // Su mobile mostrare un pannello nasconde gli altri; su desktop la lista è già visibile
1606
+ showMainPanelRef.current = () => setPanelVisibilityById('tmSearchResult', true);
1607
+ return () => { showMainPanelRef.current = null; };
1608
+ }, [setPanelVisibilityById, showMainPanelRef]);
1609
+ return null;
1610
+ };
1380
1611
  const PanelDisabledStateHandler = ({ isBoardDisabled }) => {
1381
1612
  const { setPanelVisibilityById, setToolbarButtonDisabled } = useTMPanelManagerContext();
1382
1613
  useEffect(() => {