@topconsultnpm/sdkui-react 6.22.0-dev2.19 → 6.22.0-dev2.20

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 (41) 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/TMDataGrid.d.ts +2 -0
  4. package/lib/components/base/TMDataGrid.js +68 -4
  5. package/lib/components/base/TMModal.d.ts +4 -0
  6. package/lib/components/base/TMModal.js +42 -11
  7. package/lib/components/features/documents/TMDcmtForm.d.ts +7 -0
  8. package/lib/components/features/documents/TMDcmtForm.js +94 -40
  9. package/lib/components/features/documents/TMMasterDetailDcmts.js +88 -30
  10. package/lib/components/features/documents/TMMasterInfoFields.d.ts +19 -0
  11. package/lib/components/features/documents/TMMasterInfoFields.js +172 -0
  12. package/lib/components/features/documents/TMMultiMasterDetailDcmtsForm.d.ts +40 -0
  13. package/lib/components/features/documents/TMMultiMasterDetailDcmtsForm.js +256 -0
  14. package/lib/components/features/documents/TMMultiMasterDetailDcmtsUtils.d.ts +352 -0
  15. package/lib/components/features/documents/TMMultiMasterDetailDcmtsUtils.js +221 -0
  16. package/lib/components/features/documents/TMRelationViewer.d.ts +14 -1
  17. package/lib/components/features/documents/TMRelationViewer.js +69 -20
  18. package/lib/components/features/search/TMSearchResult.d.ts +8 -0
  19. package/lib/components/features/search/TMSearchResult.js +131 -23
  20. package/lib/components/layout/panelManager/TMPanelManagerContainer.d.ts +1 -0
  21. package/lib/components/layout/panelManager/TMPanelManagerContainer.js +24 -14
  22. package/lib/components/layout/panelManager/TMPanelWrapper.js +2 -2
  23. package/lib/components/layout/panelManager/types.d.ts +3 -0
  24. package/lib/helper/SDKUI_Globals.d.ts +34 -0
  25. package/lib/helper/SDKUI_Globals.js +37 -0
  26. package/lib/helper/SDKUI_Localizator.d.ts +18 -0
  27. package/lib/helper/SDKUI_Localizator.js +180 -0
  28. package/lib/helper/TMIcons.d.ts +1 -0
  29. package/lib/helper/TMIcons.js +3 -0
  30. package/lib/helper/dcmtsHelper.d.ts +20 -0
  31. package/lib/helper/dcmtsHelper.js +58 -1
  32. package/lib/helper/helpers.d.ts +9 -0
  33. package/lib/helper/helpers.js +8 -0
  34. package/lib/hooks/useArchiveListForm.d.ts +39 -0
  35. package/lib/hooks/useArchiveListForm.js +46 -0
  36. package/lib/hooks/useDocumentOperations.d.ts +1 -0
  37. package/lib/hooks/useDocumentOperations.js +16 -4
  38. package/lib/hooks/useMultiMasterDetailDcmts.d.ts +97 -0
  39. package/lib/hooks/useMultiMasterDetailDcmts.js +555 -0
  40. package/lib/hooks/useRelatedDocuments.js +2 -26
  41. package/package.json +1 -1
@@ -32,6 +32,11 @@ const TMMasterDetailDcmts = ({ allTasks = [], getAllTasks, deleteTaskByIdsCallba
32
32
  const [contextMenuPosition, setContextMenuPosition] = useState({ x: 0, y: 0 });
33
33
  // Track if TMRelationViewer is loading (used to disable context menu during loading)
34
34
  const [isRelationViewerLoading, setIsRelationViewerLoading] = useState(false);
35
+ // Funzione fornita da TMRelationViewer per interrompere il calcolo delle correlazioni
36
+ // (nessuna ulteriore chiamata GetMetadata). È sincrona: invocabile anche subito prima di uscire.
37
+ const stopRelationsCalculationRef = useRef(undefined);
38
+ // True quando si sta uscendo dalla maschera: evita l'alert sui risultati parziali
39
+ const isLeavingRef = useRef(false);
35
40
  // Track if loading has ever been TRUE (to distinguish initial false from post-load false)
36
41
  const hasLoadingBeenTrueRef = useRef(false);
37
42
  // Track the previous loading state to detect transitions
@@ -63,6 +68,39 @@ const TMMasterDetailDcmts = ({ allTasks = [], getAllTasks, deleteTaskByIdsCallba
63
68
  const onRefreshOperationsDatagrid = useCallback(async () => {
64
69
  setRefreshOperationsTrigger(prev => prev + 1);
65
70
  }, []);
71
+ // Riceve da TMRelationViewer la funzione per interrompere il calcolo
72
+ const handleStopCalculationReady = useCallback((stopCalculation) => {
73
+ stopRelationsCalculationRef.current = stopCalculation;
74
+ }, []);
75
+ // Interrompe il calcolo delle correlazioni: mostra subito quelle già calcolate
76
+ // senza attendere le restanti chiamate GetMetadata
77
+ const handleStopRelationsCalculation = useCallback(() => {
78
+ stopRelationsCalculationRef.current?.();
79
+ setIsCheckingFirstLoad(false); // Nasconde l'overlay e mostra l'albero (parziale)
80
+ }, []);
81
+ // Esce dalla maschera interrompendo anche le chiamate GetMetadata ancora in coda:
82
+ // l'interruzione è invocata in modo sincrono perché onBack può smontare il componente
83
+ // (un effect non farebbe in tempo a propagare l'interruzione)
84
+ const handleBack = useCallback(() => {
85
+ isLeavingRef.current = true;
86
+ stopRelationsCalculationRef.current?.();
87
+ onBack?.();
88
+ }, [onBack]);
89
+ const handleCancelFirstLoad = useCallback(() => {
90
+ setIsCheckingFirstLoad(false);
91
+ handleBack();
92
+ }, [handleBack]);
93
+ // Notifica all'utente che i dati mostrati sono parziali (non quando si sta uscendo)
94
+ const handleRelationsCalculationStopped = useCallback(() => {
95
+ if (isLeavingRef.current)
96
+ return;
97
+ ShowAlert({
98
+ message: SDKUI_Localizator.RelationsCalculationStopped,
99
+ title: SDKUI_Localizator.Attention,
100
+ duration: 4000,
101
+ mode: 'info',
102
+ });
103
+ }, []);
66
104
  // Refresh ALL panels (tree view + search results) with fade-out -> update -> fade-in transition
67
105
  const onRefreshAllPanels = async () => {
68
106
  await dcmtUtility?.onRefreshPreviewForm?.(); // Refresh preview form data
@@ -238,6 +276,12 @@ const TMMasterDetailDcmts = ({ allTasks = [], getAllTasks, deleteTaskByIdsCallba
238
276
  },
239
277
  });
240
278
  const { dcmtOperations: { abortController, showWaitPanel, showPrimary, waitPanelTitle, waitPanelTextPrimary, waitPanelValuePrimary, waitPanelMaxValuePrimary, showSecondary, waitPanelTextSecondary, waitPanelValueSecondary, waitPanelMaxValueSecondary, }, } = features;
279
+ // Nuovi documenti in input: il componente non sta più uscendo dalla maschera
280
+ // (chiave stabile: l'array inputDcmts può essere ricreato a ogni render)
281
+ const inputDcmtsKey = useMemo(() => (inputDcmts ?? []).map(d => `${d.TID}-${d.DID}`).join('|'), [inputDcmts]);
282
+ useEffect(() => {
283
+ isLeavingRef.current = false;
284
+ }, [inputDcmtsKey]);
241
285
  // Load dtdMaster when inputDcmts changes
242
286
  useEffect(() => {
243
287
  const loadDtdMaster = async () => {
@@ -335,11 +379,11 @@ const TMMasterDetailDcmts = ({ allTasks = [], getAllTasks, deleteTaskByIdsCallba
335
379
  setContextMenuVisible(true);
336
380
  }, children: [_jsx(TMRelationViewerWrapper, { refreshKey: refreshKey, inputDcmts: inputDcmts, isForMaster: isForMaster, showCurrentDcmtIndicator: showCurrentDcmtIndicator, showZeroDcmts: showZeroDcmts,
337
381
  // customItemRender={customItemRender}
338
- allowMultipleSelection: allowMultipleSelection, focusedItem: focusedItem, selectedItems: selectedItems, onFocusedItemChanged: handleFocusedItemChanged, onSelectedItemsChanged: handleSelectedItemsChanged, onNoRelationsFound: handleNoRelationsFound, onItemContextMenu: isRelationViewerLoading ? undefined : onItemContextMenu, focusedItemFormData: focusedItemFormData, onLoadingStateChanged: setIsRelationViewerLoading }), _jsx(TMContextMenu, { items: operationItems, externalControl: {
382
+ allowMultipleSelection: allowMultipleSelection, focusedItem: focusedItem, selectedItems: selectedItems, onFocusedItemChanged: handleFocusedItemChanged, onSelectedItemsChanged: handleSelectedItemsChanged, onNoRelationsFound: handleNoRelationsFound, onItemContextMenu: isRelationViewerLoading ? undefined : onItemContextMenu, focusedItemFormData: focusedItemFormData, onLoadingStateChanged: setIsRelationViewerLoading, onStopCalculationReady: handleStopCalculationReady, onCalculationStopped: handleRelationsCalculationStopped }), _jsx(TMContextMenu, { items: operationItems, externalControl: {
339
383
  visible: contextMenuVisible,
340
384
  position: contextMenuPosition,
341
385
  onClose: () => setContextMenuVisible(false)
342
- } })] }) }), [inputDcmts, isForMaster, showCurrentDcmtIndicator, showZeroDcmts, allowMultipleSelection, focusedItem, selectedItems, handleFocusedItemChanged, handleSelectedItemsChanged, handleNoRelationsFound, onItemContextMenu, contextMenuVisible, contextMenuPosition, refreshKey, focusedItemFormData, isRelationViewerLoading]);
386
+ } })] }) }), [inputDcmts, isForMaster, showCurrentDcmtIndicator, showZeroDcmts, allowMultipleSelection, focusedItem, selectedItems, handleFocusedItemChanged, handleSelectedItemsChanged, handleNoRelationsFound, onItemContextMenu, contextMenuVisible, contextMenuPosition, refreshKey, focusedItemFormData, isRelationViewerLoading, handleStopCalculationReady, handleRelationsCalculationStopped]);
343
387
  const tmFormOrResult = useMemo(() => _jsx(TMFormOrResultWrapper, { refreshKey: refreshKeyFormOrResult, deviceType: deviceType, focusedItem: focusedItem, onTaskCreateRequest: onTaskCreateRequest, allTasks: allTasks, getAllTasks: getAllTasks, deleteTaskByIdsCallback: deleteTaskByIdsCallback, addTaskCallback: addTaskCallback, editTaskCallback: editTaskCallback, handleNavigateToWGs: handleNavigateToWGs, handleNavigateToDossiers: handleNavigateToDossiers, handleNavigateToReference: handleNavigateToReference, onRefreshAfterAddDcmtToFavs: onRefreshAfterAddDcmtToFavs, editPdfForm: editPdfForm, openS4TViewer: openS4TViewer, onOpenS4TViewerRequest: onOpenS4TViewerRequest, onOpenPdfEditorRequest: onOpenPdfEditorRequest, onRefreshSearchResults: onRefreshAllPanels }), [focusedItem, deviceType, allTasks, handleNavigateToWGs, handleNavigateToDossiers, handleNavigateToReference, editPdfForm, openS4TViewer, onOpenS4TViewerRequest, onOpenPdfEditorRequest, onRefreshAfterAddDcmtToFavs, refreshKeyFormOrResult]);
344
388
  const initialPanelDimensions = {
345
389
  'tmTreeView': { width: '50%', height: '100%' },
@@ -370,7 +414,9 @@ const TMMasterDetailDcmts = ({ allTasks = [], getAllTasks, deleteTaskByIdsCallba
370
414
  title: getTitle(),
371
415
  toolbar: toolbar,
372
416
  allowMaximize: !isMobile,
373
- onBack: onBack
417
+ // handleBack interrompe il calcolo prima di uscire.
418
+ // Resta undefined se il padre non ha passato onBack (nessun pulsante indietro)
419
+ onBack: onBack ? handleBack : undefined
374
420
  }
375
421
  },
376
422
  toolbarOptions: {
@@ -417,10 +463,7 @@ const TMMasterDetailDcmts = ({ allTasks = [], getAllTasks, deleteTaskByIdsCallba
417
463
  toolbarOptions: { icon: _jsx(IconSearchCheck, { fontSize: 24 }), visible: false, orderNumber: 2, isActive: allInitialPanelVisibility['tmFormOrResult'] }
418
464
  }
419
465
  ], [tmTreeView, tmFormOrResult, focusedItem?.isDcmt, dtdMaster]);
420
- return (_jsxs("div", { style: { width: '100%', height: '100%', position: 'relative' }, children: [isCheckingFirstLoad && (_jsx(TMLoadingOverlay, { onCancel: () => {
421
- setIsCheckingFirstLoad(false);
422
- onBack?.();
423
- } })), _jsxs("div", { style: isCheckingFirstLoad ? { position: 'absolute', width: 0, height: 0, overflow: 'hidden', opacity: 0, pointerEvents: 'none' } : { width: '100%', height: '100%' }, 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(TMPanelManagerProvider, { panels: initialPanels, initialVisibility: allInitialPanelVisibility, defaultDimensions: initialPanelDimensions, initialDimensions: initialPanelDimensions, initialMobilePanelId: 'tmTreeView', children: _jsx(TMPanelManagerContainer, { panels: initialPanels, direction: "horizontal", showToolbar: true }) }) }), renderDcmtOperations, renderFloatingBar] })] }));
466
+ return (_jsxs("div", { style: { width: '100%', height: '100%', position: 'relative' }, children: [isCheckingFirstLoad && (_jsx(TMLoadingOverlay, { onCancel: handleCancelFirstLoad, onStop: handleStopRelationsCalculation })), _jsxs("div", { style: isCheckingFirstLoad ? { position: 'absolute', width: 0, height: 0, overflow: 'hidden', opacity: 0, pointerEvents: 'none' } : { width: '100%', height: '100%' }, 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(TMPanelManagerProvider, { panels: initialPanels, initialVisibility: allInitialPanelVisibility, defaultDimensions: initialPanelDimensions, initialDimensions: initialPanelDimensions, initialMobilePanelId: 'tmTreeView', children: _jsx(TMPanelManagerContainer, { panels: initialPanels, direction: "horizontal", showToolbar: true }) }) }), renderDcmtOperations, renderFloatingBar] })] }));
424
467
  };
425
468
  export default TMMasterDetailDcmts;
426
469
  /**
@@ -429,7 +472,7 @@ export default TMMasterDetailDcmts;
429
472
  * - Panel visibility toggling
430
473
  * - Focus delay handling
431
474
  */
432
- const TMRelationViewerWrapper = ({ refreshKey, inputDcmts, isForMaster, showCurrentDcmtIndicator, showZeroDcmts, customItemRender, allowMultipleSelection, focusedItem, selectedItems, onFocusedItemChanged, onSelectedItemsChanged, onNoRelationsFound, onItemContextMenu, focusedItemFormData, onLoadingStateChanged }) => {
475
+ const TMRelationViewerWrapper = ({ refreshKey, inputDcmts, isForMaster, showCurrentDcmtIndicator, showZeroDcmts, customItemRender, allowMultipleSelection, focusedItem, selectedItems, onFocusedItemChanged, onSelectedItemsChanged, onNoRelationsFound, onItemContextMenu, focusedItemFormData, onLoadingStateChanged, onStopCalculationReady, onCalculationStopped }) => {
433
476
  const { setPanelVisibilityById, setToolbarButtonVisibility } = useTMPanelManagerContext();
434
477
  // Monitor device type changes to restore panel visibility when switching from mobile to desktop
435
478
  const deviceType = useDeviceType();
@@ -489,7 +532,7 @@ const TMRelationViewerWrapper = ({ refreshKey, inputDcmts, isForMaster, showCurr
489
532
  onItemContextMenu?.(item, e);
490
533
  }, 100);
491
534
  }, [onItemContextMenu, handleFocusedItemChanged]);
492
- return (_jsx(TMRelationViewer, { inputDcmts: inputDcmts, isForMaster: isForMaster, showCurrentDcmtIndicator: showCurrentDcmtIndicator, initialShowZeroDcmts: showZeroDcmts, customItemRender: customItemRender, allowMultipleSelection: allowMultipleSelection, focusedItem: focusedItem, selectedItems: selectedItems, onFocusedItemChanged: handleFocusedItemChanged, onSelectedItemsChanged: onSelectedItemsChanged, maxDepthLevel: 1, invertMasterNavigation: false, showExpandAllButton: true, onNoRelationsFound: onNoRelationsFound, onItemContextMenu: onContextMenu, focusedItemFormData: focusedItemFormData, onLoadingStateChanged: onLoadingStateChanged }, refreshKey));
535
+ return (_jsx(TMRelationViewer, { inputDcmts: inputDcmts, isForMaster: isForMaster, showCurrentDcmtIndicator: showCurrentDcmtIndicator, initialShowZeroDcmts: showZeroDcmts, customItemRender: customItemRender, allowMultipleSelection: allowMultipleSelection, focusedItem: focusedItem, selectedItems: selectedItems, onFocusedItemChanged: handleFocusedItemChanged, onSelectedItemsChanged: onSelectedItemsChanged, maxDepthLevel: 1, invertMasterNavigation: false, showExpandAllButton: true, onNoRelationsFound: onNoRelationsFound, onItemContextMenu: onContextMenu, focusedItemFormData: focusedItemFormData, onLoadingStateChanged: onLoadingStateChanged, onStopCalculationReady: onStopCalculationReady, onCalculationStopped: onCalculationStopped }, refreshKey));
493
536
  };
494
537
  const TMFormOrResultWrapper = ({ refreshKey, deviceType, focusedItem, onTaskCreateRequest, allTasks = [], getAllTasks, deleteTaskByIdsCallback, addTaskCallback, editTaskCallback, handleNavigateToWGs, handleNavigateToDossiers, onRefreshAfterAddDcmtToFavs, editPdfForm, openS4TViewer, onOpenS4TViewerRequest, onOpenPdfEditorRequest, onRefreshSearchAsyncDatagrid, onRefreshSearchResults, handleNavigateToReference, fetchRemoteCertificates }) => {
495
538
  const { setPanelVisibilityById } = useTMPanelManagerContext();
@@ -499,7 +542,7 @@ const TMFormOrResultWrapper = ({ refreshKey, deviceType, focusedItem, onTaskCrea
499
542
  }, fetchRemoteCertificates: fetchRemoteCertificates }, refreshKey) :
500
543
  focusedItem?.searchResult === undefined ? (_jsx(TMPanel, { title: SDKUI_Localizator.SearchResult, children: _jsx(TMToppyMessage, { message: SDKUI_Localizator.SelectDocumentToViewSearchResults }) })) : (_jsx(TMSearchResult, { groupId: 'tmFormOrResult', isClosable: deviceType !== DeviceType.MOBILE, context: SearchResultContext.METADATA_SEARCH, allowFloatingBar: false, allowRelations: false, openDcmtFormAsModal: true, searchResults: focusedItem?.searchResult ?? [], showSearchResultSidebar: false, showDcmtFormSidebar: false, autoFocusFirstRow: false, onTaskCreateRequest: onTaskCreateRequest, onClose: () => { setPanelVisibilityById('tmTreeView', true); }, allTasks: allTasks, getAllTasks: getAllTasks, deleteTaskByIdsCallback: deleteTaskByIdsCallback, addTaskCallback: addTaskCallback, editTaskCallback: editTaskCallback, handleNavigateToWGs: handleNavigateToWGs, handleNavigateToDossiers: handleNavigateToDossiers, editPdfForm: editPdfForm, onOpenPdfEditorRequest: onOpenPdfEditorRequest, openS4TViewer: openS4TViewer, onOpenS4TViewerRequest: onOpenS4TViewerRequest, enablePinIcons: false, onRefreshAfterAddDcmtToFavs: onRefreshAfterAddDcmtToFavs, showBackButton: false, onRefreshSearchAsyncDatagrid: onRefreshSearchResults, onReferenceClick: handleNavigateToReference, fetchRemoteCertificates: fetchRemoteCertificates }, refreshKey)) }));
501
544
  };
502
- const TMLoadingOverlay = ({ onCancel, description, cancelText }) => {
545
+ const TMLoadingOverlay = ({ onCancel, onStop, description, cancelText, stopText }) => {
503
546
  return (_jsx("div", { style: {
504
547
  position: 'absolute',
505
548
  top: 0,
@@ -569,24 +612,39 @@ const TMLoadingOverlay = ({ onCancel, description, cancelText }) => {
569
612
  .tm-spinner-animation div:nth-child(7):after { top: 63px; left: 17px; background: #782b7d; }
570
613
  .tm-spinner-animation div:nth-child(8) { animation-delay: -0.288s; }
571
614
  .tm-spinner-animation div:nth-child(8):after { top: 56px; left: 12px; background: #782b7d; }
572
- ` }), _jsxs("div", { className: "tm-spinner-animation", children: [_jsx("div", {}), _jsx("div", {}), _jsx("div", {}), _jsx("div", {}), _jsx("div", {}), _jsx("div", {}), _jsx("div", {}), _jsx("div", {})] })] }), _jsx("span", { style: { fontSize: '14px', color: '#334155', textAlign: 'center' }, children: description ?? SDKUI_Localizator.Loading }), onCancel && (_jsx("button", { onClick: onCancel, style: {
573
- marginTop: '4px',
574
- padding: '8px 24px',
575
- fontSize: '13px',
576
- fontWeight: 500,
577
- color: '#64748b',
578
- background: 'transparent',
579
- border: '1.5px solid #cbd5e1',
580
- borderRadius: '6px',
581
- cursor: 'pointer',
582
- transition: 'all 0.2s ease',
583
- }, onMouseEnter: (e) => {
584
- e.currentTarget.style.background = '#f1f5f9';
585
- e.currentTarget.style.borderColor = '#94a3b8';
586
- e.currentTarget.style.color = '#475569';
587
- }, onMouseLeave: (e) => {
588
- e.currentTarget.style.background = 'transparent';
589
- e.currentTarget.style.borderColor = '#cbd5e1';
590
- e.currentTarget.style.color = '#64748b';
591
- }, children: cancelText ?? SDKUI_Localizator.Cancel }))] }) }));
615
+ ` }), _jsxs("div", { className: "tm-spinner-animation", children: [_jsx("div", {}), _jsx("div", {}), _jsx("div", {}), _jsx("div", {}), _jsx("div", {}), _jsx("div", {}), _jsx("div", {}), _jsx("div", {})] })] }), _jsx("span", { style: { fontSize: '14px', color: '#334155', textAlign: 'center' }, children: description ?? SDKUI_Localizator.Loading }), (onStop || onCancel) && (_jsxs("div", { style: { marginTop: '4px', display: 'flex', alignItems: 'center', justifyContent: 'center', flexWrap: 'wrap', gap: '10px' }, children: [onStop && (_jsx("button", { onClick: onStop, title: SDKUI_Localizator.StopRelationsCalculation, style: {
616
+ padding: '8px 24px',
617
+ fontSize: '13px',
618
+ fontWeight: 500,
619
+ color: 'white',
620
+ background: '#0c448e',
621
+ border: '1.5px solid #0c448e',
622
+ borderRadius: '6px',
623
+ cursor: 'pointer',
624
+ transition: 'all 0.2s ease',
625
+ }, onMouseEnter: (e) => {
626
+ e.currentTarget.style.background = '#0a3872';
627
+ e.currentTarget.style.borderColor = '#0a3872';
628
+ }, onMouseLeave: (e) => {
629
+ e.currentTarget.style.background = '#0c448e';
630
+ e.currentTarget.style.borderColor = '#0c448e';
631
+ }, children: stopText ?? SDKUI_Localizator.ShowPartialResults })), onCancel && (_jsx("button", { onClick: onCancel, style: {
632
+ padding: '8px 24px',
633
+ fontSize: '13px',
634
+ fontWeight: 500,
635
+ color: '#64748b',
636
+ background: 'transparent',
637
+ border: '1.5px solid #cbd5e1',
638
+ borderRadius: '6px',
639
+ cursor: 'pointer',
640
+ transition: 'all 0.2s ease',
641
+ }, onMouseEnter: (e) => {
642
+ e.currentTarget.style.background = '#f1f5f9';
643
+ e.currentTarget.style.borderColor = '#94a3b8';
644
+ e.currentTarget.style.color = '#475569';
645
+ }, onMouseLeave: (e) => {
646
+ e.currentTarget.style.background = 'transparent';
647
+ e.currentTarget.style.borderColor = '#cbd5e1';
648
+ e.currentTarget.style.color = '#64748b';
649
+ }, children: cancelText ?? SDKUI_Localizator.Back }))] }))] }) }));
592
650
  };
@@ -0,0 +1,19 @@
1
+ import { DcmtTypeDescriptor } from '@topconsultnpm/sdk-ts';
2
+ import { DcmtInfo } from '../../../ts';
3
+ interface TMMasterInfoFieldsProps {
4
+ /** Tipo documento: definisce i metadati (utente) da mostrare e come formattarli */
5
+ dtd: DcmtTypeDescriptor | undefined;
6
+ /** Documento di cui mostrare i metadati: se cambia, i valori vengono riletti */
7
+ dcmt: DcmtInfo | undefined;
8
+ /** Legge i valori del documento indicizzati per mid (il chiamante li tiene in cache) */
9
+ getValuesByMidAsync: () => Promise<Map<number, any>>;
10
+ /** Incrementare per forzare la rilettura dei valori (es. dopo il salvataggio del documento) */
11
+ reloadToken?: number;
12
+ /**
13
+ * Vista a tutto schermo: c'è spazio per tutti i metadati, quindi niente limite dell'anteprima
14
+ * ed è qui che compare la ricerca per nome/valore.
15
+ */
16
+ isExpandedView?: boolean;
17
+ }
18
+ declare const TMMasterInfoFields: ({ dtd, dcmt, getValuesByMidAsync, reloadToken, isExpandedView }: TMMasterInfoFieldsProps) => import("react/jsx-runtime").JSX.Element;
19
+ export default TMMasterInfoFields;
@@ -0,0 +1,172 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useMemo, useState } from 'react';
3
+ import { DataListViewModes, MetadataDataDomains, SDK_Globals } from '@topconsultnpm/sdk-ts';
4
+ import { hoverStyle, IconChevronDown, IconClearButton, IconCopy, IconSearch, IconSuccess, SDKUI_Localizator } from '../../../helper';
5
+ import { formatScalarValue, getUserMetadataSorted } from '../../../helper/dcmtsHelper';
6
+ import { useDataListItem } from '../../../hooks/useDataListItem';
7
+ import { TMColors } from '../../../utils/theme';
8
+ import ShowAlert from '../../base/TMAlert';
9
+ import TMTooltip from '../../base/TMTooltip';
10
+ // Metadati mostrati in linea prima del pulsante "mostra tutti".
11
+ const PREVIEW_COUNT = 12;
12
+ const styles = {
13
+ wrapper: { display: 'flex', flexDirection: 'column', flex: 1, minHeight: 0, borderTop: `1px solid ${TMColors.card_background}` },
14
+ body: { flex: 1, minHeight: 0, padding: '13px 10px 9px', display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(185px, 1fr))', gridAutoRows: 'max-content', alignContent: 'start', columnGap: '10px', rowGap: '13px', overflow: 'auto' },
15
+ item: { position: 'relative', display: 'flex', alignItems: 'center', minWidth: 0, minHeight: '30px', padding: '5px 10px 4px', borderRadius: '8px', border: `1px solid ${TMColors.card_background}`, background: TMColors.default_background, transition: 'border-color 0.15s ease, box-shadow 0.15s ease' },
16
+ label: { position: 'absolute', top: '-7px', left: '8px', maxWidth: 'calc(100% - 16px)', padding: '0 5px', background: TMColors.default_background, fontSize: '9px', fontWeight: 700, lineHeight: '13px', letterSpacing: '0.5px', textTransform: 'uppercase', color: TMColors.label_normal, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' },
17
+ value: { display: 'block', width: '100%', fontSize: '12.5px', fontWeight: 500, color: TMColors.text_normal, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', userSelect: 'text', cursor: 'text', minHeight: '17px', lineHeight: '17px' },
18
+ emptyValue: { color: TMColors.disabled, fontWeight: 400 },
19
+ // Pulsante di copia: pastiglia sul bordo superiore della card, come la caption
20
+ copyBtn: { position: 'absolute', top: '-9px', right: '6px', display: 'flex', alignItems: 'center', justifyContent: 'center', width: '18px', height: '18px', padding: 0, borderRadius: '50%', border: `1px solid ${TMColors.card_background}`, background: TMColors.default_background, color: TMColors.label_normal, cursor: 'pointer', transition: 'opacity 0.15s ease, background 0.15s ease, color 0.15s ease, border-color 0.15s ease' },
21
+ placeholder: { gridColumn: '1 / -1', padding: '6px 2px', color: TMColors.label_normal, fontSize: '12px' },
22
+ // Barra di ricerca
23
+ searchBar: { display: 'flex', alignItems: 'center', gap: '8px', padding: '12px 14px 0', flexShrink: 0 },
24
+ searchBox: { position: 'relative', display: 'flex', alignItems: 'center', flex: 1, minWidth: 0, maxWidth: '300px', height: '26px', borderRadius: '13px', border: `1px solid ${TMColors.card_background}`, background: TMColors.toolbar_background, transition: 'border-color 0.15s ease, box-shadow 0.15s ease' },
25
+ searchIcon: { flexShrink: 0, margin: '0 3px 0 9px', color: TMColors.label_normal, display: 'flex' },
26
+ searchInput: { flex: 1, minWidth: 0, height: '100%', padding: '0 2px', margin: 0, border: 'none', outline: 'none', background: 'transparent', fontSize: '12px', color: TMColors.text_normal },
27
+ searchClear: { display: 'flex', alignItems: 'center', justifyContent: 'center', width: '18px', height: '18px', marginRight: '5px', padding: 0, border: 'none', borderRadius: '50%', background: 'transparent', color: TMColors.label_normal, cursor: 'pointer', flexShrink: 0, transition: 'background 0.15s ease, color 0.15s ease' },
28
+ searchCount: { fontSize: '11px', fontWeight: 600, color: TMColors.label_normal, whiteSpace: 'nowrap', flexShrink: 0 },
29
+ // Pulsante "mostra tutti / comprimi"
30
+ footer: { display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '2px 10px 9px', flexShrink: 0 },
31
+ showAllBtn: { display: 'inline-flex', alignItems: 'center', gap: '6px', height: '24px', padding: '0 13px', borderRadius: '12px', border: `1px solid ${TMColors.primary_container}`, background: TMColors.primary_container, color: TMColors.primary, fontSize: '11.5px', fontWeight: 700, letterSpacing: '0.2px', cursor: 'pointer', transition: 'background 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease' },
32
+ };
33
+ // Sui dispositivi touch l'hover non esiste: il pulsante di copia deve restare sempre visibile
34
+ const SUPPORTS_HOVER = typeof window === 'undefined' || window.matchMedia?.('(hover: hover)')?.matches !== false;
35
+ // Colori del pulsante di copia nei suoi tre stati
36
+ const COPY_BTN_IDLE = { background: TMColors.default_background, borderColor: TMColors.card_background, color: TMColors.label_normal };
37
+ const COPY_BTN_HOVER = { background: TMColors.primary_container, borderColor: TMColors.primary, color: TMColors.primary };
38
+ const COPY_BTN_COPIED = { background: TMColors.default_background, borderColor: TMColors.success, color: TMColors.success };
39
+ // Card di un singolo metadato: hover e feedback della copia sono locali, così non si ridisegna tutta la griglia
40
+ const MetadataCard = ({ caption, copyText, children }) => {
41
+ const [isHovered, setIsHovered] = useState(false);
42
+ const [isCopied, setIsCopied] = useState(false);
43
+ // Il feedback "copiato" si spegne da solo: il timer va annullato se la card sparisce prima
44
+ useEffect(() => {
45
+ if (!isCopied)
46
+ return;
47
+ const timerID = setTimeout(() => setIsCopied(false), 1400);
48
+ return () => clearTimeout(timerID);
49
+ }, [isCopied]);
50
+ const copyAsync = async () => {
51
+ try {
52
+ await navigator.clipboard.writeText(copyText);
53
+ setIsCopied(true);
54
+ }
55
+ catch {
56
+ ShowAlert({ message: SDKUI_Localizator.FailedToCopyToClipboard, mode: 'error', title: SDKUI_Localizator.CopyToClipboard, duration: 3000 });
57
+ }
58
+ };
59
+ // Il pulsante resta visibile durante il feedback e quando ha il focus (accessibilità da tastiera)
60
+ const isCopyVisible = copyText !== '' && (!SUPPORTS_HOVER || isHovered || isCopied);
61
+ return (_jsxs("div", { style: styles.item, onMouseEnter: (e) => { setIsHovered(true); Object.assign(e.currentTarget.style, { borderColor: TMColors.primary, boxShadow: '0 2px 8px rgba(37, 89, 165, 0.14)' }); }, onMouseLeave: (e) => { setIsHovered(false); Object.assign(e.currentTarget.style, { borderColor: TMColors.card_background, boxShadow: 'none' }); }, children: [_jsx("span", { style: { ...styles.label, maxWidth: isCopyVisible ? 'calc(100% - 38px)' : 'calc(100% - 16px)' }, title: caption, children: caption }), children, copyText !== '' && (_jsx("button", { type: 'button', style: { ...styles.copyBtn, ...(isCopied ? COPY_BTN_COPIED : COPY_BTN_IDLE), opacity: isCopyVisible ? 1 : 0, pointerEvents: isCopyVisible ? 'auto' : 'none' }, title: isCopied ? SDKUI_Localizator.CopiedSuccessfully : SDKUI_Localizator.CopyToClipboard, "aria-label": SDKUI_Localizator.CopyToClipboard, onClick: copyAsync, onFocus: () => setIsHovered(true), onBlur: () => setIsHovered(false), ...hoverStyle(isCopied ? COPY_BTN_COPIED : COPY_BTN_HOVER, isCopied ? COPY_BTN_COPIED : COPY_BTN_IDLE), children: isCopied ? _jsx(IconSuccess, { fontSize: 11 }) : _jsx(IconCopy, { fontSize: 11 }) }))] }));
62
+ };
63
+ // Riepilogo dei metadati di un documento in card compatte
64
+ const TMMasterInfoFields = ({ dtd, dcmt, getValuesByMidAsync, reloadToken, isExpandedView = false }) => {
65
+ const { renderDataListCell, loadDataListsAsync, getDataListItem } = useDataListItem();
66
+ const [rows, setRows] = useState([]);
67
+ // Finché la prima lettura non è conclusa non si mostra "nessun dato" (evita il lampo al primo render)
68
+ const [isLoaded, setIsLoaded] = useState(false);
69
+ const [filter, setFilter] = useState('');
70
+ const [showAll, setShowAll] = useState(false);
71
+ // Carica i metadati del documento (valori + liste dati referenziate).
72
+ // getValuesByMidAsync è volutamente fuori dalle dipendenze: il chiamante la ricrea a ogni render
73
+ useEffect(() => {
74
+ let cancelled = false;
75
+ const loadAsync = async () => {
76
+ try {
77
+ if (!dcmt?.TID || !dcmt?.DID) {
78
+ setRows([]);
79
+ return;
80
+ }
81
+ const metadata = getUserMetadataSorted(dtd);
82
+ // Precarica le liste dati referenziate dai metadati, così le card mostrano subito il valore leggibile
83
+ const dataListIDs = new Set();
84
+ metadata.forEach(md => { if (md.dataDomain === MetadataDataDomains.DataList && md.dataListID)
85
+ dataListIDs.add(md.dataListID); });
86
+ await loadDataListsAsync(dataListIDs);
87
+ const valueByMid = await getValuesByMidAsync();
88
+ if (cancelled)
89
+ return;
90
+ setRows(metadata.map(md => ({
91
+ mid: md.id,
92
+ caption: (SDK_Globals.useLocalizedName ? md.nameLoc : md.name) ?? md.name ?? '',
93
+ value: valueByMid.get(md.id),
94
+ dataListID: (md.dataDomain === MetadataDataDomains.DataList && md.dataListID) ? md.dataListID : 0,
95
+ viewMode: md.dataListViewMode ?? DataListViewModes.None,
96
+ dataType: md.dataType,
97
+ format: md.format?.format,
98
+ formatCulture: md.format?.formatCulture,
99
+ })));
100
+ }
101
+ catch {
102
+ if (!cancelled)
103
+ setRows([]);
104
+ }
105
+ finally {
106
+ if (!cancelled)
107
+ setIsLoaded(true);
108
+ }
109
+ };
110
+ // Cambiando documento/tipo documento il filtro non ha più senso: si riparte dall'anteprima
111
+ setFilter('');
112
+ setShowAll(false);
113
+ loadAsync();
114
+ return () => { cancelled = true; };
115
+ }, [dtd?.id, dcmt?.TID, dcmt?.DID, reloadToken]);
116
+ // Testo su cui cerca il filtro: valore visualizzato del metadato (per le liste dati il nome dell'item)
117
+ const getSearchText = (item) => {
118
+ if (item.value === undefined || item.value === null || item.value === '')
119
+ return '';
120
+ if (item.dataListID > 0) {
121
+ const dataListItem = getDataListItem(item.dataListID, item.value);
122
+ return dataListItem ? `${dataListItem.name ?? ''} ${dataListItem.value ?? ''}` : String(item.value);
123
+ }
124
+ return formatScalarValue(item.value, item.dataType, item.format, item.formatCulture);
125
+ };
126
+ // Testo copiato negli appunti: quello che l'utente vede nella card (per le liste dati il nome dell'item)
127
+ const getCopyText = (item) => {
128
+ if (item.value === undefined || item.value === null || item.value === '')
129
+ return '';
130
+ if (item.dataListID > 0) {
131
+ const dataListItem = getDataListItem(item.dataListID, item.value);
132
+ return dataListItem?.name ?? String(item.value);
133
+ }
134
+ return formatScalarValue(item.value, item.dataType, item.format, item.formatCulture);
135
+ };
136
+ // Ricerca case insensitive su nome e valore del metadato
137
+ const query = filter.trim().toLowerCase();
138
+ const filteredRows = useMemo(() => {
139
+ if (!query)
140
+ return rows;
141
+ return rows.filter(item => item.caption.toLowerCase().includes(query) || getSearchText(item).toLowerCase().includes(query));
142
+ }, [rows, query]);
143
+ const renderCard = (item) => {
144
+ const hasValue = item.value !== undefined && item.value !== null && item.value !== '';
145
+ const displayNode = item.dataListID > 0 && hasValue ? renderDataListCell(item.value, item.dataListID, item.viewMode) : (hasValue ? formatScalarValue(item.value, item.dataType, item.format, item.formatCulture) : '');
146
+ return (_jsx(MetadataCard, { caption: item.caption, copyText: getCopyText(item), children: hasValue
147
+ ? (
148
+ // Tooltip con il valore esteso (utile quando è troncato)
149
+ _jsx(TMTooltip, { content: displayNode, parentStyle: { flex: 1, minWidth: 0 }, childStyle: { maxWidth: '100%', minWidth: 0 }, children: _jsx("span", { style: { ...styles.value, minWidth: 0 }, children: displayNode }) }))
150
+ // Segnaposto per i metadati vuoti: la card mantiene la stessa altezza
151
+ : _jsx("span", { style: { ...styles.value, ...styles.emptyValue }, children: "\u2014" }) }, item.mid));
152
+ };
153
+ // Ricerca dei metadati: solo nella vista ingrandita e solo quando sono troppi per essere trovati a vista
154
+ const renderSearchBar = () => (_jsxs("div", { style: styles.searchBar, children: [_jsxs("div", { style: styles.searchBox, children: [_jsx(IconSearch, { fontSize: 13, style: styles.searchIcon }), _jsx("input", { type: 'text', value: filter, placeholder: `${SDKUI_Localizator.SearchAction}...`, style: styles.searchInput, onChange: (e) => setFilter(e.target.value),
155
+ // Esc svuota il filtro senza propagare la chiusura del modale
156
+ onKeyDown: (e) => { if (e.key === 'Escape' && filter !== '') {
157
+ e.preventDefault();
158
+ e.stopPropagation();
159
+ setFilter('');
160
+ } }, onFocus: (e) => Object.assign(e.currentTarget.parentElement?.style ?? {}, { borderColor: TMColors.primary, boxShadow: '0 0 0 2px rgba(37, 89, 165, 0.14)' }), onBlur: (e) => Object.assign(e.currentTarget.parentElement?.style ?? {}, { borderColor: TMColors.card_background, boxShadow: 'none' }) }), filter !== '' && (_jsx("button", { type: 'button', style: styles.searchClear, title: SDKUI_Localizator.Clear, "aria-label": SDKUI_Localizator.Clear, onClick: () => setFilter(''), ...hoverStyle({ background: TMColors.primary_container, color: TMColors.primary }, { background: 'transparent', color: TMColors.label_normal }), children: _jsx(IconClearButton, { fontSize: 13 }) }))] }), _jsxs("span", { style: styles.searchCount, children: [filteredRows.length, "/", rows.length] })] }));
161
+ // La ricerca sta solo nella vista ingrandita: in linea lo spazio è poco e l'elenco è limitato
162
+ // all'anteprima, con il pulsante per aprirlo tutto
163
+ const hasSearch = isExpandedView && rows.length > PREVIEW_COUNT;
164
+ const isCapped = !isExpandedView && !showAll && filteredRows.length > PREVIEW_COUNT;
165
+ const canCollapse = !isExpandedView && showAll && filteredRows.length > PREVIEW_COUNT;
166
+ const visibleRows = isCapped ? filteredRows.slice(0, PREVIEW_COUNT) : filteredRows;
167
+ const hiddenCount = filteredRows.length - visibleRows.length;
168
+ return (_jsxs("div", { style: { ...styles.wrapper, ...(isExpandedView ? { borderTop: 'none' } : {}) }, children: [hasSearch && renderSearchBar(), _jsx("div", { style: { ...styles.body, ...(isExpandedView ? { padding: '18px 14px 14px' } : {}) }, children: visibleRows.length > 0
169
+ ? visibleRows.map(renderCard)
170
+ : (isLoaded && _jsx("span", { style: styles.placeholder, children: SDKUI_Localizator.NoDataToDisplay })) }), (isCapped || canCollapse) && (_jsx("div", { style: styles.footer, children: _jsxs("button", { type: 'button', style: styles.showAllBtn, onClick: () => setShowAll(!showAll), ...hoverStyle({ background: TMColors.primary, borderColor: TMColors.primary, color: '#ffffff', boxShadow: '0 2px 6px rgba(37, 89, 165, 0.25)' }, { background: TMColors.primary_container, borderColor: TMColors.primary_container, color: TMColors.primary, boxShadow: 'none' }), children: [isCapped ? `${SDKUI_Localizator.ShowAll} (+${hiddenCount})` : SDKUI_Localizator.Collapse, _jsx(IconChevronDown, { fontSize: 13, style: { transform: isCapped ? 'none' : 'rotate(180deg)', transition: 'transform 0.2s ease' } })] }) }))] }));
171
+ };
172
+ export default TMMasterInfoFields;
@@ -0,0 +1,40 @@
1
+ import { DcmtTypeDescriptor, HomeBlogPost, ObjectRef, TaskDescriptor } from "@topconsultnpm/sdk-ts";
2
+ import { DcmtInfo, IGraphometricManagerProp, TaskContext } from "../../../ts";
3
+ import { IntesiCertificateData } from "../../../helper";
4
+ interface TMMultiMasterDetailDcmtsFormProps {
5
+ /** Tipo documento del master (per individuare le relazioni di dettaglio) */
6
+ dtd: DcmtTypeDescriptor;
7
+ /** Documento master da cui parte l'archiviazione dei dettagli */
8
+ masterDcmt: DcmtInfo;
9
+ /** Funzione per la chiusura del modale */
10
+ onClose: () => void;
11
+ allTasks?: Array<TaskDescriptor>;
12
+ getAllTasks?: () => Promise<void>;
13
+ deleteTaskByIdsCallback?: (deletedTaskIds: Array<number>) => Promise<void>;
14
+ addTaskCallback?: (task: TaskDescriptor) => Promise<void>;
15
+ editTaskCallback?: (task: TaskDescriptor) => Promise<void>;
16
+ handleNavigateToWGs?: (value: HomeBlogPost | number) => Promise<void>;
17
+ handleNavigateToDossiers?: (value: HomeBlogPost | number) => Promise<void>;
18
+ showDcmtFormSidebar?: boolean;
19
+ showTodoDcmtForm?: boolean;
20
+ openFileUploaderPdfEditor?: (fromDTD?: DcmtTypeDescriptor, file?: File | null, handleFile?: (file: File) => void) => void;
21
+ graphometricManager?: IGraphometricManagerProp;
22
+ editPdfForm?: boolean;
23
+ openS4TViewer?: boolean;
24
+ showToppyDraggableHelpCenter?: boolean;
25
+ toppyHelpCenterUsePortal?: boolean;
26
+ fetchRemoteCertificates?: (email: string) => Promise<IntesiCertificateData[]>;
27
+ onOpenS4TViewerRequest?: (dcmtInfo: Array<DcmtInfo>, refreshDocumentPreview?: () => Promise<void>) => void;
28
+ onOpenPdfEditorRequest?: (dcmtInfo: Array<DcmtInfo>, refreshDocumentPreview?: () => Promise<void>) => void;
29
+ onReferenceClick?: (ref: ObjectRef) => void;
30
+ onTaskCreateRequest?: (taskContext: TaskContext, onTaskCreated?: (task?: TaskDescriptor) => void) => void;
31
+ onRefreshAfterAddDcmtToFavs?: () => void;
32
+ onFileOpened?: (blob: File | undefined) => void;
33
+ passToArchiveCallback?: (outputMids: Array<{
34
+ mid: number;
35
+ value: string;
36
+ }>, tid?: number) => void;
37
+ openWGsCopyMoveForm?: (mode: "copyToWgDraft" | "copyToWgArchivedDoc", dcmtTypeDescriptor: DcmtTypeDescriptor, documents: Array<DcmtInfo>) => void;
38
+ }
39
+ declare const TMMultiMasterDetailDcmtsForm: (props: TMMultiMasterDetailDcmtsFormProps) => import("react/jsx-runtime").JSX.Element | null;
40
+ export default TMMultiMasterDetailDcmtsForm;