@topconsultnpm/sdkui-react 6.22.0-dev2.3 → 6.22.0-dev2.30

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 (118) 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 +81 -8
  7. package/lib/components/base/TMEditorBase.d.ts +2 -0
  8. package/lib/components/base/TMFileManager.js +5 -3
  9. package/lib/components/base/TMFileManagerUtils.d.ts +1 -0
  10. package/lib/components/base/TMFileManagerUtils.js +16 -0
  11. package/lib/components/base/TMModal.d.ts +4 -0
  12. package/lib/components/base/TMModal.js +42 -11
  13. package/lib/components/base/TMPanel.d.ts +1 -0
  14. package/lib/components/base/TMPanel.js +79 -20
  15. package/lib/components/base/TMPopUp.js +3 -0
  16. package/lib/components/base/TMTooltip.d.ts +2 -1
  17. package/lib/components/base/TMTooltip.js +23 -6
  18. package/lib/components/base/TMTreeView.js +108 -67
  19. package/lib/components/choosers/TMDcmtTypeChooser.js +4 -3
  20. package/lib/components/choosers/TMDistinctValues.d.ts +9 -7
  21. package/lib/components/choosers/TMDistinctValues.js +149 -142
  22. package/lib/components/choosers/TMMetadataChooser.js +1 -1
  23. package/lib/components/choosers/TMQuickSearchInfo.d.ts +12 -0
  24. package/lib/components/choosers/TMQuickSearchInfo.js +279 -0
  25. package/lib/components/choosers/TMQuickSearchSettingsForm.d.ts +16 -0
  26. package/lib/components/choosers/TMQuickSearchSettingsForm.js +94 -0
  27. package/lib/components/choosers/TMSelectedValuesSummary.d.ts +13 -0
  28. package/lib/components/choosers/TMSelectedValuesSummary.js +91 -0
  29. package/lib/components/editors/TMDropDown.d.ts +3 -0
  30. package/lib/components/editors/TMDropDown.js +197 -5
  31. package/lib/components/editors/TMMetadataEditor.d.ts +5 -1
  32. package/lib/components/editors/TMMetadataEditor.js +8 -3
  33. package/lib/components/editors/TMTextBox.js +2 -2
  34. package/lib/components/features/documents/TMBatchUpdateForm.js +19 -5
  35. package/lib/components/features/documents/TMDcmtBlog.d.ts +3 -0
  36. package/lib/components/features/documents/TMDcmtBlog.js +4 -4
  37. package/lib/components/features/documents/TMDcmtForm.d.ts +7 -0
  38. package/lib/components/features/documents/TMDcmtForm.js +112 -45
  39. package/lib/components/features/documents/TMDcmtFormActionButtons.d.ts +11 -0
  40. package/lib/components/features/documents/TMDcmtFormActionButtons.js +74 -7
  41. package/lib/components/features/documents/TMDcmtPreview.js +10 -6
  42. package/lib/components/features/documents/TMMasterDetailDcmts.js +95 -33
  43. package/lib/components/features/documents/TMMasterInfoFields.d.ts +19 -0
  44. package/lib/components/features/documents/TMMasterInfoFields.js +186 -0
  45. package/lib/components/features/documents/TMMultiMasterDetailDcmtsForm.d.ts +40 -0
  46. package/lib/components/features/documents/TMMultiMasterDetailDcmtsForm.js +296 -0
  47. package/lib/components/features/documents/TMMultiMasterDetailDcmtsUtils.d.ts +363 -0
  48. package/lib/components/features/documents/TMMultiMasterDetailDcmtsUtils.js +259 -0
  49. package/lib/components/features/documents/TMRelationViewer.d.ts +14 -1
  50. package/lib/components/features/documents/TMRelationViewer.js +69 -20
  51. package/lib/components/features/search/TMSearch.js +29 -16
  52. package/lib/components/features/search/TMSearchQueryEditor.js +5 -3
  53. package/lib/components/features/search/TMSearchQueryPanel.d.ts +1 -1
  54. package/lib/components/features/search/TMSearchQueryPanel.js +28 -7
  55. package/lib/components/features/search/TMSearchResult.d.ts +9 -0
  56. package/lib/components/features/search/TMSearchResult.js +419 -108
  57. package/lib/components/features/search/TMTreeSelector.js +18 -5
  58. package/lib/components/features/workflow/TMWorkflowPopup.d.ts +10 -0
  59. package/lib/components/features/workflow/TMWorkflowPopup.js +8 -4
  60. package/lib/components/layout/panelManager/TMPanelManagerContainer.d.ts +1 -0
  61. package/lib/components/layout/panelManager/TMPanelManagerContainer.js +24 -14
  62. package/lib/components/layout/panelManager/TMPanelManagerContext.js +68 -93
  63. package/lib/components/layout/panelManager/TMPanelWrapper.js +2 -2
  64. package/lib/components/layout/panelManager/types.d.ts +3 -0
  65. package/lib/components/layout/panelManager/utils.js +13 -11
  66. package/lib/components/viewers/TMMidViewer.js +4 -1
  67. package/lib/components/viewers/TMTidViewer.js +25 -11
  68. package/lib/helper/SDKUI_Globals.d.ts +82 -0
  69. package/lib/helper/SDKUI_Globals.js +119 -0
  70. package/lib/helper/SDKUI_Localizator.d.ts +24 -0
  71. package/lib/helper/SDKUI_Localizator.js +240 -0
  72. package/lib/helper/TMIcons.d.ts +1 -0
  73. package/lib/helper/TMIcons.js +3 -0
  74. package/lib/helper/dataGridSearchHelper.d.ts +28 -0
  75. package/lib/helper/dataGridSearchHelper.js +51 -0
  76. package/lib/helper/dcmtsHelper.d.ts +20 -0
  77. package/lib/helper/dcmtsHelper.js +58 -1
  78. package/lib/helper/helpers.d.ts +20 -0
  79. package/lib/helper/helpers.js +55 -1
  80. package/lib/helper/index.d.ts +1 -0
  81. package/lib/helper/index.js +1 -0
  82. package/lib/helper/queryHelper.d.ts +2 -0
  83. package/lib/helper/queryHelper.js +3 -1
  84. package/lib/hooks/tmDistinctValuesGridHelper.d.ts +16 -0
  85. package/lib/hooks/tmDistinctValuesGridHelper.js +31 -0
  86. package/lib/hooks/useArchiveListForm.d.ts +39 -0
  87. package/lib/hooks/useArchiveListForm.js +46 -0
  88. package/lib/hooks/useCaseFlowApprove.d.ts +56 -0
  89. package/lib/hooks/useCaseFlowApprove.js +157 -0
  90. package/lib/hooks/useDataUserIdItem.d.ts +1 -0
  91. package/lib/hooks/useDataUserIdItem.js +1 -0
  92. package/lib/hooks/useDocumentOperations.d.ts +2 -0
  93. package/lib/hooks/useDocumentOperations.js +28 -5
  94. package/lib/hooks/useMultiMasterDetailDcmts.d.ts +100 -0
  95. package/lib/hooks/useMultiMasterDetailDcmts.js +587 -0
  96. package/lib/hooks/useRelatedDocuments.js +2 -26
  97. package/lib/hooks/useTMDistinctValuesMetadataDisplay.d.ts +18 -0
  98. package/lib/hooks/useTMDistinctValuesMetadataDisplay.js +103 -0
  99. package/lib/hooks/useTMDistinctValuesQuickSearch.d.ts +49 -0
  100. package/lib/hooks/useTMDistinctValuesQuickSearch.js +307 -0
  101. package/lib/hooks/useTMDistinctValuesSelection.d.ts +42 -0
  102. package/lib/hooks/useTMDistinctValuesSelection.js +103 -0
  103. package/lib/hooks/useTMDistinctValuesSource.d.ts +29 -0
  104. package/lib/hooks/useTMDistinctValuesSource.js +105 -0
  105. package/lib/index.d.ts +1 -0
  106. package/lib/index.js +1 -0
  107. package/lib/services/caseflow/CaseFlowService.d.ts +236 -0
  108. package/lib/services/caseflow/CaseFlowService.js +693 -0
  109. package/lib/services/caseflow/CaseFlowTemplateCacheService.d.ts +28 -0
  110. package/lib/services/caseflow/CaseFlowTemplateCacheService.js +121 -0
  111. package/lib/services/caseflow/caseFlowNameCache.d.ts +32 -0
  112. package/lib/services/caseflow/caseFlowNameCache.js +116 -0
  113. package/lib/services/caseflow/caseFlowWorkItemUtils.d.ts +61 -0
  114. package/lib/services/caseflow/caseFlowWorkItemUtils.js +113 -0
  115. package/lib/services/index.d.ts +4 -0
  116. package/lib/services/index.js +4 -0
  117. package/lib/services/platform_services.d.ts +1 -1
  118. package/package.json +66 -66
@@ -1,13 +1,13 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
+ import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } 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, getSearchResultPanelLayout, getSearchResultPanelLayoutSignature, hasSearchResultPanelLayout, saveSearchResultPanelLayout, subscribeSearchResultPanelLayout } 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';
@@ -23,7 +23,8 @@ import { StyledMultiViewPanel } from '../../base/Styled';
23
23
  import { TMLayoutWaitingContainer } from '../../base/TMWaitPanel';
24
24
  import TMMetadataValues from '../../editors/TMMetadataValues';
25
25
  import TMTidViewer from '../../viewers/TMTidViewer';
26
- import { TMPanelManagerProvider, useTMPanelManagerContext } from '../../layout/panelManager/TMPanelManagerContext';
26
+ import { useTMPanelManagerContext } from '../../layout/panelManager/TMPanelManagerContext';
27
+ import { TMPanelManagerWithPersistenceProvider } from '../../layout/panelManager/TMPanelManagerWithPersistenceProvider';
27
28
  import TMPanelManagerContainer from '../../layout/panelManager/TMPanelManagerContainer';
28
29
  import TMContextMenu from '../../NewComponents/ContextMenu/TMContextMenu';
29
30
  import TMToppyDraggableHelpCenter from '../assistant/TMToppyDraggableHelpCenter';
@@ -51,13 +52,25 @@ const orderByName = (array) => {
51
52
  return 1;
52
53
  } return 0; });
53
54
  };
55
+ //#endregion Helper Methods
56
+ /**
57
+ * Contesti che ricordano il layout dei pannelli, condividendone uno solo.
58
+ * Gli altri sono esclusi di proposito: FREE_SEARCH ha un pannello in più, quindi il suo layout non vale
59
+ * per gli altri, e i restanti mostrano il risultato dentro contenitori con esigenze di spazio loro.
60
+ */
61
+ const PERSISTED_LAYOUT_CONTEXTS = [
62
+ SearchResultContext.METADATA_SEARCH,
63
+ SearchResultContext.WORKFLOW_APPROVE,
64
+ ];
54
65
  const TMSearchResult = ({
55
66
  // Data
56
67
  groupId, searchResults = [], context = SearchResultContext.METADATA_SEARCH, title, selectedSearchResultTID, floatingActionConfig, workingGroupContext = undefined, inputDID,
57
68
  // 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,
69
+ 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,
70
+ // Counters
71
+ counterConfig,
59
72
  // Callbacks (optional)
60
- openInOffice, onRefreshAfterAddDcmtToFavs, onRefreshSearchAsyncDatagrid, onSelectedTIDChanged, onWFOperationCompleted, onClose, onFileOpened, onTaskCreateRequest, openWGsCopyMoveForm, openCommentFormCallback, openAddDocumentForm, onOpenS4TViewerRequest, onOpenPdfEditorRequest, openFileUploaderPdfEditor, passToArchiveCallback, onReferenceClick,
73
+ openInOffice, onRefreshAfterAddDcmtToFavs, onRefreshSearchAsyncDatagrid, onSelectedTIDChanged, onSelectedTIDCleared, onWFOperationCompleted, onClose, onFileOpened, onTaskCreateRequest, openWGsCopyMoveForm, openCommentFormCallback, openAddDocumentForm, onOpenS4TViewerRequest, onOpenPdfEditorRequest, openFileUploaderPdfEditor, passToArchiveCallback, onReferenceClick,
61
74
  // Tasks
62
75
  allTasks = [], getAllTasks, deleteTaskByIdsCallback, addTaskCallback, editTaskCallback,
63
76
  // Navigation
@@ -69,13 +82,19 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
69
82
  // Quando l'utente seleziona manualmente, il ref viene settato a false e il focus automatico
70
83
  // torna al comportamento normale.
71
84
  const isFirstAutoFocusRef = useRef(true);
85
+ // Mostra il pannello lista. È una ref perché il TMPanelManagerProvider sta dentro al JSX di
86
+ // questo componente, quindi qui il context non si può usare: la riempie ShowMainPanelBridge.
87
+ const showMainPanelRef = useRef(null);
72
88
  // ID state to force remount of components
73
89
  const [id, setID] = useState('');
74
90
  // Document type descriptor of the currently document
75
91
  const [fromDTD, setFromDTD] = useState();
76
92
  const [currentDcmt, setCurrentDcmt] = useState();
77
- // State to specify if the details form is open or not
78
- const [splitterSize, setSplitterSize] = useState(['100%', '0']);
93
+ // Passo corrente. Su mobile decide anche quale pannello è a schermo (vedi isSteppedLayout).
94
+ // 'auto' -> l'utente non ha ancora scelto: si può selezionare il primo tipo documento
95
+ // 'selector' -> è tornato indietro col back: non selezionare niente al posto suo
96
+ // 'results' -> è sulla griglia del tipo scelto
97
+ const [navStep, setNavStep] = useState('auto');
79
98
  // State to control the selected search result
80
99
  const [selectedSearchResult, setSelectedSearchResult] = useState();
81
100
  // State to control the search results to show in the datagrid
@@ -303,7 +322,7 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
303
322
  createTaskFromDocumentOrWorkItem();
304
323
  }
305
324
  };
306
- const { operationItems, renderFloatingBar, renderDcmtOperations, features, } = useDocumentOperations({
325
+ const { operationItems, selectedDcmtInfos, renderFloatingBar, renderDcmtOperations, features, } = useDocumentOperations({
307
326
  context: context,
308
327
  documentData: {
309
328
  dtd: fromDTD,
@@ -377,9 +396,60 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
377
396
  openTaskFormHandler,
378
397
  },
379
398
  });
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;
399
+ 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;
400
+ // ----- Azioni della colonna di riga (showActionsColumn) -----------------------------------------
401
+ // Le voci di operationItems agiscono sui documenti selezionati o, in mancanza, su quello a fuoco:
402
+ // non accettano una riga come argomento. Il click sull'icona quindi azzera la selezione e porta il
403
+ // fuoco sulla riga cliccata, poi attende. L'attesa non è sul fuoco ma su selectedDcmtInfos, che
404
+ // l'hook ricalcola in un effect (un render più tardi): lanciare l'operazione prima significherebbe
405
+ // eseguirla sulla riga precedente.
406
+ const pendingRowActionRef = useRef(undefined);
407
+ const requestRowAction = useCallback((operationId, rowData) => {
408
+ if (!showActionsColumn)
409
+ return;
410
+ if (rowData?.TID === undefined || rowData?.DID === undefined) {
411
+ ShowAlert({ message: SDKUI_Localizator.InvalidDcmt, mode: "warning", duration: 3000 });
412
+ return;
413
+ }
414
+ pendingRowActionRef.current = { operationId, TID: rowData.TID, DID: rowData.DID };
415
+ isFirstAutoFocusRef.current = false;
416
+ setSelectedItems([]);
417
+ setFocusedItem(rowData);
418
+ }, [showActionsColumn]);
419
+ // La richiesta viene scartata se il documento sparisce dai risultati (es. refresh dopo
420
+ // un'eliminazione): senza questo resterebbe appesa e partirebbe al prossimo cambio di fuoco.
421
+ useEffect(() => { pendingRowActionRef.current = undefined; }, [selectedSearchResult, lastUpdateSearchTime]);
422
+ useEffect(() => {
423
+ if (!showActionsColumn)
424
+ return;
425
+ const pending = pendingRowActionRef.current;
426
+ if (!pending)
427
+ return;
428
+ // L'hook non ha ancora recepito la riga richiesta: si riproverà al prossimo aggiornamento
429
+ if (selectedDcmtInfos.length !== 1)
430
+ return;
431
+ if (selectedDcmtInfos[0].TID !== pending.TID || selectedDcmtInfos[0].DID !== pending.DID)
432
+ return;
433
+ pendingRowActionRef.current = undefined;
434
+ const operation = findOperationItemById(operationItems, pending.operationId);
435
+ // Ora che il fuoco è sulla riga, `disabled` riflette i permessi per quel documento
436
+ if (!operation || operation.disabled || operation.visible === false)
437
+ return;
438
+ operation.onClick?.();
439
+ }, [showActionsColumn, selectedDcmtInfos, operationItems]);
381
440
  const deviceType = useDeviceType();
382
441
  const isMobile = deviceType === DeviceType.MOBILE;
442
+ const hasSingleDcmtType = currentSearchResults.length === 1;
443
+ // Su mobile si vede un pannello per volta; su desktop selettore e griglia sono affiancati
444
+ const isSteppedLayout = showSelector && isMobile;
445
+ // Un solo tipo documento su desktop: non c'è niente da scegliere, quindi il selettore sparisce
446
+ // (larghezza 0, nessun separatore) e lo spazio va tutto alla griglia. Il menu "Altro" permette
447
+ // di riaprirlo (vedi canToggleDcmtTypesSelection nell'hook).
448
+ const isSelectorCollapsed = showSelector && !isSteppedLayout && hasSingleDcmtType && !showDcmtTypesSelection;
449
+ // Senza selettore lo spazio va tutto alla griglia; altrimenti a step su mobile, affiancati su desktop
450
+ const splitterSizes = (!showSelector || isSelectorCollapsed) ? ['0%', '100%']
451
+ : isSteppedLayout ? (navStep === 'results' ? ['0', '100%'] : ['100%', '0'])
452
+ : ['30%', '70%'];
383
453
  const selectedDocs = getSelectedDcmtsOrFocused(selectedItems, focusedItem);
384
454
  // Disable the "Sign/Approve" button if:
385
455
  // 1. No document or multiple documents are selected, OR
@@ -400,8 +470,14 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
400
470
  setSelectedSearchResult(undefined);
401
471
  return;
402
472
  }
473
+ // Un solo tipo documento: selezionalo e vai ai risultati, a meno che l'utente sia tornato indietro
403
474
  if (searchResults.length === 1) {
475
+ if (navStep === 'selector') {
476
+ setSelectedSearchResult(undefined);
477
+ return;
478
+ }
404
479
  setSelectedSearchResult(searchResults[0]);
480
+ setNavStep('results');
405
481
  return;
406
482
  }
407
483
  // Se il TID precedentemente selezionato esiste ancora nei nuovi risultati, mantienilo.
@@ -415,6 +491,11 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
415
491
  return;
416
492
  }
417
493
  }
494
+ // L'utente è sul selettore
495
+ if (isMobile && navStep !== 'results') {
496
+ setSelectedSearchResult(undefined);
497
+ return;
498
+ }
418
499
  // Altrimenti seleziona il primo risultato ordinato
419
500
  setSelectedSearchResult(orderByName(searchResults)[0]);
420
501
  }, [searchResults]);
@@ -471,7 +552,9 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
471
552
  return;
472
553
  if (focusedItem?.DID != inputDID)
473
554
  return;
474
- openFormHandler(LayoutModes.Update);
555
+ if (focusedItem?.TID === undefined)
556
+ return;
557
+ onDcmtFormOpenChange(true, LayoutModes.Update);
475
558
  }, [inputDID, formAutoOpen, focusedItem]);
476
559
  // Quando openS4TViewer è true, apri automaticamente il viewer S4T
477
560
  useEffect(() => {
@@ -531,47 +614,75 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
531
614
  let counters = (showSelector && disableAccordionIfSingleCategory && searchResults.length > 1) ? getSearchResultCountersSingleCategory(searchResults) : "";
532
615
  if (title)
533
616
  return title + counters;
534
- let titleHeader = 'Ricerca per metadati';
617
+ // Nome del tipo documento selezionato, accodato al titolo
618
+ const selectedDcmtTypeName = selectedSearchResult?.fromName ?? (searchResults?.length === 1 ? searchResults[0]?.fromName : undefined);
619
+ const withSelectedDcmtTypeName = (baseTitle) => selectedDcmtTypeName ? `${baseTitle} - ${selectedDcmtTypeName}` : baseTitle;
620
+ let titleHeader = withSelectedDcmtTypeName('Ricerca per metadati');
535
621
  switch (context) {
536
622
  case SearchResultContext.MASTER_DETAIL:
537
623
  case SearchResultContext.METADATA_SEARCH:
538
624
  titleHeader = `${searchResults?.length > 1 ? selectedSearchResult?.fromName ?? 'Ricerca per metadati' : searchResults[0]?.fromName ?? 'Ricerca per metadati'}`;
539
625
  break;
540
626
  case SearchResultContext.FAVORITES_AND_RECENTS:
541
- titleHeader = SDKUI_Localizator.FavoritesAndRecentDcmts;
627
+ titleHeader = withSelectedDcmtTypeName(SDKUI_Localizator.FavoritesAndRecentDcmts);
542
628
  break;
543
629
  case SearchResultContext.WORKFLOW_APPROVE:
544
- titleHeader = SDKUI_Localizator.WorkflowApproval;
630
+ titleHeader = withSelectedDcmtTypeName(SDKUI_Localizator.WorkflowApproval);
545
631
  break;
546
632
  case SearchResultContext.FREE_SEARCH:
547
- titleHeader = `Risultato della ricerca full text`;
633
+ titleHeader = withSelectedDcmtTypeName('Risultato della ricerca full text');
548
634
  break;
549
635
  }
550
636
  return titleHeader + counters;
551
637
  };
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?.();
638
+ // Dai risultati si risale al selettore dei tipi documento
639
+ const canGoBackToSelector = isSteppedLayout && navStep === 'results' && selectedSearchResult !== undefined;
640
+ /**
641
+ * Back di tutti i pannelli (e X di chiusura se isClosable). Su mobile risale un livello alla volta:
642
+ * pannello secondario -> lista dei risultati -> selettore dei tipi documento -> uscita.
643
+ * Su desktop i pannelli sono affiancati, quindi resta solo l'uscita.
644
+ *
645
+ * @param fromSecondaryPanel - true se il back arriva da un pannello secondario
646
+ */
647
+ const onBack = useCallback((fromSecondaryPanel = false) => {
648
+ // Non è previsto nessun modo di uscire: non fare niente
649
+ if (!showBackButton && !isClosable)
650
+ return;
651
+ // Torna alla lista senza toccare gli stati: l'utente deve ritrovare la sua selezione
652
+ if (fromSecondaryPanel) {
653
+ showMainPanelRef.current?.();
654
+ return;
573
655
  }
574
- };
656
+ // Sia il ritorno al selettore sia l'uscita ripartono da una lista pulita
657
+ setSelectedSearchResult(undefined);
658
+ setFocusedItem(undefined);
659
+ setSelectedItems([]);
660
+ setSelectedRowKeysFromDataGrid([]);
661
+ setCurrentDcmt(undefined);
662
+ setCurrentMetadataValues([]);
663
+ setShowIndexingInfo(false);
664
+ isFirstAutoFocusRef.current = true;
665
+ // Il chiamante può tenere allineato il suo TID selezionato (es. selectedSearchResultTID)
666
+ onSelectedTIDCleared?.();
667
+ // [2] l'utente ha chiesto la lista: resta lì finché non sceglie lui, niente auto-selezione.
668
+ // [1] si esce: si riparte da capo, quindi l'auto-selezione torna permessa.
669
+ setNavStep(canGoBackToSelector ? 'selector' : 'auto');
670
+ // [2] C'è ancora un livello sopra: restiamo dentro, sul selettore
671
+ if (canGoBackToSelector)
672
+ return;
673
+ // [1] Si esce e si avvisa il chiamante
674
+ onClose?.();
675
+ }, [canGoBackToSelector, showBackButton, isClosable, onClose, onSelectedTIDCleared]);
676
+ /**
677
+ * Back del pannello lista. `undefined` = freccia non renderizzata, quindi la mostriamo solo se
678
+ * ha dove tornare: uno step intermedio, o un onClose per uscire. Con `isClosable` basta la X.
679
+ */
680
+ const backHandler = (showBackButton && !isClosable && (canGoBackToSelector || onClose !== undefined)) ? onBack : undefined;
681
+ /**
682
+ * Back dei pannelli secondari: la stessa onBack marcata come livello [3]. Solo su mobile:
683
+ * su desktop stanno affiancati alla lista e non c'è niente da cui tornare.
684
+ */
685
+ const backHandlerSecondary = useMemo(() => (isMobile && backHandler) ? () => backHandler(true) : undefined, [isMobile, backHandler]);
575
686
  const refreshDataGridAfterRemoveAsync = async () => {
576
687
  let index = selectedSearchResult?.dtdResult?.columns?.findIndex(col => col.caption === 'DID');
577
688
  let selectedRows = [];
@@ -603,21 +714,6 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
603
714
  return newResults;
604
715
  });
605
716
  };
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
717
  const removeDcmtFromFavsOrRecents = async () => {
622
718
  switch (selectedSearchResult?.category) {
623
719
  case "Favorites":
@@ -634,9 +730,9 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
634
730
  };
635
731
  const onSearchResultSelectionChanged = (e) => {
636
732
  setSelectedSearchResult(e);
637
- if (deviceType === DeviceType.MOBILE) {
638
- setSplitterSize(['0', '100%']);
639
- }
733
+ // Scelto il tipo documento si passa ai risultati (a livello di layout è ignorato se i due
734
+ // step sono affiancati, ma vale comunque come "l'utente non è più fermo sul selettore")
735
+ setNavStep('results');
640
736
  onSelectedTIDChanged?.(e.fromTID);
641
737
  };
642
738
  const showToppyForApprove = useMemo(() => {
@@ -660,14 +756,15 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
660
756
  && !showTodoDcmtForm);
661
757
  }, [showToppyForApprove, showToppyDraggableHelpCenter, selectedDocs, showApprovePopup, showRejectPopup, showReAssignPopup, showMoreInfoPopup, editPdfForm, openS4TViewer, showTodoDcmtForm]);
662
758
  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' }) })] });
759
+ _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 &&
760
+ _jsx(TMButton, { btnStyle: 'icon', icon: _jsx(IconCloseOutline, { color: 'white' }), caption: SDKUI_Localizator.Close, onClick: onClose })] });
664
761
  const tmSearchResult = useMemo(() => (!searchResults || searchResults.length <= 0)
665
762
  ? _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
763
  :
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 }) })
764
+ _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 ?
765
+ _jsx(TMLayoutItem, { children: _jsx(TMSearchResultSelector, { searchResults: currentSearchResults, disableAccordionIfSingleCategory: disableAccordionIfSingleCategory, selectedTID: selectedSearchResultTID, selectedSearchResult: selectedSearchResult, autoSelectFirst: !isMobile || hasSingleDcmtType, onSelectionChanged: onSearchResultSelectionChanged }) })
669
766
  :
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: () => {
767
+ _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
768
  updateShowApprovePopup(true);
672
769
  }, onSignApprove: () => {
673
770
  handleSignApprove();
@@ -687,7 +784,6 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
687
784
  showApprovePopup,
688
785
  showRejectPopup,
689
786
  showReAssignPopup,
690
- isOpenBatchUpdate,
691
787
  isOpenDetails,
692
788
  isOpenMaster,
693
789
  isOpenDcmtForm,
@@ -698,6 +794,10 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
698
794
  openS4TViewer,
699
795
  showBackButton,
700
796
  isMobile,
797
+ navStep,
798
+ isSteppedLayout,
799
+ isSelectorCollapsed,
800
+ currentSearchResults,
701
801
  currentMetadataValues,
702
802
  onRefreshSearchAsyncDatagrid,
703
803
  handleNavigateToWGs,
@@ -705,9 +805,14 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
705
805
  editPdfForm,
706
806
  showNoDcmtFoundMessage
707
807
  ]);
708
- 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]);
808
+ /**
809
+ * Il risultato è davvero a video: le pagine restano montate anche in secondo piano, e senza questo
810
+ * bacheca e anteprima scaricherebbero il documento fuori campo.
811
+ */
812
+ const isSearchResultOnScreen = isVisible && !isOpenDcmtForm && !isOpenDetails && !isOpenMaster;
813
+ const tmBlog = useMemo(() => _jsx(TMDcmtBlog, { tid: focusedItem?.TID, did: focusedItem?.DID, isPageVisible: isSearchResultOnScreen, fetchBlogDataTrigger: refreshBlogTrigger, allTasks: allTasks, getAllTasks: getAllTasks, deleteTaskByIdsCallback: deleteTaskByIdsCallback, addTaskCallback: addTaskCallback, editTaskCallback: editTaskCallback, handleNavigateToWGs: handleNavigateToWGs, handleNavigateToDossiers: handleNavigateToDossiers }), [focusedItem, allTasks, refreshBlogTrigger, isSearchResultOnScreen, handleNavigateToWGs, handleNavigateToDossiers, onRefreshBlogDatagrid]);
709
814
  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]);
815
+ const tmDcmtPreview = useMemo(() => _jsx(TMDcmtPreviewWrapper, { refreshPreviewTrigger: refreshPreviewTrigger, currentDcmt: currentDcmt, isPageVisible: isSearchResultOnScreen, onBack: backHandlerSecondary }), [currentDcmt, refreshPreviewTrigger, isSearchResultOnScreen, backHandlerSecondary]);
711
816
  // Auto-fetch indexing info when drawer is open and focusedItem changes
712
817
  useEffect(() => {
713
818
  if (!focusedItem || !showIndexingInfo)
@@ -764,6 +869,22 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
764
869
  }
765
870
  return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', height: '100%', overflow: 'hidden', width: '100%' }, children: [_jsx("div", { style: { padding: '10px', overflow: 'auto', flex: 1 }, children: _jsx("div", { dangerouslySetInnerHTML: { __html: ftExplanation } }) }), _jsxs(StyledIndexingInfoSection, { children: [_jsxs(StyledIndexingToggle, { onClick: handleToggleIndexingInfo, disabled: loadingIndexingInfo, children: [_jsx(StyledLeftContent, { children: _jsx("span", { children: SDKUI_Localizator.IndexingInformation }) }), _jsx(StyledRightContent, { children: _jsx(StyledChevron, { "$isOpen": showIndexingInfo, children: "\u25BC" }) })] }), showIndexingInfo && indexingInfo && (_jsxs(StyledIndexingInfoBox, { children: [_jsx("div", { dangerouslySetInnerHTML: { __html: indexingInfo } }), loadingIndexingInfo && (_jsxs("div", { style: { position: 'absolute', top: '50%', left: '50%', transform: 'translate(-50%, -50%)', background: 'rgba(255, 255, 255, 0.9)', padding: '10px', borderRadius: '4px', boxShadow: '0 2px 8px rgba(0,0,0,0.15)' }, children: [SDKUI_Localizator.Loading, "..."] }))] }))] })] }));
766
871
  }, [selectedSearchResult, focusedItem, indexingInfo, showIndexingInfo, loadingIndexingInfo]);
872
+ /**
873
+ * Su mobile i pannelli si mostrano uno per volta, quindi il layout non va ricordato; con groupId
874
+ * i pannelli sono di un panel manager esterno, che ha un layout suo. Senza la barra laterale
875
+ * l'utente non può aprire/chiudere i pannelli: non ha senso applicargli il layout salvato (li
876
+ * troverebbe aperti senza poterli chiudere) né lasciargli sporcare quello condiviso.
877
+ * Il layout salvato vale solo per il login con Surfer: gli altri moduli hanno layout propri.
878
+ */
879
+ const isLayoutPersisted = SDK_Globals.tmSession?.SessionDescr?.appModuleID === AppModules.SURFER && !isMobile && !groupId?.length && showSearchResultSidebar && PERSISTED_LAYOUT_CONTEXTS.includes(context);
880
+ /**
881
+ * Pannelli che questo risultato non può mostrare (la bacheca è l'unico, vedi PanelDisabledStateHandler).
882
+ * Il layout è condiviso con gli altri risultati aperti: qui non vanno aperti quando il layout lo chiede,
883
+ * e il loro stato salvato non va toccato, altrimenti li si chiuderebbe dove invece sono attivi.
884
+ */
885
+ const disabledLayoutPanelIds = isBoardDisabled ? ['tmBlog'] : [];
886
+ /** Bacheca aperta nel layout salvato: da riaprire quando il tipo documento torna a prevederla */
887
+ const savedBlogVisible = isLayoutPersisted && (getSearchResultPanelLayout()['tmBlog']?.visible ?? false);
767
888
  const allInitialPanelVisibility = {
768
889
  'tmSearchResult': true,
769
890
  'tmBlog': false,
@@ -786,9 +907,10 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
786
907
  component: tmSearchResult,
787
908
  panelContainer: {
788
909
  title: getTitleHeader(),
910
+ enableTitleMarquee: true,
789
911
  showHeader: showToolbarHeader,
790
912
  allowMaximize: !isMobile,
791
- onBack: getOnBackHandler(true),
913
+ onBack: backHandler,
792
914
  onClose: isClosable ? onBack : undefined,
793
915
  toolbar: searchResutlToolbar
794
916
  },
@@ -803,7 +925,7 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
803
925
  panelContainer: {
804
926
  title: SDKUI_Localizator.BlogCase,
805
927
  allowMaximize: !isMobile,
806
- onBack: getOnBackHandler(),
928
+ onBack: backHandlerSecondary,
807
929
  }
808
930
  },
809
931
  toolbarOptions: { icon: _jsx(IconBoard, { fontSize: 24 }), visible: getSearchToolbarVisibility(SDK_Globals.tmSession?.SessionDescr?.appModuleID ?? AppModules.SURFER).tmBlog, orderNumber: 2, isActive: allInitialPanelVisibility['tmBlog'] }
@@ -816,7 +938,7 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
816
938
  panelContainer: {
817
939
  title: SDKUI_Localizator.MetadataSystem,
818
940
  allowMaximize: !isMobile,
819
- onBack: getOnBackHandler(),
941
+ onBack: backHandlerSecondary,
820
942
  }
821
943
  },
822
944
  toolbarOptions: { icon: _jsx(IconDcmtTypeSys, { fontSize: 24 }), visible: getSearchToolbarVisibility(SDK_Globals.tmSession?.SessionDescr?.appModuleID ?? AppModules.SURFER).tmSysMetadata, orderNumber: 3, isActive: allInitialPanelVisibility['tmSysMetadata'] }
@@ -830,7 +952,7 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
830
952
  panelContainer: {
831
953
  title: SDKUI_Localizator.ResultDetails,
832
954
  allowMaximize: !isMobile,
833
- onBack: getOnBackHandler(),
955
+ onBack: backHandlerSecondary,
834
956
  }
835
957
  },
836
958
  toolbarOptions: { icon: _jsx(IconPlatform, { fontSize: 20 }), visible: getSearchToolbarVisibility(SDK_Globals.tmSession?.SessionDescr?.appModuleID ?? AppModules.SURFER).tmFullTextSearch, orderNumber: 4, isActive: allInitialPanelVisibility['tmFullTextSearch'] }
@@ -844,7 +966,9 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
844
966
  },
845
967
  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
968
  }
847
- ], [tmSearchResult, tmBlog, tmSysMetadata, tmDcmtPreview, tmFullTextSearch, showToolbarHeader, context, isMobile]);
969
+ ], [tmSearchResult, tmBlog, tmSysMetadata, tmDcmtPreview, tmFullTextSearch, showToolbarHeader, context, isMobile, backHandler, backHandlerSecondary, isClosable, onBack]);
970
+ /** Contenuto del panel manager: con groupId sta nel provider del contenitore, altrimenti in quello qui sotto */
971
+ const panelManagerContent = (_jsxs(_Fragment, { children: [_jsx(PanelDisabledStateHandler, { isBoardDisabled: isBoardDisabled, savedBlogVisible: savedBlogVisible }), isLayoutPersisted && _jsx(SavedLayoutSyncHandler, { disabledPanelIds: disabledLayoutPanelIds }), _jsx(ShowMainPanelBridge, { showMainPanelRef: showMainPanelRef }), _jsx(TMPanelManagerContainer, { panels: initialPanels, direction: "horizontal", parentId: groupId, showToolbar: showSearchResultSidebar })] }));
848
972
  return (_jsxs(StyledMultiViewPanel, { "$isVisible": isVisible, children: [_jsx(StyledMultiViewPanel, { "$isVisible": !isOpenDcmtForm && !isOpenDetails && !isOpenMaster, style: {
849
973
  display: 'flex',
850
974
  flexDirection: isMobile ? 'column' : 'row',
@@ -852,13 +976,39 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
852
976
  gap: Gutters.getGutters(),
853
977
  width: '100%',
854
978
  height: '100%',
855
- }, 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 })] })
857
- :
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] }));
979
+ }, 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) ? panelManagerContent
980
+ // Senza le prop di persistenza il provider si comporta come quello base:
981
+ // i contesti che non ricordano il layout restano come prima
982
+ : _jsx(TMPanelManagerWithPersistenceProvider, { panels: initialPanels, initialVisibility: allInitialPanelVisibility, defaultDimensions: initialPanelDimensions, initialDimensions: initialPanelDimensions, initialMobilePanelId: 'tmSearchResult', isPersistenceEnabled: isLayoutPersisted && hasSearchResultPanelLayout(), persistPanelStates: isLayoutPersisted ? (state) => saveSearchResultPanelLayout(state, disabledLayoutPanelIds) : undefined, persistedPanelStates: isLayoutPersisted ? getSearchResultPanelLayout() : undefined, children: panelManagerContent }) }) }) }), renderDcmtOperations] }));
859
983
  };
860
984
  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 }) => {
985
+ /** Cerca una voce di operazione per id, anche dentro i sottomenu */
986
+ const findOperationItemById = (items, id) => {
987
+ for (const item of items) {
988
+ if (item.id === id)
989
+ return item;
990
+ const found = item.submenu ? findOperationItemById(item.submenu, id) : undefined;
991
+ if (found)
992
+ return found;
993
+ }
994
+ return undefined;
995
+ };
996
+ /**
997
+ * Azioni della colonna di riga. Gli id sono quelli di useDocumentOperations, così il comportamento è
998
+ * lo stesso del menu contestuale; icone e colori sono allineati alle altre griglie mentre il tooltip
999
+ * resta il nome dell'operazione. "Cancella" è la cancellazione logica: ripristino ed eliminazione
1000
+ * fisica restano nel menu contestuale, dove la scelta è esplicita.
1001
+ */
1002
+ const rowActions = [
1003
+ { operationId: 'open-form', icon: _jsx(IconEdit, { fontSize: 18, color: TMColors.primary }) },
1004
+ { operationId: 'dup', icon: _jsx(IconDuplicate, { fontSize: 18, color: TMColors.tertiary }) },
1005
+ { operationId: 'del-log', icon: _jsx(IconDelete, { fontSize: 18, color: TMColors.error }) },
1006
+ ];
1007
+ const rowActionsStyles = {
1008
+ cell: { display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '14px' },
1009
+ icon: { cursor: 'pointer', display: 'flex' },
1010
+ };
1011
+ 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
1012
  const [dataSource, setDataSource] = useState();
863
1013
  const [columns, setColumns] = useState([]);
864
1014
  const [selectedRowKeys, setSelectedRowKeys] = useState([]);
@@ -879,8 +1029,8 @@ const TMSearchResultGrid = ({ openInOffice, fromDTD, operationItems, allUsers, i
879
1029
  useEffect(() => {
880
1030
  updateDataSourceFromDataGrid(dataSource ?? []);
881
1031
  }, [dataSource]);
882
- const { loadDataListsAsync, renderDataListCell, dataListsCache } = useDataListItem();
883
- const { loadUsersAsync, renderUserIdViewer, usersCache } = useDataUserIdItem();
1032
+ const { loadDataListsAsync, renderDataListCell, dataListsCache, getDataListItem } = useDataListItem();
1033
+ const { loadUsersAsync, renderUserIdViewer, usersCache, getUserItem, getCompleteUserName } = useDataUserIdItem();
884
1034
  useEffect(() => {
885
1035
  // Sincronizza focusedItem con inputFocusedItem dal padre
886
1036
  if (deepCompare(inputFocusedItem, focusedItem))
@@ -1112,6 +1262,57 @@ const TMSearchResultGrid = ({ openInOffice, fromDTD, operationItems, allUsers, i
1112
1262
  });
1113
1263
  return cols;
1114
1264
  }, [searchResult, dataType, cellRender, getDisplayFormat, dataListsCache, usersCache]);
1265
+ /**
1266
+ * Colonne in cui il testo mostrato è diverso dal valore archiviato: date e numeri formattati, descrizioni
1267
+ * di liste dati e utenti. Su queste la ricerca della griglia va fatta sul testo mostrato, altrimenti non
1268
+ * trova nulla (es. cercando 29/01/2023 su una data, che nei dati è una stringa ISO).
1269
+ * Le cache sono ref lette al momento della ricerca: a quel punto sono già popolate.
1270
+ */
1271
+ const searchTextGetters = useMemo(() => {
1272
+ const getters = new Map();
1273
+ const columnsOfResult = searchResult?.dtdResult?.columns;
1274
+ if (!columnsOfResult)
1275
+ return getters;
1276
+ const uniqueKeys = generateUniqueColumnKeys(columnsOfResult, searchResult?.fromTID);
1277
+ columnsOfResult.forEach((col, index) => {
1278
+ const dataField = uniqueKeys[index];
1279
+ if (!dataField)
1280
+ return;
1281
+ const dataDomain = MetadataDataDomains[(col.extendedProperties?.["DataDomain"] ?? "None")];
1282
+ const dataListID = Number(col.extendedProperties?.["DataListID"]);
1283
+ if (dataDomain === MetadataDataDomains.DataList && dataListID) {
1284
+ getters.set(dataField, (rowData) => getDataListItem(dataListID, rowData?.[dataField])?.name ?? String(rowData?.[dataField] ?? ''));
1285
+ return;
1286
+ }
1287
+ if (dataDomain === MetadataDataDomains.UserID) {
1288
+ getters.set(dataField, (rowData) => {
1289
+ const rawValue = rowData?.[dataField];
1290
+ // Un campo utente vuoto non mostra nulla: senza questo controllo Number('') darebbe 0, cioè l'utente di sistema
1291
+ if (rawValue === undefined || rawValue === null || rawValue === '')
1292
+ return '';
1293
+ const userId = Number(rawValue);
1294
+ if (Number.isNaN(userId))
1295
+ return String(rawValue);
1296
+ if (userId === 0)
1297
+ return SDKUI_Localizator.SystemUser ?? '';
1298
+ const ud = getUserItem(userId);
1299
+ return (ud ? getCompleteUserName(ud.domain, ud.name) : undefined) ?? String(rawValue);
1300
+ });
1301
+ return;
1302
+ }
1303
+ // Data e numero vengono mostrati con il formato definito sulle proprietà estese della colonna
1304
+ const { format, formatCulture } = getColumnFormatInfo(col);
1305
+ if (col.dataType === DataColumnTypes.DateTime) {
1306
+ getters.set(dataField, (rowData) => getDateDisplayText(rowData?.[dataField], format, formatCulture));
1307
+ return;
1308
+ }
1309
+ if (col.dataType === DataColumnTypes.Number) {
1310
+ getters.set(dataField, (rowData) => getNumberDisplayText(rowData?.[dataField], format, formatCulture));
1311
+ }
1312
+ });
1313
+ return getters;
1314
+ }, [searchResult, getDataListItem, getUserItem, getCompleteUserName]);
1315
+ const customizeColumns = useMemo(() => customizeSearchByDisplayText(searchTextGetters), [searchTextGetters]);
1115
1316
  /**
1116
1317
  * Effect che carica i dati necessari e genera le colonne della griglia.
1117
1318
  * Esegue il caricamento delle cache (DataList e UserID) in parallelo prima di generare le colonne,
@@ -1203,6 +1404,9 @@ const TMSearchResultGrid = ({ openInOffice, fromDTD, operationItems, allUsers, i
1203
1404
  // Era sull'icona, non fare nulla
1204
1405
  return;
1205
1406
  }
1407
+ // Due click rapidi su un'icona della colonna azioni non devono aprire anche il form
1408
+ if (e.event?.target.closest('.tm-row-actions'))
1409
+ return;
1206
1410
  // Verifica che il documento abbia TID e DID validi prima di procedere
1207
1411
  if (!e.data.TID || !e.data.DID) {
1208
1412
  ShowAlert({
@@ -1216,8 +1420,41 @@ const TMSearchResultGrid = ({ openInOffice, fromDTD, operationItems, allUsers, i
1216
1420
  onFocusedItemChanged?.(e.data);
1217
1421
  onDblClick();
1218
1422
  }, [onDblClick]);
1423
+ /**
1424
+ * Colonna delle azioni di riga, in testa alla griglia (subito dopo la checkbox di selezione, che
1425
+ * DevExtreme tiene sempre per prima). Vive solo qui e non nello stato `columns`, che è quello
1426
+ * sincronizzato col padre per l'esportazione: una colonna senza dataField finirebbe in Excel come
1427
+ * colonna vuota.
1428
+ *
1429
+ * Le operazioni sono lette da una ref perché `operationItems` è ricostruito a ogni render del
1430
+ * padre: usarlo come dipendenza renderebbe nuova la colonna, e con essa `dataColumns`, ad ogni
1431
+ * giro. La cellRender le rilegge a ogni ridisegno della griglia, quindi resta comunque aggiornata.
1432
+ */
1433
+ const operationItemsRef = useRef(operationItems);
1434
+ operationItemsRef.current = operationItems;
1435
+ const actionsColumn = useMemo(() => ({
1436
+ caption: '',
1437
+ width: 110,
1438
+ alignment: 'center',
1439
+ allowSorting: false,
1440
+ allowFiltering: false,
1441
+ allowHeaderFiltering: false,
1442
+ allowResizing: false,
1443
+ allowReordering: false,
1444
+ allowHiding: false,
1445
+ allowExporting: false,
1446
+ showInColumnChooser: false,
1447
+ cssClass: 'tm-row-actions',
1448
+ cellRender: (cellData) => (_jsx("div", { style: rowActionsStyles.cell, children: rowActions.map(action => {
1449
+ const operation = findOperationItemById(operationItemsRef.current, action.operationId);
1450
+ if (!operation || operation.visible === false)
1451
+ return null;
1452
+ return (_jsx("span", { style: rowActionsStyles.icon, title: operation.name, onClick: () => onRowActionRequest?.(action.operationId, cellData.data), children: action.icon }, action.operationId));
1453
+ }) })),
1454
+ }), [onRowActionRequest]);
1219
1455
  const dataColumns = useMemo(() => {
1220
1456
  return [
1457
+ ...(showActionsColumn ? [actionsColumn] : []),
1221
1458
  {
1222
1459
  dataType: "object",
1223
1460
  dataField: 'FILEEXT',
@@ -1231,7 +1468,7 @@ const TMSearchResultGrid = ({ openInOffice, fromDTD, operationItems, allUsers, i
1231
1468
  },
1232
1469
  ...columns.filter(col => col.dataField !== 'FILEEXT')
1233
1470
  ];
1234
- }, [columns, onDownloadDcmtsAsync, openInOffice]);
1471
+ }, [columns, onDownloadDcmtsAsync, openInOffice, showActionsColumn, actionsColumn]);
1235
1472
  const onContentReady = useCallback((e) => {
1236
1473
  if (e === undefined)
1237
1474
  return;
@@ -1242,21 +1479,24 @@ const TMSearchResultGrid = ({ openInOffice, fromDTD, operationItems, allUsers, i
1242
1479
  setVisibleItems(visibleData);
1243
1480
  }, []);
1244
1481
  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
- } })] });
1482
+ const gridCounterConfig = useMemo(() => {
1483
+ if (!showCounterConfig)
1484
+ return { show: false };
1485
+ const defaultItems = dcmtsReturned !== undefined && dcmtsFound !== undefined
1486
+ ? new Map([
1487
+ [CounterItemKey.allItems, {
1488
+ key: CounterItemKey.allItems,
1489
+ show: true,
1490
+ caption: `${dcmtsReturned} restituiti su ${dcmtsFound} trovati`,
1491
+ value: `${dcmtsReturned}/${dcmtsFound}`,
1492
+ icon: _jsx(IconAll, {}),
1493
+ order: -1
1494
+ }]
1495
+ ])
1496
+ : undefined;
1497
+ return { ...counterConfig, show: true, items: counterConfig?.items ?? defaultItems };
1498
+ }, [showCounterConfig, counterConfig, dcmtsReturned, dcmtsFound]);
1499
+ 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
1500
  };
1261
1501
  //#region TMSearchResultSelector
1262
1502
  const StyledItemTemplate = styled.div `
@@ -1317,14 +1557,29 @@ const TMSearchResultSelector = ({ searchResults = [], disableAccordionIfSingleCa
1317
1557
  }
1318
1558
  }
1319
1559
  }, [sortedCategories.length, autoSelectFirst]);
1560
+ // La preselezione esterna va applicata una volta per valore: riapplicarla a ogni cambio di
1561
+ // searchResults faceva sì che un refresh annullasse la navigazione dell'utente.
1562
+ const appliedSelectedTIDRef = useRef(undefined);
1320
1563
  useEffect(() => {
1321
- if (selectedTID !== undefined) {
1322
- const found = searchResults.find(r => Number(r.fromTID) === selectedTID);
1323
- handleSelect(found);
1564
+ // Richiesta ritirata: dimentichiamo il valore applicato, così se il chiamante richiede di
1565
+ // nuovo lo stesso TID la preselezione riparte
1566
+ if (selectedTID === undefined) {
1567
+ appliedSelectedTIDRef.current = undefined;
1568
+ return;
1324
1569
  }
1570
+ if (appliedSelectedTIDRef.current === selectedTID)
1571
+ return;
1572
+ const found = searchResults.find(r => Number(r.fromTID) === selectedTID);
1573
+ if (!found)
1574
+ return; // risultati non ancora arrivati: riproviamo al prossimo giro
1575
+ appliedSelectedTIDRef.current = selectedTID;
1576
+ handleSelect(found);
1325
1577
  }, [selectedTID, searchResults]);
1326
- const handleSelect = useCallback((result) => {
1327
- if (selectedResult !== result && result !== undefined) {
1578
+ // force = true per i click dell'utente: la selezione va notificata anche se il tipo documento è già quello selezionato
1579
+ const handleSelect = useCallback((result, force = false) => {
1580
+ if (result === undefined)
1581
+ return;
1582
+ if (selectedResult !== result || force) {
1328
1583
  setSelectedResult(result);
1329
1584
  onSelectionChanged?.(result);
1330
1585
  }
@@ -1369,32 +1624,88 @@ const TMSearchResultSelector = ({ searchResults = [], disableAccordionIfSingleCa
1369
1624
  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
1625
  itemHeight: 40
1371
1626
  })), [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))) }));
1627
+ 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
1628
  if (disableAccordionIfSingleCategory && sortedCategories.length === 1) {
1374
1629
  const category = sortedCategories[0];
1375
1630
  return renderCategoryItems(category);
1376
1631
  }
1377
- return (_jsx(TMAccordionNew, { groups: accordionGroups, selectedItem: selectedResult, onSelectedItemChange: (result) => handleSelect(result) }));
1632
+ return (_jsx(TMAccordionNew, { groups: accordionGroups, selectedItem: selectedResult, onSelectedItemChange: (result) => handleSelect(result, true) }));
1378
1633
  };
1379
1634
  //#endregion TMSearchResultSelector
1380
- const PanelDisabledStateHandler = ({ isBoardDisabled }) => {
1381
- const { setPanelVisibilityById, setToolbarButtonDisabled } = useTMPanelManagerContext();
1635
+ /** Senza UI: vive dentro il provider e passa a onBack, via ref, l'azione "mostra il pannello lista". */
1636
+ const ShowMainPanelBridge = ({ showMainPanelRef }) => {
1637
+ const { setPanelVisibilityById } = useTMPanelManagerContext();
1382
1638
  useEffect(() => {
1383
- if (isBoardDisabled) {
1384
- setToolbarButtonDisabled('tmBlog', true);
1385
- setPanelVisibilityById('tmBlog', false);
1386
- }
1387
- else {
1388
- setToolbarButtonDisabled('tmBlog', false);
1389
- }
1639
+ // Su mobile mostrare un pannello nasconde gli altri; su desktop la lista è già visibile
1640
+ showMainPanelRef.current = () => setPanelVisibilityById('tmSearchResult', true);
1641
+ return () => { showMainPanelRef.current = null; };
1642
+ }, [setPanelVisibilityById, showMainPanelRef]);
1643
+ return null;
1644
+ };
1645
+ /** Senza UI: allinea i pannelli quando il layout viene salvato da un altro risultato di ricerca già aperto */
1646
+ const SavedLayoutSyncHandler = ({ disabledPanelIds }) => {
1647
+ const { panelVisibility, panelDimensions, maximizedPanels, isResizingActive, setPanelVisibilityById, setPanelDimensionsById } = useTMPanelManagerContext();
1648
+ // Il layout è condiviso fra i risultati aperti: si sottoscrive la firma, che è una stringa, perché
1649
+ // React confronta le letture per riferimento e su un oggetto ri-renderizzerebbe all'infinito
1650
+ const savedLayoutSignature = useSyncExternalStore(subscribeSearchResultPanelLayout, getSearchResultPanelLayoutSignature);
1651
+ useEffect(() => {
1652
+ // Con un pannello massimizzato o un separatore in movimento comanda l'utente di questa istanza
1653
+ if (maximizedPanels.length > 0 || isResizingActive)
1654
+ return;
1655
+ // Al mount panelVisibility è ancora vuota: lo stato iniziale lo applica il provider.
1656
+ // Un pannello disabilitato qui non va aperto, gli altri si allineano comunque
1657
+ const panelsToApply = Object.entries(getSearchResultPanelLayout())
1658
+ .filter(([id, state]) => panelVisibility[id] !== undefined && !(state?.visible && disabledPanelIds.includes(id)));
1659
+ panelsToApply.forEach(([id, state]) => {
1660
+ const isVisible = state?.visible ?? false;
1661
+ if (panelVisibility[id] !== isVisible)
1662
+ setPanelVisibilityById(id, isVisible);
1663
+ });
1664
+ // Le larghezze salvate valgono come pesi, non come percentuali: qui si normalizzano sui soli pannelli
1665
+ // che questo risultato apre davvero, così lo spazio di quelli scartati non resta vuoto
1666
+ const panelsToShow = panelsToApply.filter(([, state]) => state?.visible && parseFloat(state.width) > 0 && parseFloat(state.height) > 0);
1667
+ const weightTotal = panelsToShow.reduce((total, [, state]) => total + parseFloat(state.width), 0);
1668
+ if (weightTotal <= 0)
1669
+ return;
1670
+ // Dopo la visibilità, che accoda un suo ricalcolo delle quote: così restano quelle salvate.
1671
+ // Le quote sono arrotondate, altrimenti due risultati si rincorrono sugli ultimi decimali
1672
+ panelsToShow.forEach(([id, state]) => {
1673
+ const width = `${Math.round(parseFloat(state.width) / weightTotal * 10000) / 100}%`;
1674
+ if (panelDimensions[id]?.width === width && panelDimensions[id]?.height === state.height)
1675
+ return;
1676
+ setPanelDimensionsById(id, width, state.height);
1677
+ });
1678
+ }, [savedLayoutSignature]);
1679
+ return null;
1680
+ };
1681
+ const PanelDisabledStateHandler = ({ isBoardDisabled, savedBlogVisible = false }) => {
1682
+ const { panelVisibility, setPanelVisibilityById, setToolbarButtonDisabled } = useTMPanelManagerContext();
1683
+ const isBlogVisible = panelVisibility['tmBlog'] ?? false;
1684
+ useEffect(() => {
1685
+ setToolbarButtonDisabled('tmBlog', isBoardDisabled);
1390
1686
  }, [isBoardDisabled]);
1687
+ /**
1688
+ * Senza bacheca nel tipo documento il pannello va chiuso ogni volta che ricompare, non solo al cambio
1689
+ * di stato: il layout salvato viene applicato dopo questo effetto e può riportarlo a video.
1690
+ * È un effetto di layout per chiuderlo prima che venga disegnato, altrimenti lampeggia.
1691
+ */
1692
+ useLayoutEffect(() => {
1693
+ if (isBoardDisabled && isBlogVisible)
1694
+ setPanelVisibilityById('tmBlog', false);
1695
+ }, [isBoardDisabled, isBlogVisible]);
1696
+ // Bacheca di nuovo prevista: si riapre come da layout salvato. isBlogVisible non è fra le dipendenze,
1697
+ // altrimenti riaprirebbe il pannello appena chiuso dall'utente
1698
+ useEffect(() => {
1699
+ if (!isBoardDisabled && savedBlogVisible && !isBlogVisible)
1700
+ setPanelVisibilityById('tmBlog', true);
1701
+ }, [isBoardDisabled, savedBlogVisible]);
1391
1702
  return null;
1392
1703
  };
1393
- const TMDcmtPreviewWrapper = ({ refreshPreviewTrigger, currentDcmt, isVisible, onBack }) => {
1704
+ const TMDcmtPreviewWrapper = ({ refreshPreviewTrigger, currentDcmt, isVisible, isPageVisible = true, onBack }) => {
1394
1705
  const { setPanelVisibilityById, toggleMaximize, isResizingActive, countVisibleLeafPanels } = useTMPanelManagerContext();
1395
1706
  const deviceType = useDeviceType();
1396
1707
  const isMobile = deviceType === DeviceType.MOBILE;
1397
- return (_jsx(TMDcmtPreview, { dcmtData: currentDcmt, onClosePanel: (!isMobile && countVisibleLeafPanels() > 1) ? () => setPanelVisibilityById('tmDcmtPreview', false) : undefined, onBack: onBack, allowMaximize: !isMobile && countVisibleLeafPanels() > 1, onMaximizePanel: (!isMobile && countVisibleLeafPanels() > 1) ? () => toggleMaximize("tmDcmtPreview") : undefined, isResizingActive: isResizingActive, isVisible: isVisible }, refreshPreviewTrigger));
1708
+ return (_jsx(TMDcmtPreview, { dcmtData: currentDcmt, onClosePanel: (!isMobile && countVisibleLeafPanels() > 1) ? () => setPanelVisibilityById('tmDcmtPreview', false) : undefined, onBack: onBack, allowMaximize: !isMobile && countVisibleLeafPanels() > 1, onMaximizePanel: (!isMobile && countVisibleLeafPanels() > 1) ? () => toggleMaximize("tmDcmtPreview") : undefined, isResizingActive: isResizingActive, isVisible: isVisible && isPageVisible }, refreshPreviewTrigger));
1398
1709
  };
1399
1710
  // Styled Components
1400
1711
  const StyledPlaceholder = styled.div `