@topconsultnpm/sdkui-react 6.22.0-dev2.4 → 6.22.0-dev2.41

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 (124) 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 +11 -0
  12. package/lib/components/base/TMModal.js +47 -13
  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/TMMergeToPdfForm.d.ts +1 -2
  46. package/lib/components/features/documents/TMMergeToPdfForm.js +30 -5
  47. package/lib/components/features/documents/TMMultiMasterDetailDcmtsForm.d.ts +45 -0
  48. package/lib/components/features/documents/TMMultiMasterDetailDcmtsForm.js +323 -0
  49. package/lib/components/features/documents/TMMultiMasterDetailDcmtsUtils.d.ts +370 -0
  50. package/lib/components/features/documents/TMMultiMasterDetailDcmtsUtils.js +283 -0
  51. package/lib/components/features/documents/TMRelationViewer.d.ts +14 -1
  52. package/lib/components/features/documents/TMRelationViewer.js +69 -20
  53. package/lib/components/features/search/TMSearch.js +31 -18
  54. package/lib/components/features/search/TMSearchQueryEditor.js +5 -3
  55. package/lib/components/features/search/TMSearchQueryPanel.d.ts +2 -1
  56. package/lib/components/features/search/TMSearchQueryPanel.js +45 -9
  57. package/lib/components/features/search/TMSearchResult.d.ts +15 -0
  58. package/lib/components/features/search/TMSearchResult.js +509 -187
  59. package/lib/components/features/search/TMTreeSelector.js +18 -5
  60. package/lib/components/features/workflow/TMWorkflowPopup.d.ts +10 -0
  61. package/lib/components/features/workflow/TMWorkflowPopup.js +8 -4
  62. package/lib/components/layout/panelManager/TMPanelManagerContainer.d.ts +1 -0
  63. package/lib/components/layout/panelManager/TMPanelManagerContainer.js +24 -14
  64. package/lib/components/layout/panelManager/TMPanelManagerContext.js +68 -93
  65. package/lib/components/layout/panelManager/TMPanelWrapper.js +2 -2
  66. package/lib/components/layout/panelManager/types.d.ts +3 -0
  67. package/lib/components/layout/panelManager/utils.js +13 -11
  68. package/lib/components/viewers/TMMidViewer.js +4 -1
  69. package/lib/components/viewers/TMTidViewer.js +25 -11
  70. package/lib/devextreme-license.d.ts +1 -0
  71. package/lib/devextreme-license.js +3 -0
  72. package/lib/helper/SDKUI_Globals.d.ts +109 -0
  73. package/lib/helper/SDKUI_Globals.js +150 -0
  74. package/lib/helper/SDKUI_Localizator.d.ts +34 -0
  75. package/lib/helper/SDKUI_Localizator.js +340 -0
  76. package/lib/helper/TMIcons.d.ts +1 -0
  77. package/lib/helper/TMIcons.js +3 -0
  78. package/lib/helper/dataGridSearchHelper.d.ts +28 -0
  79. package/lib/helper/dataGridSearchHelper.js +51 -0
  80. package/lib/helper/dcmtsHelper.d.ts +20 -0
  81. package/lib/helper/dcmtsHelper.js +58 -1
  82. package/lib/helper/helpers.d.ts +20 -0
  83. package/lib/helper/helpers.js +55 -1
  84. package/lib/helper/index.d.ts +1 -0
  85. package/lib/helper/index.js +1 -0
  86. package/lib/helper/queryHelper.d.ts +2 -0
  87. package/lib/helper/queryHelper.js +3 -1
  88. package/lib/hooks/tmDistinctValuesGridHelper.d.ts +16 -0
  89. package/lib/hooks/tmDistinctValuesGridHelper.js +31 -0
  90. package/lib/hooks/useArchiveListForm.d.ts +39 -0
  91. package/lib/hooks/useArchiveListForm.js +46 -0
  92. package/lib/hooks/useCaseFlowApprove.d.ts +56 -0
  93. package/lib/hooks/useCaseFlowApprove.js +157 -0
  94. package/lib/hooks/useDataUserIdItem.d.ts +1 -0
  95. package/lib/hooks/useDataUserIdItem.js +1 -0
  96. package/lib/hooks/useDocumentOperations.d.ts +4 -0
  97. package/lib/hooks/useDocumentOperations.js +41 -7
  98. package/lib/hooks/useMultiMasterDetailDcmts.d.ts +113 -0
  99. package/lib/hooks/useMultiMasterDetailDcmts.js +760 -0
  100. package/lib/hooks/useRelatedDocuments.js +2 -26
  101. package/lib/hooks/useShowDossiersV5.d.ts +12 -0
  102. package/lib/hooks/useShowDossiersV5.js +37 -0
  103. package/lib/hooks/useTMDistinctValuesMetadataDisplay.d.ts +18 -0
  104. package/lib/hooks/useTMDistinctValuesMetadataDisplay.js +103 -0
  105. package/lib/hooks/useTMDistinctValuesQuickSearch.d.ts +49 -0
  106. package/lib/hooks/useTMDistinctValuesQuickSearch.js +307 -0
  107. package/lib/hooks/useTMDistinctValuesSelection.d.ts +42 -0
  108. package/lib/hooks/useTMDistinctValuesSelection.js +103 -0
  109. package/lib/hooks/useTMDistinctValuesSource.d.ts +29 -0
  110. package/lib/hooks/useTMDistinctValuesSource.js +105 -0
  111. package/lib/index.d.ts +2 -0
  112. package/lib/index.js +5 -2
  113. package/lib/services/caseflow/CaseFlowService.d.ts +236 -0
  114. package/lib/services/caseflow/CaseFlowService.js +693 -0
  115. package/lib/services/caseflow/CaseFlowTemplateCacheService.d.ts +28 -0
  116. package/lib/services/caseflow/CaseFlowTemplateCacheService.js +121 -0
  117. package/lib/services/caseflow/caseFlowNameCache.d.ts +32 -0
  118. package/lib/services/caseflow/caseFlowNameCache.js +116 -0
  119. package/lib/services/caseflow/caseFlowWorkItemUtils.d.ts +61 -0
  120. package/lib/services/caseflow/caseFlowWorkItemUtils.js +113 -0
  121. package/lib/services/index.d.ts +4 -0
  122. package/lib/services/index.js +4 -0
  123. package/lib/services/platform_services.d.ts +1 -1
  124. package/package.json +9 -5
@@ -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, onRowDuplicateRequest,
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,69 @@ 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, isDcmtFormModal, 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
+ // La callback è letta da una ref: dipendere dal prop renderebbe nuova requestRowAction, e con essa
408
+ // la colonna delle azioni, a ogni render del padre (vedi actionsColumn in TMSearchResultGrid)
409
+ const onRowDuplicateRequestRef = useRef(onRowDuplicateRequest);
410
+ onRowDuplicateRequestRef.current = onRowDuplicateRequest;
411
+ const requestRowAction = useCallback((operationId, rowData) => {
412
+ if (!showActionsColumn)
413
+ return;
414
+ if (rowData?.TID === undefined || rowData?.DID === undefined) {
415
+ ShowAlert({ message: SDKUI_Localizator.InvalidDcmt, mode: "warning", duration: 3000 });
416
+ return;
417
+ }
418
+ // Duplica ridefinita dal chiamante: agisce sulla riga cliccata, senza toccare fuoco e selezione
419
+ if (operationId === 'dup' && onRowDuplicateRequestRef.current) {
420
+ onRowDuplicateRequestRef.current(rowData);
421
+ return;
422
+ }
423
+ pendingRowActionRef.current = { operationId, TID: rowData.TID, DID: rowData.DID };
424
+ isFirstAutoFocusRef.current = false;
425
+ setSelectedItems([]);
426
+ setFocusedItem(rowData);
427
+ }, [showActionsColumn]);
428
+ // La richiesta viene scartata se il documento sparisce dai risultati (es. refresh dopo
429
+ // un'eliminazione): senza questo resterebbe appesa e partirebbe al prossimo cambio di fuoco.
430
+ useEffect(() => { pendingRowActionRef.current = undefined; }, [selectedSearchResult, lastUpdateSearchTime]);
431
+ useEffect(() => {
432
+ if (!showActionsColumn)
433
+ return;
434
+ const pending = pendingRowActionRef.current;
435
+ if (!pending)
436
+ return;
437
+ // L'hook non ha ancora recepito la riga richiesta: si riproverà al prossimo aggiornamento
438
+ if (selectedDcmtInfos.length !== 1)
439
+ return;
440
+ if (selectedDcmtInfos[0].TID !== pending.TID || selectedDcmtInfos[0].DID !== pending.DID)
441
+ return;
442
+ pendingRowActionRef.current = undefined;
443
+ const operation = findOperationItemById(operationItems, pending.operationId);
444
+ // Ora che il fuoco è sulla riga, `disabled` riflette i permessi per quel documento
445
+ if (!operation || operation.disabled || operation.visible === false)
446
+ return;
447
+ operation.onClick?.();
448
+ }, [showActionsColumn, selectedDcmtInfos, operationItems]);
381
449
  const deviceType = useDeviceType();
382
450
  const isMobile = deviceType === DeviceType.MOBILE;
451
+ const hasSingleDcmtType = currentSearchResults.length === 1;
452
+ // Su mobile si vede un pannello per volta; su desktop selettore e griglia sono affiancati
453
+ const isSteppedLayout = showSelector && isMobile;
454
+ // Un solo tipo documento su desktop: non c'è niente da scegliere, quindi il selettore sparisce
455
+ // (larghezza 0, nessun separatore) e lo spazio va tutto alla griglia. Il menu "Altro" permette
456
+ // di riaprirlo (vedi canToggleDcmtTypesSelection nell'hook).
457
+ const isSelectorCollapsed = showSelector && !isSteppedLayout && hasSingleDcmtType && !showDcmtTypesSelection;
458
+ // Senza selettore lo spazio va tutto alla griglia; altrimenti a step su mobile, affiancati su desktop
459
+ const splitterSizes = (!showSelector || isSelectorCollapsed) ? ['0%', '100%']
460
+ : isSteppedLayout ? (navStep === 'results' ? ['0', '100%'] : ['100%', '0'])
461
+ : ['30%', '70%'];
383
462
  const selectedDocs = getSelectedDcmtsOrFocused(selectedItems, focusedItem);
384
463
  // Disable the "Sign/Approve" button if:
385
464
  // 1. No document or multiple documents are selected, OR
@@ -400,8 +479,14 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
400
479
  setSelectedSearchResult(undefined);
401
480
  return;
402
481
  }
482
+ // Un solo tipo documento: selezionalo e vai ai risultati, a meno che l'utente sia tornato indietro
403
483
  if (searchResults.length === 1) {
484
+ if (navStep === 'selector') {
485
+ setSelectedSearchResult(undefined);
486
+ return;
487
+ }
404
488
  setSelectedSearchResult(searchResults[0]);
489
+ setNavStep('results');
405
490
  return;
406
491
  }
407
492
  // Se il TID precedentemente selezionato esiste ancora nei nuovi risultati, mantienilo.
@@ -415,6 +500,11 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
415
500
  return;
416
501
  }
417
502
  }
503
+ // L'utente è sul selettore
504
+ if (isMobile && navStep !== 'results') {
505
+ setSelectedSearchResult(undefined);
506
+ return;
507
+ }
418
508
  // Altrimenti seleziona il primo risultato ordinato
419
509
  setSelectedSearchResult(orderByName(searchResults)[0]);
420
510
  }, [searchResults]);
@@ -471,7 +561,9 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
471
561
  return;
472
562
  if (focusedItem?.DID != inputDID)
473
563
  return;
474
- openFormHandler(LayoutModes.Update);
564
+ if (focusedItem?.TID === undefined)
565
+ return;
566
+ onDcmtFormOpenChange(true, LayoutModes.Update);
475
567
  }, [inputDID, formAutoOpen, focusedItem]);
476
568
  // Quando openS4TViewer è true, apri automaticamente il viewer S4T
477
569
  useEffect(() => {
@@ -531,47 +623,75 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
531
623
  let counters = (showSelector && disableAccordionIfSingleCategory && searchResults.length > 1) ? getSearchResultCountersSingleCategory(searchResults) : "";
532
624
  if (title)
533
625
  return title + counters;
534
- let titleHeader = 'Ricerca per metadati';
626
+ // Nome del tipo documento selezionato, accodato al titolo
627
+ const selectedDcmtTypeName = selectedSearchResult?.fromName ?? (searchResults?.length === 1 ? searchResults[0]?.fromName : undefined);
628
+ const withSelectedDcmtTypeName = (baseTitle) => selectedDcmtTypeName ? `${baseTitle} - ${selectedDcmtTypeName}` : baseTitle;
629
+ let titleHeader = withSelectedDcmtTypeName('Ricerca per metadati');
535
630
  switch (context) {
536
631
  case SearchResultContext.MASTER_DETAIL:
537
632
  case SearchResultContext.METADATA_SEARCH:
538
633
  titleHeader = `${searchResults?.length > 1 ? selectedSearchResult?.fromName ?? 'Ricerca per metadati' : searchResults[0]?.fromName ?? 'Ricerca per metadati'}`;
539
634
  break;
540
635
  case SearchResultContext.FAVORITES_AND_RECENTS:
541
- titleHeader = SDKUI_Localizator.FavoritesAndRecentDcmts;
636
+ titleHeader = withSelectedDcmtTypeName(SDKUI_Localizator.FavoritesAndRecentDcmts);
542
637
  break;
543
638
  case SearchResultContext.WORKFLOW_APPROVE:
544
- titleHeader = SDKUI_Localizator.WorkflowApproval;
639
+ titleHeader = withSelectedDcmtTypeName(SDKUI_Localizator.WorkflowApproval);
545
640
  break;
546
641
  case SearchResultContext.FREE_SEARCH:
547
- titleHeader = `Risultato della ricerca full text`;
642
+ titleHeader = withSelectedDcmtTypeName('Risultato della ricerca full text');
548
643
  break;
549
644
  }
550
645
  return titleHeader + counters;
551
646
  };
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?.();
647
+ // Dai risultati si risale al selettore dei tipi documento
648
+ const canGoBackToSelector = isSteppedLayout && navStep === 'results' && selectedSearchResult !== undefined;
649
+ /**
650
+ * Back di tutti i pannelli (e X di chiusura se isClosable). Su mobile risale un livello alla volta:
651
+ * pannello secondario -> lista dei risultati -> selettore dei tipi documento -> uscita.
652
+ * Su desktop i pannelli sono affiancati, quindi resta solo l'uscita.
653
+ *
654
+ * @param fromSecondaryPanel - true se il back arriva da un pannello secondario
655
+ */
656
+ const onBack = useCallback((fromSecondaryPanel = false) => {
657
+ // Non è previsto nessun modo di uscire: non fare niente
658
+ if (!showBackButton && !isClosable)
659
+ return;
660
+ // Torna alla lista senza toccare gli stati: l'utente deve ritrovare la sua selezione
661
+ if (fromSecondaryPanel) {
662
+ showMainPanelRef.current?.();
663
+ return;
573
664
  }
574
- };
665
+ // Sia il ritorno al selettore sia l'uscita ripartono da una lista pulita
666
+ setSelectedSearchResult(undefined);
667
+ setFocusedItem(undefined);
668
+ setSelectedItems([]);
669
+ setSelectedRowKeysFromDataGrid([]);
670
+ setCurrentDcmt(undefined);
671
+ setCurrentMetadataValues([]);
672
+ setShowIndexingInfo(false);
673
+ isFirstAutoFocusRef.current = true;
674
+ // Il chiamante può tenere allineato il suo TID selezionato (es. selectedSearchResultTID)
675
+ onSelectedTIDCleared?.();
676
+ // [2] l'utente ha chiesto la lista: resta lì finché non sceglie lui, niente auto-selezione.
677
+ // [1] si esce: si riparte da capo, quindi l'auto-selezione torna permessa.
678
+ setNavStep(canGoBackToSelector ? 'selector' : 'auto');
679
+ // [2] C'è ancora un livello sopra: restiamo dentro, sul selettore
680
+ if (canGoBackToSelector)
681
+ return;
682
+ // [1] Si esce e si avvisa il chiamante
683
+ onClose?.();
684
+ }, [canGoBackToSelector, showBackButton, isClosable, onClose, onSelectedTIDCleared]);
685
+ /**
686
+ * Back del pannello lista. `undefined` = freccia non renderizzata, quindi la mostriamo solo se
687
+ * ha dove tornare: uno step intermedio, o un onClose per uscire. Con `isClosable` basta la X.
688
+ */
689
+ const backHandler = (showBackButton && !isClosable && (canGoBackToSelector || onClose !== undefined)) ? onBack : undefined;
690
+ /**
691
+ * Back dei pannelli secondari: la stessa onBack marcata come livello [3]. Solo su mobile:
692
+ * su desktop stanno affiancati alla lista e non c'è niente da cui tornare.
693
+ */
694
+ const backHandlerSecondary = useMemo(() => (isMobile && backHandler) ? () => backHandler(true) : undefined, [isMobile, backHandler]);
575
695
  const refreshDataGridAfterRemoveAsync = async () => {
576
696
  let index = selectedSearchResult?.dtdResult?.columns?.findIndex(col => col.caption === 'DID');
577
697
  let selectedRows = [];
@@ -603,21 +723,6 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
603
723
  return newResults;
604
724
  });
605
725
  };
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
726
  const removeDcmtFromFavsOrRecents = async () => {
622
727
  switch (selectedSearchResult?.category) {
623
728
  case "Favorites":
@@ -634,9 +739,9 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
634
739
  };
635
740
  const onSearchResultSelectionChanged = (e) => {
636
741
  setSelectedSearchResult(e);
637
- if (deviceType === DeviceType.MOBILE) {
638
- setSplitterSize(['0', '100%']);
639
- }
742
+ // Scelto il tipo documento si passa ai risultati (a livello di layout è ignorato se i due
743
+ // step sono affiancati, ma vale comunque come "l'utente non è più fermo sul selettore")
744
+ setNavStep('results');
640
745
  onSelectedTIDChanged?.(e.fromTID);
641
746
  };
642
747
  const showToppyForApprove = useMemo(() => {
@@ -660,14 +765,15 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
660
765
  && !showTodoDcmtForm);
661
766
  }, [showToppyForApprove, showToppyDraggableHelpCenter, selectedDocs, showApprovePopup, showRejectPopup, showReAssignPopup, showMoreInfoPopup, editPdfForm, openS4TViewer, showTodoDcmtForm]);
662
767
  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' }) })] });
768
+ _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 &&
769
+ _jsx(TMButton, { btnStyle: 'icon', icon: _jsx(IconCloseOutline, { color: 'white' }), caption: SDKUI_Localizator.Close, onClick: onClose })] });
664
770
  const tmSearchResult = useMemo(() => (!searchResults || searchResults.length <= 0)
665
771
  ? _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
772
  :
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 }) })
773
+ _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 ?
774
+ _jsx(TMLayoutItem, { children: _jsx(TMSearchResultSelector, { searchResults: currentSearchResults, disableAccordionIfSingleCategory: disableAccordionIfSingleCategory, selectedTID: selectedSearchResultTID, selectedSearchResult: selectedSearchResult, autoSelectFirst: !isMobile || hasSingleDcmtType, onSelectionChanged: onSearchResultSelectionChanged }) })
669
775
  :
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: () => {
776
+ _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
777
  updateShowApprovePopup(true);
672
778
  }, onSignApprove: () => {
673
779
  handleSignApprove();
@@ -687,7 +793,6 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
687
793
  showApprovePopup,
688
794
  showRejectPopup,
689
795
  showReAssignPopup,
690
- isOpenBatchUpdate,
691
796
  isOpenDetails,
692
797
  isOpenMaster,
693
798
  isOpenDcmtForm,
@@ -698,6 +803,10 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
698
803
  openS4TViewer,
699
804
  showBackButton,
700
805
  isMobile,
806
+ navStep,
807
+ isSteppedLayout,
808
+ isSelectorCollapsed,
809
+ currentSearchResults,
701
810
  currentMetadataValues,
702
811
  onRefreshSearchAsyncDatagrid,
703
812
  handleNavigateToWGs,
@@ -705,9 +814,14 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
705
814
  editPdfForm,
706
815
  showNoDcmtFoundMessage
707
816
  ]);
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]);
817
+ /**
818
+ * Il risultato è davvero a video: le pagine restano montate anche in secondo piano, e senza questo
819
+ * bacheca e anteprima scaricherebbero il documento fuori campo.
820
+ */
821
+ const isSearchResultOnScreen = isVisible && !isOpenDcmtForm && !isOpenDetails && !isOpenMaster;
822
+ 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
823
  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]);
824
+ const tmDcmtPreview = useMemo(() => _jsx(TMDcmtPreviewWrapper, { refreshPreviewTrigger: refreshPreviewTrigger, currentDcmt: currentDcmt, isPageVisible: isSearchResultOnScreen, onBack: backHandlerSecondary }), [currentDcmt, refreshPreviewTrigger, isSearchResultOnScreen, backHandlerSecondary]);
711
825
  // Auto-fetch indexing info when drawer is open and focusedItem changes
712
826
  useEffect(() => {
713
827
  if (!focusedItem || !showIndexingInfo)
@@ -764,6 +878,22 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
764
878
  }
765
879
  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
880
  }, [selectedSearchResult, focusedItem, indexingInfo, showIndexingInfo, loadingIndexingInfo]);
881
+ /**
882
+ * Su mobile i pannelli si mostrano uno per volta, quindi il layout non va ricordato; con groupId
883
+ * i pannelli sono di un panel manager esterno, che ha un layout suo. Senza la barra laterale
884
+ * l'utente non può aprire/chiudere i pannelli: non ha senso applicargli il layout salvato (li
885
+ * troverebbe aperti senza poterli chiudere) né lasciargli sporcare quello condiviso.
886
+ * Il layout salvato vale solo per il login con Surfer: gli altri moduli hanno layout propri.
887
+ */
888
+ const isLayoutPersisted = SDK_Globals.tmSession?.SessionDescr?.appModuleID === AppModules.SURFER && !isMobile && !groupId?.length && showSearchResultSidebar && PERSISTED_LAYOUT_CONTEXTS.includes(context);
889
+ /**
890
+ * Pannelli che questo risultato non può mostrare (la bacheca è l'unico, vedi PanelDisabledStateHandler).
891
+ * Il layout è condiviso con gli altri risultati aperti: qui non vanno aperti quando il layout lo chiede,
892
+ * e il loro stato salvato non va toccato, altrimenti li si chiuderebbe dove invece sono attivi.
893
+ */
894
+ const disabledLayoutPanelIds = isBoardDisabled ? ['tmBlog'] : [];
895
+ /** Bacheca aperta nel layout salvato: da riaprire quando il tipo documento torna a prevederla */
896
+ const savedBlogVisible = isLayoutPersisted && (getSearchResultPanelLayout()['tmBlog']?.visible ?? false);
767
897
  const allInitialPanelVisibility = {
768
898
  'tmSearchResult': true,
769
899
  'tmBlog': false,
@@ -786,9 +916,10 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
786
916
  component: tmSearchResult,
787
917
  panelContainer: {
788
918
  title: getTitleHeader(),
919
+ enableTitleMarquee: true,
789
920
  showHeader: showToolbarHeader,
790
921
  allowMaximize: !isMobile,
791
- onBack: getOnBackHandler(true),
922
+ onBack: backHandler,
792
923
  onClose: isClosable ? onBack : undefined,
793
924
  toolbar: searchResutlToolbar
794
925
  },
@@ -803,7 +934,7 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
803
934
  panelContainer: {
804
935
  title: SDKUI_Localizator.BlogCase,
805
936
  allowMaximize: !isMobile,
806
- onBack: getOnBackHandler(),
937
+ onBack: backHandlerSecondary,
807
938
  }
808
939
  },
809
940
  toolbarOptions: { icon: _jsx(IconBoard, { fontSize: 24 }), visible: getSearchToolbarVisibility(SDK_Globals.tmSession?.SessionDescr?.appModuleID ?? AppModules.SURFER).tmBlog, orderNumber: 2, isActive: allInitialPanelVisibility['tmBlog'] }
@@ -816,7 +947,7 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
816
947
  panelContainer: {
817
948
  title: SDKUI_Localizator.MetadataSystem,
818
949
  allowMaximize: !isMobile,
819
- onBack: getOnBackHandler(),
950
+ onBack: backHandlerSecondary,
820
951
  }
821
952
  },
822
953
  toolbarOptions: { icon: _jsx(IconDcmtTypeSys, { fontSize: 24 }), visible: getSearchToolbarVisibility(SDK_Globals.tmSession?.SessionDescr?.appModuleID ?? AppModules.SURFER).tmSysMetadata, orderNumber: 3, isActive: allInitialPanelVisibility['tmSysMetadata'] }
@@ -830,7 +961,7 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
830
961
  panelContainer: {
831
962
  title: SDKUI_Localizator.ResultDetails,
832
963
  allowMaximize: !isMobile,
833
- onBack: getOnBackHandler(),
964
+ onBack: backHandlerSecondary,
834
965
  }
835
966
  },
836
967
  toolbarOptions: { icon: _jsx(IconPlatform, { fontSize: 20 }), visible: getSearchToolbarVisibility(SDK_Globals.tmSession?.SessionDescr?.appModuleID ?? AppModules.SURFER).tmFullTextSearch, orderNumber: 4, isActive: allInitialPanelVisibility['tmFullTextSearch'] }
@@ -844,21 +975,49 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
844
975
  },
845
976
  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
977
  }
847
- ], [tmSearchResult, tmBlog, tmSysMetadata, tmDcmtPreview, tmFullTextSearch, showToolbarHeader, context, isMobile]);
848
- return (_jsxs(StyledMultiViewPanel, { "$isVisible": isVisible, children: [_jsx(StyledMultiViewPanel, { "$isVisible": !isOpenDcmtForm && !isOpenDetails && !isOpenMaster, style: {
978
+ ], [tmSearchResult, tmBlog, tmSysMetadata, tmDcmtPreview, tmFullTextSearch, showToolbarHeader, context, isMobile, backHandler, backHandlerSecondary, isClosable, onBack]);
979
+ /** Contenuto del panel manager: con groupId sta nel provider del contenitore, altrimenti in quello qui sotto */
980
+ 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 })] }));
981
+ return (_jsxs(StyledMultiViewPanel, { "$isVisible": isVisible, children: [_jsx(StyledMultiViewPanel, { "$isVisible": (!isOpenDcmtForm || isDcmtFormModal) && !isOpenDetails && !isOpenMaster, style: {
849
982
  display: 'flex',
850
983
  flexDirection: isMobile ? 'column' : 'row',
851
984
  justifyContent: 'space-between',
852
985
  gap: Gutters.getGutters(),
853
986
  width: '100%',
854
987
  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] }));
988
+ }, 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
989
+ // Senza le prop di persistenza il provider si comporta come quello base:
990
+ // i contesti che non ricordano il layout restano come prima
991
+ : _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
992
  };
860
993
  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 }) => {
994
+ /** Cerca una voce di operazione per id, anche dentro i sottomenu */
995
+ const findOperationItemById = (items, id) => {
996
+ for (const item of items) {
997
+ if (item.id === id)
998
+ return item;
999
+ const found = item.submenu ? findOperationItemById(item.submenu, id) : undefined;
1000
+ if (found)
1001
+ return found;
1002
+ }
1003
+ return undefined;
1004
+ };
1005
+ /**
1006
+ * Azioni della colonna di riga. Gli id sono quelli di useDocumentOperations, così il comportamento è
1007
+ * lo stesso del menu contestuale; icone e colori sono allineati alle altre griglie mentre il tooltip
1008
+ * resta il nome dell'operazione. "Cancella" è la cancellazione logica: ripristino ed eliminazione
1009
+ * fisica restano nel menu contestuale, dove la scelta è esplicita.
1010
+ */
1011
+ const rowActions = [
1012
+ { operationId: 'open-form', icon: _jsx(IconEdit, { fontSize: 18, color: TMColors.primary }) },
1013
+ { operationId: 'dup', icon: _jsx(IconDuplicate, { fontSize: 18, color: TMColors.tertiary }) },
1014
+ { operationId: 'del-log', icon: _jsx(IconDelete, { fontSize: 18, color: TMColors.error }), isVisible: (rowData) => rowData?.ISLOGDEL != 1 },
1015
+ ];
1016
+ const rowActionsStyles = {
1017
+ cell: { display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '14px' },
1018
+ icon: { cursor: 'pointer', display: 'flex' },
1019
+ };
1020
+ 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
1021
  const [dataSource, setDataSource] = useState();
863
1022
  const [columns, setColumns] = useState([]);
864
1023
  const [selectedRowKeys, setSelectedRowKeys] = useState([]);
@@ -879,8 +1038,8 @@ const TMSearchResultGrid = ({ openInOffice, fromDTD, operationItems, allUsers, i
879
1038
  useEffect(() => {
880
1039
  updateDataSourceFromDataGrid(dataSource ?? []);
881
1040
  }, [dataSource]);
882
- const { loadDataListsAsync, renderDataListCell, dataListsCache } = useDataListItem();
883
- const { loadUsersAsync, renderUserIdViewer, usersCache } = useDataUserIdItem();
1041
+ const { loadDataListsAsync, renderDataListCell, dataListsCache, getDataListItem } = useDataListItem();
1042
+ const { loadUsersAsync, renderUserIdViewer, usersCache, getUserItem, getCompleteUserName } = useDataUserIdItem();
884
1043
  useEffect(() => {
885
1044
  // Sincronizza focusedItem con inputFocusedItem dal padre
886
1045
  if (deepCompare(inputFocusedItem, focusedItem))
@@ -1112,6 +1271,57 @@ const TMSearchResultGrid = ({ openInOffice, fromDTD, operationItems, allUsers, i
1112
1271
  });
1113
1272
  return cols;
1114
1273
  }, [searchResult, dataType, cellRender, getDisplayFormat, dataListsCache, usersCache]);
1274
+ /**
1275
+ * Colonne in cui il testo mostrato è diverso dal valore archiviato: date e numeri formattati, descrizioni
1276
+ * di liste dati e utenti. Su queste la ricerca della griglia va fatta sul testo mostrato, altrimenti non
1277
+ * trova nulla (es. cercando 29/01/2023 su una data, che nei dati è una stringa ISO).
1278
+ * Le cache sono ref lette al momento della ricerca: a quel punto sono già popolate.
1279
+ */
1280
+ const searchTextGetters = useMemo(() => {
1281
+ const getters = new Map();
1282
+ const columnsOfResult = searchResult?.dtdResult?.columns;
1283
+ if (!columnsOfResult)
1284
+ return getters;
1285
+ const uniqueKeys = generateUniqueColumnKeys(columnsOfResult, searchResult?.fromTID);
1286
+ columnsOfResult.forEach((col, index) => {
1287
+ const dataField = uniqueKeys[index];
1288
+ if (!dataField)
1289
+ return;
1290
+ const dataDomain = MetadataDataDomains[(col.extendedProperties?.["DataDomain"] ?? "None")];
1291
+ const dataListID = Number(col.extendedProperties?.["DataListID"]);
1292
+ if (dataDomain === MetadataDataDomains.DataList && dataListID) {
1293
+ getters.set(dataField, (rowData) => getDataListItem(dataListID, rowData?.[dataField])?.name ?? String(rowData?.[dataField] ?? ''));
1294
+ return;
1295
+ }
1296
+ if (dataDomain === MetadataDataDomains.UserID) {
1297
+ getters.set(dataField, (rowData) => {
1298
+ const rawValue = rowData?.[dataField];
1299
+ // Un campo utente vuoto non mostra nulla: senza questo controllo Number('') darebbe 0, cioè l'utente di sistema
1300
+ if (rawValue === undefined || rawValue === null || rawValue === '')
1301
+ return '';
1302
+ const userId = Number(rawValue);
1303
+ if (Number.isNaN(userId))
1304
+ return String(rawValue);
1305
+ if (userId === 0)
1306
+ return SDKUI_Localizator.SystemUser ?? '';
1307
+ const ud = getUserItem(userId);
1308
+ return (ud ? getCompleteUserName(ud.domain, ud.name) : undefined) ?? String(rawValue);
1309
+ });
1310
+ return;
1311
+ }
1312
+ // Data e numero vengono mostrati con il formato definito sulle proprietà estese della colonna
1313
+ const { format, formatCulture } = getColumnFormatInfo(col);
1314
+ if (col.dataType === DataColumnTypes.DateTime) {
1315
+ getters.set(dataField, (rowData) => getDateDisplayText(rowData?.[dataField], format, formatCulture));
1316
+ return;
1317
+ }
1318
+ if (col.dataType === DataColumnTypes.Number) {
1319
+ getters.set(dataField, (rowData) => getNumberDisplayText(rowData?.[dataField], format, formatCulture));
1320
+ }
1321
+ });
1322
+ return getters;
1323
+ }, [searchResult, getDataListItem, getUserItem, getCompleteUserName]);
1324
+ const customizeColumns = useMemo(() => customizeSearchByDisplayText(searchTextGetters), [searchTextGetters]);
1115
1325
  /**
1116
1326
  * Effect che carica i dati necessari e genera le colonne della griglia.
1117
1327
  * Esegue il caricamento delle cache (DataList e UserID) in parallelo prima di generare le colonne,
@@ -1203,6 +1413,9 @@ const TMSearchResultGrid = ({ openInOffice, fromDTD, operationItems, allUsers, i
1203
1413
  // Era sull'icona, non fare nulla
1204
1414
  return;
1205
1415
  }
1416
+ // Due click rapidi su un'icona della colonna azioni non devono aprire anche il form
1417
+ if (e.event?.target.closest('.tm-row-actions'))
1418
+ return;
1206
1419
  // Verifica che il documento abbia TID e DID validi prima di procedere
1207
1420
  if (!e.data.TID || !e.data.DID) {
1208
1421
  ShowAlert({
@@ -1216,8 +1429,43 @@ const TMSearchResultGrid = ({ openInOffice, fromDTD, operationItems, allUsers, i
1216
1429
  onFocusedItemChanged?.(e.data);
1217
1430
  onDblClick();
1218
1431
  }, [onDblClick]);
1432
+ /**
1433
+ * Colonna delle azioni di riga, in testa alla griglia (subito dopo la checkbox di selezione, che
1434
+ * DevExtreme tiene sempre per prima). Vive solo qui e non nello stato `columns`, che è quello
1435
+ * sincronizzato col padre per l'esportazione: una colonna senza dataField finirebbe in Excel come
1436
+ * colonna vuota.
1437
+ *
1438
+ * Le operazioni sono lette da una ref perché `operationItems` è ricostruito a ogni render del
1439
+ * padre: usarlo come dipendenza renderebbe nuova la colonna, e con essa `dataColumns`, ad ogni
1440
+ * giro. La cellRender le rilegge a ogni ridisegno della griglia, quindi resta comunque aggiornata.
1441
+ */
1442
+ const operationItemsRef = useRef(operationItems);
1443
+ operationItemsRef.current = operationItems;
1444
+ const actionsColumn = useMemo(() => ({
1445
+ caption: '',
1446
+ width: 110,
1447
+ alignment: 'center',
1448
+ allowSorting: false,
1449
+ allowFiltering: false,
1450
+ allowHeaderFiltering: false,
1451
+ allowResizing: false,
1452
+ allowReordering: false,
1453
+ allowHiding: false,
1454
+ allowExporting: false,
1455
+ showInColumnChooser: false,
1456
+ cssClass: 'tm-row-actions',
1457
+ cellRender: (cellData) => (_jsx("div", { style: rowActionsStyles.cell, children: rowActions.map(action => {
1458
+ const operation = findOperationItemById(operationItemsRef.current, action.operationId);
1459
+ if (!operation || operation.visible === false)
1460
+ return null;
1461
+ if (action.isVisible && !action.isVisible(cellData.data))
1462
+ return null;
1463
+ return (_jsx("span", { style: rowActionsStyles.icon, title: operation.name, onClick: () => onRowActionRequest?.(action.operationId, cellData.data), children: action.icon }, action.operationId));
1464
+ }) })),
1465
+ }), [onRowActionRequest]);
1219
1466
  const dataColumns = useMemo(() => {
1220
1467
  return [
1468
+ ...(showActionsColumn ? [actionsColumn] : []),
1221
1469
  {
1222
1470
  dataType: "object",
1223
1471
  dataField: 'FILEEXT',
@@ -1231,7 +1479,7 @@ const TMSearchResultGrid = ({ openInOffice, fromDTD, operationItems, allUsers, i
1231
1479
  },
1232
1480
  ...columns.filter(col => col.dataField !== 'FILEEXT')
1233
1481
  ];
1234
- }, [columns, onDownloadDcmtsAsync, openInOffice]);
1482
+ }, [columns, onDownloadDcmtsAsync, openInOffice, showActionsColumn, actionsColumn]);
1235
1483
  const onContentReady = useCallback((e) => {
1236
1484
  if (e === undefined)
1237
1485
  return;
@@ -1242,26 +1490,29 @@ const TMSearchResultGrid = ({ openInOffice, fromDTD, operationItems, allUsers, i
1242
1490
  setVisibleItems(visibleData);
1243
1491
  }, []);
1244
1492
  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
- } })] });
1493
+ const gridCounterConfig = useMemo(() => {
1494
+ if (!showCounterConfig)
1495
+ return { show: false };
1496
+ const defaultItems = dcmtsReturned !== undefined && dcmtsFound !== undefined
1497
+ ? new Map([
1498
+ [CounterItemKey.allItems, {
1499
+ key: CounterItemKey.allItems,
1500
+ show: true,
1501
+ caption: `${dcmtsReturned} restituiti su ${dcmtsFound} trovati`,
1502
+ value: `${dcmtsReturned}/${dcmtsFound}`,
1503
+ icon: _jsx(IconAll, {}),
1504
+ order: -1
1505
+ }]
1506
+ ])
1507
+ : undefined;
1508
+ return { ...counterConfig, show: true, items: counterConfig?.items ?? defaultItems };
1509
+ }, [showCounterConfig, counterConfig, dcmtsReturned, dcmtsFound]);
1510
+ 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
1511
  };
1261
1512
  //#region TMSearchResultSelector
1262
- const StyledItemTemplate = styled.div `
1263
- background: ${(props) => props.$isSelected ? 'oklch(from var(--dx-color-primary) l c h / .2) !important' : 'transparent'};
1264
- cursor: pointer;
1513
+ const StyledItemTemplate = styled.div `
1514
+ background: ${(props) => props.$isSelected ? 'oklch(from var(--dx-color-primary) l c h / .2) !important' : 'transparent'};
1515
+ cursor: pointer;
1265
1516
  `;
1266
1517
  const MemoizedStyledItemTemplate = React.memo(StyledItemTemplate);
1267
1518
  const TMSearchResultSelector = ({ searchResults = [], disableAccordionIfSingleCategory = false, selectedTID, selectedSearchResult, autoSelectFirst = true, onSelectionChanged }) => {
@@ -1317,14 +1568,29 @@ const TMSearchResultSelector = ({ searchResults = [], disableAccordionIfSingleCa
1317
1568
  }
1318
1569
  }
1319
1570
  }, [sortedCategories.length, autoSelectFirst]);
1571
+ // La preselezione esterna va applicata una volta per valore: riapplicarla a ogni cambio di
1572
+ // searchResults faceva sì che un refresh annullasse la navigazione dell'utente.
1573
+ const appliedSelectedTIDRef = useRef(undefined);
1320
1574
  useEffect(() => {
1321
- if (selectedTID !== undefined) {
1322
- const found = searchResults.find(r => Number(r.fromTID) === selectedTID);
1323
- handleSelect(found);
1575
+ // Richiesta ritirata: dimentichiamo il valore applicato, così se il chiamante richiede di
1576
+ // nuovo lo stesso TID la preselezione riparte
1577
+ if (selectedTID === undefined) {
1578
+ appliedSelectedTIDRef.current = undefined;
1579
+ return;
1324
1580
  }
1581
+ if (appliedSelectedTIDRef.current === selectedTID)
1582
+ return;
1583
+ const found = searchResults.find(r => Number(r.fromTID) === selectedTID);
1584
+ if (!found)
1585
+ return; // risultati non ancora arrivati: riproviamo al prossimo giro
1586
+ appliedSelectedTIDRef.current = selectedTID;
1587
+ handleSelect(found);
1325
1588
  }, [selectedTID, searchResults]);
1326
- const handleSelect = useCallback((result) => {
1327
- if (selectedResult !== result && result !== undefined) {
1589
+ // force = true per i click dell'utente: la selezione va notificata anche se il tipo documento è già quello selezionato
1590
+ const handleSelect = useCallback((result, force = false) => {
1591
+ if (result === undefined)
1592
+ return;
1593
+ if (selectedResult !== result || force) {
1328
1594
  setSelectedResult(result);
1329
1595
  onSelectionChanged?.(result);
1330
1596
  }
@@ -1369,113 +1635,169 @@ const TMSearchResultSelector = ({ searchResults = [], disableAccordionIfSingleCa
1369
1635
  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
1636
  itemHeight: 40
1371
1637
  })), [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))) }));
1638
+ 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
1639
  if (disableAccordionIfSingleCategory && sortedCategories.length === 1) {
1374
1640
  const category = sortedCategories[0];
1375
1641
  return renderCategoryItems(category);
1376
1642
  }
1377
- return (_jsx(TMAccordionNew, { groups: accordionGroups, selectedItem: selectedResult, onSelectedItemChange: (result) => handleSelect(result) }));
1643
+ return (_jsx(TMAccordionNew, { groups: accordionGroups, selectedItem: selectedResult, onSelectedItemChange: (result) => handleSelect(result, true) }));
1378
1644
  };
1379
1645
  //#endregion TMSearchResultSelector
1380
- const PanelDisabledStateHandler = ({ isBoardDisabled }) => {
1381
- const { setPanelVisibilityById, setToolbarButtonDisabled } = useTMPanelManagerContext();
1646
+ /** Senza UI: vive dentro il provider e passa a onBack, via ref, l'azione "mostra il pannello lista". */
1647
+ const ShowMainPanelBridge = ({ showMainPanelRef }) => {
1648
+ const { setPanelVisibilityById } = useTMPanelManagerContext();
1382
1649
  useEffect(() => {
1383
- if (isBoardDisabled) {
1384
- setToolbarButtonDisabled('tmBlog', true);
1385
- setPanelVisibilityById('tmBlog', false);
1386
- }
1387
- else {
1388
- setToolbarButtonDisabled('tmBlog', false);
1389
- }
1650
+ // Su mobile mostrare un pannello nasconde gli altri; su desktop la lista è già visibile
1651
+ showMainPanelRef.current = () => setPanelVisibilityById('tmSearchResult', true);
1652
+ return () => { showMainPanelRef.current = null; };
1653
+ }, [setPanelVisibilityById, showMainPanelRef]);
1654
+ return null;
1655
+ };
1656
+ /** Senza UI: allinea i pannelli quando il layout viene salvato da un altro risultato di ricerca già aperto */
1657
+ const SavedLayoutSyncHandler = ({ disabledPanelIds }) => {
1658
+ const { panelVisibility, panelDimensions, maximizedPanels, isResizingActive, setPanelVisibilityById, setPanelDimensionsById } = useTMPanelManagerContext();
1659
+ // Il layout è condiviso fra i risultati aperti: si sottoscrive la firma, che è una stringa, perché
1660
+ // React confronta le letture per riferimento e su un oggetto ri-renderizzerebbe all'infinito
1661
+ const savedLayoutSignature = useSyncExternalStore(subscribeSearchResultPanelLayout, getSearchResultPanelLayoutSignature);
1662
+ useEffect(() => {
1663
+ // Con un pannello massimizzato o un separatore in movimento comanda l'utente di questa istanza
1664
+ if (maximizedPanels.length > 0 || isResizingActive)
1665
+ return;
1666
+ // Al mount panelVisibility è ancora vuota: lo stato iniziale lo applica il provider.
1667
+ // Un pannello disabilitato qui non va aperto, gli altri si allineano comunque
1668
+ const panelsToApply = Object.entries(getSearchResultPanelLayout())
1669
+ .filter(([id, state]) => panelVisibility[id] !== undefined && !(state?.visible && disabledPanelIds.includes(id)));
1670
+ panelsToApply.forEach(([id, state]) => {
1671
+ const isVisible = state?.visible ?? false;
1672
+ if (panelVisibility[id] !== isVisible)
1673
+ setPanelVisibilityById(id, isVisible);
1674
+ });
1675
+ // Le larghezze salvate valgono come pesi, non come percentuali: qui si normalizzano sui soli pannelli
1676
+ // che questo risultato apre davvero, così lo spazio di quelli scartati non resta vuoto
1677
+ const panelsToShow = panelsToApply.filter(([, state]) => state?.visible && parseFloat(state.width) > 0 && parseFloat(state.height) > 0);
1678
+ const weightTotal = panelsToShow.reduce((total, [, state]) => total + parseFloat(state.width), 0);
1679
+ if (weightTotal <= 0)
1680
+ return;
1681
+ // Dopo la visibilità, che accoda un suo ricalcolo delle quote: così restano quelle salvate.
1682
+ // Le quote sono arrotondate, altrimenti due risultati si rincorrono sugli ultimi decimali
1683
+ panelsToShow.forEach(([id, state]) => {
1684
+ const width = `${Math.round(parseFloat(state.width) / weightTotal * 10000) / 100}%`;
1685
+ if (panelDimensions[id]?.width === width && panelDimensions[id]?.height === state.height)
1686
+ return;
1687
+ setPanelDimensionsById(id, width, state.height);
1688
+ });
1689
+ }, [savedLayoutSignature]);
1690
+ return null;
1691
+ };
1692
+ const PanelDisabledStateHandler = ({ isBoardDisabled, savedBlogVisible = false }) => {
1693
+ const { panelVisibility, setPanelVisibilityById, setToolbarButtonDisabled } = useTMPanelManagerContext();
1694
+ const isBlogVisible = panelVisibility['tmBlog'] ?? false;
1695
+ useEffect(() => {
1696
+ setToolbarButtonDisabled('tmBlog', isBoardDisabled);
1390
1697
  }, [isBoardDisabled]);
1698
+ /**
1699
+ * Senza bacheca nel tipo documento il pannello va chiuso ogni volta che ricompare, non solo al cambio
1700
+ * di stato: il layout salvato viene applicato dopo questo effetto e può riportarlo a video.
1701
+ * È un effetto di layout per chiuderlo prima che venga disegnato, altrimenti lampeggia.
1702
+ */
1703
+ useLayoutEffect(() => {
1704
+ if (isBoardDisabled && isBlogVisible)
1705
+ setPanelVisibilityById('tmBlog', false);
1706
+ }, [isBoardDisabled, isBlogVisible]);
1707
+ // Bacheca di nuovo prevista: si riapre come da layout salvato. isBlogVisible non è fra le dipendenze,
1708
+ // altrimenti riaprirebbe il pannello appena chiuso dall'utente
1709
+ useEffect(() => {
1710
+ if (!isBoardDisabled && savedBlogVisible && !isBlogVisible)
1711
+ setPanelVisibilityById('tmBlog', true);
1712
+ }, [isBoardDisabled, savedBlogVisible]);
1391
1713
  return null;
1392
1714
  };
1393
- const TMDcmtPreviewWrapper = ({ refreshPreviewTrigger, currentDcmt, isVisible, onBack }) => {
1715
+ const TMDcmtPreviewWrapper = ({ refreshPreviewTrigger, currentDcmt, isVisible, isPageVisible = true, onBack }) => {
1394
1716
  const { setPanelVisibilityById, toggleMaximize, isResizingActive, countVisibleLeafPanels } = useTMPanelManagerContext();
1395
1717
  const deviceType = useDeviceType();
1396
1718
  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));
1719
+ 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
1720
  };
1399
1721
  // Styled Components
1400
- const StyledPlaceholder = styled.div `
1401
- padding: 20px;
1402
- text-align: center;
1403
- color: #888;
1722
+ const StyledPlaceholder = styled.div `
1723
+ padding: 20px;
1724
+ text-align: center;
1725
+ color: #888;
1404
1726
  `;
1405
- const StyledIndexingInfoSection = styled.div `
1406
- padding: 15px;
1407
- border-top: 1px solid #e0e0e0;
1408
- background: linear-gradient(to bottom, #f9f9f9, #f5f5f5);
1409
- display: flex;
1410
- flex-direction: column;
1411
- gap: 10px;
1727
+ const StyledIndexingInfoSection = styled.div `
1728
+ padding: 15px;
1729
+ border-top: 1px solid #e0e0e0;
1730
+ background: linear-gradient(to bottom, #f9f9f9, #f5f5f5);
1731
+ display: flex;
1732
+ flex-direction: column;
1733
+ gap: 10px;
1412
1734
  `;
1413
- const StyledIndexingToggle = styled.button `
1414
- display: flex;
1415
- align-items: center;
1416
- justify-content: space-between;
1417
- width: 100%;
1418
- padding: 10px 16px;
1419
- background: white;
1420
- border: 1px solid #d0d0d0;
1421
- border-radius: 6px;
1422
- cursor: ${props => props.disabled ? 'not-allowed' : 'pointer'};
1423
- transition: all 0.2s ease;
1424
- font-size: 14px;
1425
- font-weight: 500;
1426
- color: #333;
1427
- opacity: ${props => props.disabled ? 0.6 : 1};
1428
- box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
1429
-
1430
- &:hover:not(:disabled) {
1431
- background: #f8f8f8;
1432
- border-color: #2196F3;
1433
- box-shadow: 0 2px 4px rgba(33, 150, 243, 0.2);
1434
- }
1435
-
1436
- &:active:not(:disabled) {
1437
- transform: translateY(1px);
1438
- box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
1439
- }
1735
+ const StyledIndexingToggle = styled.button `
1736
+ display: flex;
1737
+ align-items: center;
1738
+ justify-content: space-between;
1739
+ width: 100%;
1740
+ padding: 10px 16px;
1741
+ background: white;
1742
+ border: 1px solid #d0d0d0;
1743
+ border-radius: 6px;
1744
+ cursor: ${props => props.disabled ? 'not-allowed' : 'pointer'};
1745
+ transition: all 0.2s ease;
1746
+ font-size: 14px;
1747
+ font-weight: 500;
1748
+ color: #333;
1749
+ opacity: ${props => props.disabled ? 0.6 : 1};
1750
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
1751
+
1752
+ &:hover:not(:disabled) {
1753
+ background: #f8f8f8;
1754
+ border-color: #2196F3;
1755
+ box-shadow: 0 2px 4px rgba(33, 150, 243, 0.2);
1756
+ }
1757
+
1758
+ &:active:not(:disabled) {
1759
+ transform: translateY(1px);
1760
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
1761
+ }
1440
1762
  `;
1441
- const StyledLeftContent = styled.div `
1442
- display: flex;
1443
- align-items: center;
1444
- gap: 8px;
1763
+ const StyledLeftContent = styled.div `
1764
+ display: flex;
1765
+ align-items: center;
1766
+ gap: 8px;
1445
1767
  `;
1446
- const StyledRightContent = styled.div `
1447
- display: flex;
1448
- align-items: center;
1449
- gap: 8px;
1768
+ const StyledRightContent = styled.div `
1769
+ display: flex;
1770
+ align-items: center;
1771
+ gap: 8px;
1450
1772
  `;
1451
- const StyledChevron = styled.span `
1452
- transition: transform 0.2s ease;
1453
- transform: ${props => props.$isOpen ? 'rotate(180deg)' : 'rotate(0deg)'};
1454
- color: #666;
1455
- font-size: 12px;
1773
+ const StyledChevron = styled.span `
1774
+ transition: transform 0.2s ease;
1775
+ transform: ${props => props.$isOpen ? 'rotate(180deg)' : 'rotate(0deg)'};
1776
+ color: #666;
1777
+ font-size: 12px;
1456
1778
  `;
1457
- const StyledIndexingInfoBox = styled.div `
1458
- position: relative;
1459
- background: white;
1460
- border: 1px solid #e0e0e0;
1461
- border-radius: 6px;
1462
- padding: 12px;
1463
- max-height: 200px;
1464
- overflow: auto;
1465
- font-family: 'Courier New', monospace;
1466
- font-size: 12px;
1467
- color: #333;
1468
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
1469
- animation: slideDown 0.2s ease;
1470
-
1471
- @keyframes slideDown {
1472
- from {
1473
- opacity: 0;
1474
- transform: translateY(-10px);
1475
- }
1476
- to {
1477
- opacity: 1;
1478
- transform: translateY(0);
1479
- }
1480
- }
1779
+ const StyledIndexingInfoBox = styled.div `
1780
+ position: relative;
1781
+ background: white;
1782
+ border: 1px solid #e0e0e0;
1783
+ border-radius: 6px;
1784
+ padding: 12px;
1785
+ max-height: 200px;
1786
+ overflow: auto;
1787
+ font-family: 'Courier New', monospace;
1788
+ font-size: 12px;
1789
+ color: #333;
1790
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
1791
+ animation: slideDown 0.2s ease;
1792
+
1793
+ @keyframes slideDown {
1794
+ from {
1795
+ opacity: 0;
1796
+ transform: translateY(-10px);
1797
+ }
1798
+ to {
1799
+ opacity: 1;
1800
+ transform: translateY(0);
1801
+ }
1802
+ }
1481
1803
  `;