@topconsultnpm/sdkui-react 6.22.0-dev3.24 → 6.22.0-dev3.25

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 (47) hide show
  1. package/lib/components/base/TMAlert.js +6 -8
  2. package/lib/components/base/TMAreaManager.js +53 -55
  3. package/lib/components/base/TMCustomButton.js +39 -48
  4. package/lib/components/base/TMTreeView.js +14 -0
  5. package/lib/components/base/waitPanel/TMWaitPanelConfirm.d.ts +12 -0
  6. package/lib/components/base/waitPanel/TMWaitPanelConfirm.js +185 -0
  7. package/lib/components/base/waitPanel/TMWaitPanelManager.d.ts +169 -0
  8. package/lib/components/base/waitPanel/TMWaitPanelManager.js +320 -0
  9. package/lib/components/base/waitPanel/TMWaitPanelView.d.ts +53 -0
  10. package/lib/components/base/waitPanel/TMWaitPanelView.js +274 -0
  11. package/lib/components/base/waitPanel/index.d.ts +1 -0
  12. package/lib/components/base/waitPanel/index.js +1 -0
  13. package/lib/components/choosers/TMPathChooser.js +17 -34
  14. package/lib/components/features/assistant/TMToppyDraggableHelpCenter.js +2 -2
  15. package/lib/components/features/documents/TMBatchUpdateForm.js +3 -4
  16. package/lib/components/features/documents/TMCopyToFolderForm.js +32 -35
  17. package/lib/components/features/documents/TMDcmtForm.js +48 -60
  18. package/lib/components/features/documents/TMDcmtIcon.d.ts +1 -3
  19. package/lib/components/features/documents/TMDcmtIcon.js +3 -5
  20. package/lib/components/features/documents/TMDcmtPreview.js +5 -3
  21. package/lib/components/features/documents/TMMasterDetailDcmts.d.ts +2 -0
  22. package/lib/components/features/documents/TMMasterDetailDcmts.js +45 -147
  23. package/lib/components/features/documents/TMMergeToPdfForm.js +48 -47
  24. package/lib/components/features/documents/TMMultiMasterDetailDcmtsForm.js +14 -15
  25. package/lib/components/features/documents/TMRelationViewer.d.ts +3 -14
  26. package/lib/components/features/documents/TMRelationViewer.js +115 -79
  27. package/lib/components/features/search/TMSearchResult.js +6 -8
  28. package/lib/components/features/search/TMViewHistoryDcmt.js +14 -25
  29. package/lib/components/features/tasks/TMTasksView.js +91 -90
  30. package/lib/components/features/wg/TMWGsCopyMoveForm.js +91 -91
  31. package/lib/components/grids/TMBlogsPost.js +92 -96
  32. package/lib/components/index.d.ts +2 -0
  33. package/lib/components/index.js +2 -0
  34. package/lib/components/pages/TMPage.js +29 -31
  35. package/lib/helper/SDKUI_Localizator.d.ts +7 -0
  36. package/lib/helper/SDKUI_Localizator.js +70 -0
  37. package/lib/helper/ZipManager.d.ts +1 -1
  38. package/lib/helper/ZipManager.js +2 -1
  39. package/lib/hooks/useCheckInOutOperations.d.ts +0 -6
  40. package/lib/hooks/useCheckInOutOperations.js +14 -34
  41. package/lib/hooks/useDcmtOperations.d.ts +3 -12
  42. package/lib/hooks/useDcmtOperations.js +130 -160
  43. package/lib/hooks/useDocumentOperations.js +12 -23
  44. package/lib/hooks/useMultiMasterDetailDcmts.d.ts +0 -15
  45. package/lib/hooks/useMultiMasterDetailDcmts.js +62 -65
  46. package/lib/hooks/useWorkflowApprove.js +33 -17
  47. package/package.json +1 -1
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import React, { useState, useEffect } from 'react';
3
3
  import ReactDOM from 'react-dom/client';
4
4
  import { IconCloseCircle, IconInfo, IconSuccess, IconWarning } from '../../helper';
5
+ import { DeviceType, TMDeviceProvider, useDeviceType } from './TMDeviceProvider';
5
6
  const TMAlertPosition = {
6
7
  TOP_RIGHT: { top: '10px', right: '10px', position: 'absolute' },
7
8
  TOP_CENTER: { top: '10px', left: '50%', transform: 'translateX(-50%)', position: 'absolute' },
@@ -45,13 +46,8 @@ const Mode = {
45
46
  };
46
47
  // const TMAlert = ({ mode, duration = 5000, message, position = 'BOTTOM_CENTER' }: { mode: keyof AlertModes, duration?: number, message: string, position?: keyof typeof TMAlertPosition }) => {
47
48
  const TMAlert = ({ mode, duration = 1000, title, message, position = 'BOTTOM_RIGHT' }) => {
48
- const [currentWidth, setCurrentWidth] = useState(window.innerWidth);
49
+ const deviceType = useDeviceType();
49
50
  const [container] = useState(document.getElementsByClassName('tm-alert-temporary-container'));
50
- useEffect(() => {
51
- window.addEventListener('resize', () => {
52
- setCurrentWidth(window.innerWidth);
53
- });
54
- }, [currentWidth]);
55
51
  useEffect(() => {
56
52
  const tiemout = setTimeout(() => {
57
53
  let container = document.getElementsByClassName('tm-alert-temporary-container');
@@ -63,7 +59,9 @@ const TMAlert = ({ mode, duration = 1000, title, message, position = 'BOTTOM_RIG
63
59
  }, [container]);
64
60
  // Determine the style for the alert container based on the position prop
65
61
  const alertContainerStyle = TMAlertPosition[position];
66
- return (_jsx("div", { style: { width: '100%', paddingTop: '3px', ...alertContainerStyle }, children: _jsx("div", { className: 'tm-alert-animation-style', style: { animationDuration: `${duration}ms`, position: 'relative', float: 'right', zIndex: '20000999', width: '356px', height: '86px', borderRadius: '8px', overflow: 'hidden', border: '2px white solid', boxShadow: '0px 3px 6px #00000029' }, children: _jsx("div", { className: 'tm-alert-animation-style', style: { animationDuration: `${duration}ms` }, children: _jsxs("div", { className: 'tm-alert-container', style: { background: Mode[mode].color, animationDuration: `${duration}ms` }, children: [_jsxs("div", { className: "tm-alert-context", style: { position: 'relative' }, children: [_jsx("div", { className: 'tm-alert-icon', style: { position: 'absolute', top: '12px', left: '12px', paddingTop: '1px' }, children: Mode[mode].icon }), _jsx("div", { style: { color: Mode[mode].textColor, position: 'absolute', top: '15px', left: '45px', textAlign: 'left', fontSize: '1.1rem', fontWeight: 'bold', maxWidth: '280px' }, children: title }), _jsx("div", { style: { color: Mode[mode].textColor, position: 'absolute', top: '35px', left: '45px', textAlign: 'left', fontSize: '1rem', maxWidth: '280px' }, children: message })] }), _jsx("div", { className: 'tm-alert-loading', style: { backgroundImage: `linear-gradient(to right, ${Mode[mode].borderColor}, ${Mode[mode].textColor})`, animationDuration: `${duration}ms` } })] }) }) }) }));
62
+ // Su mobile l'alert e' piu' basso e con testo piu' piccolo: la versione desktop copre troppo schermo.
63
+ const isMobile = deviceType === DeviceType.MOBILE;
64
+ return (_jsx("div", { style: { width: '100%', paddingTop: '3px', ...alertContainerStyle }, children: _jsx("div", { className: 'tm-alert-animation-style', style: { animationDuration: `${duration}ms`, position: 'relative', float: 'right', zIndex: '20000999', width: isMobile ? 'min(300px, calc(100vw - 20px))' : '356px', height: isMobile ? '58px' : '86px', borderRadius: '8px', overflow: 'hidden', border: '2px white solid', boxShadow: '0px 3px 6px #00000029' }, children: _jsx("div", { className: 'tm-alert-animation-style', style: { animationDuration: `${duration}ms` }, children: _jsxs("div", { className: 'tm-alert-container', style: { background: Mode[mode].color, animationDuration: `${duration}ms` }, children: [_jsxs("div", { className: "tm-alert-context", style: { position: 'relative' }, children: [_jsx("div", { className: 'tm-alert-icon', style: { position: 'absolute', top: isMobile ? '8px' : '12px', left: isMobile ? '8px' : '12px', paddingTop: '1px' }, children: Mode[mode].icon }), _jsx("div", { style: { color: Mode[mode].textColor, position: 'absolute', top: isMobile ? '7px' : '15px', left: isMobile ? '38px' : '45px', textAlign: 'left', fontSize: isMobile ? '0.85rem' : '1.1rem', fontWeight: 'bold', maxWidth: isMobile ? 'calc(100% - 46px)' : '280px', ...(isMobile && { whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }) }, children: title }), _jsx("div", { style: { color: Mode[mode].textColor, position: 'absolute', top: isMobile ? '25px' : '35px', left: isMobile ? '38px' : '45px', textAlign: 'left', fontSize: isMobile ? '0.8rem' : '1rem', maxWidth: isMobile ? 'calc(100% - 46px)' : '280px', ...(isMobile && { whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }) }, children: message })] }), _jsx("div", { className: 'tm-alert-loading', style: { backgroundImage: `linear-gradient(to right, ${Mode[mode].borderColor}, ${Mode[mode].textColor})`, animationDuration: `${duration}ms` } })] }) }) }) }));
67
65
  };
68
66
  const ShowAlert = ({ mode, message, duration, title, position = 'BOTTOM_RIGHT' }) => {
69
67
  let container = document.createElement('div');
@@ -72,6 +70,6 @@ const ShowAlert = ({ mode, message, duration, title, position = 'BOTTOM_RIGHT' }
72
70
  // container.style.position = 'relative'
73
71
  container.style.display = 'flex';
74
72
  const root = ReactDOM.createRoot(container);
75
- root.render(_jsx(React.StrictMode, { children: _jsx(TMAlert, { message: message, mode: mode, duration: duration, title: title, position: position }) }));
73
+ root.render(_jsx(React.StrictMode, { children: _jsx(TMDeviceProvider, { children: _jsx(TMAlert, { message: message, mode: mode, duration: duration, title: title, position: position }) }) }));
76
74
  };
77
75
  export default ShowAlert;
@@ -10,7 +10,8 @@ import { loadMessages } from 'devextreme/localization';
10
10
  import { getFileManagerFolderColor, Globalization, IconAll, IconCloud, IconFolder, IconFolderForCSS, IconSelected, SDKUI_Localizator, svgToString } from '../../helper';
11
11
  import { TMExceptionBoxManager } from './TMPopUp';
12
12
  import ShowAlert from './TMAlert';
13
- import { TMLayoutWaitingContainer } from './TMWaitPanel';
13
+ import TMLayoutContainer from './TMLayout';
14
+ import TMWaitPanelManager from './waitPanel/TMWaitPanelManager';
14
15
  import TMCounterContainer, { CounterItemKey } from './TMCounterContainer';
15
16
  import { IconPdf, IconTxt, IconXls, IconDocx, IconImage, IconZip, IconXml, IconMp4, IconEmail, IconPpt, IconSigned, IconExe, IconHtml, IconDwg, IconDicom, IconSlddrw } from '../../assets/thumbnails';
16
17
  import styled from 'styled-components';
@@ -89,17 +90,6 @@ const TMAreaManager = (props = { selectionMode: 'multiple', isPathChooser: false
89
90
  const [counterValues, setCounterValues] = useState(new Map());
90
91
  // State to maintain the selection mode (File System Item - right panel)
91
92
  const [selectionMode, setSelectionMode] = useState('single');
92
- /* State to manage the Wait Panel */
93
- const [showWaitPanel, setShowWaitPanel] = useState(false);
94
- const [waitPanelTitle, setWaitPanelTitle] = useState('');
95
- const [showPrimary, setShowPrimary] = useState(false);
96
- const [waitPanelTextPrimary, setWaitPanelTextPrimary] = useState('');
97
- const [waitPanelValuePrimary, setWaitPanelValuePrimary] = useState(0);
98
- const [waitPanelMaxValuePrimary, setWaitPanelMaxValuePrimary] = useState(0);
99
- const [showSecondary, setShowSecondary] = useState(false);
100
- const [waitPanelTextSecondary, setWaitPanelTextSecondary] = useState('');
101
- const [waitPanelValueSecondary, setWaitPanelValueSecondary] = useState(0);
102
- const [waitPanelMaxValueSecondary, setWaitPanelMaxValueSecondary] = useState(0);
103
93
  // Timer ID to store reference for any scheduled operation
104
94
  let timerId = null;
105
95
  // Prefix for area folder names
@@ -430,44 +420,55 @@ const TMAreaManager = (props = { selectionMode: 'multiple', isPathChooser: false
430
420
  }
431
421
  };
432
422
  const downloadItems = async (items) => {
423
+ const files = items.filter(item => !item.isDirectory);
424
+ const isBatch = files.length > 1;
425
+ const waitPanel = TMWaitPanelManager.show({
426
+ title: "Download",
427
+ max: isBatch ? files.length : 0,
428
+ cancelable: true,
429
+ showCounters: false,
430
+ showSecondary: isBatch,
431
+ primaryLabel: isBatch ? 'File' : undefined,
432
+ secondaryLabel: isBatch ? 'File corrente' : undefined,
433
+ });
434
+ abortController = waitPanel.abortController;
435
+ const publishBytes = (loaded, total) => {
436
+ const text = total > 0
437
+ ? `Downloading... ${Globalization.getNumberDisplayValue(loaded, true)} / ${Globalization.getNumberDisplayValue(total, true)}`
438
+ : '';
439
+ if (isBatch)
440
+ waitPanel.setSecondary({ value: loaded, max: total, text });
441
+ else
442
+ waitPanel.update({ value: loaded, max: total, subtitle: text });
443
+ };
444
+ // Un solo avviso per tutta l'operazione: con più file riporta quanti ne sono stati scaricati.
445
+ let downloaded = 0;
446
+ const showInterrupted = () => ShowAlert({
447
+ mode: 'warning',
448
+ title: "Download",
449
+ duration: 3000,
450
+ message: isBatch ? SDKUI_Localizator.DownloadInterruptedFiles.replaceParams(downloaded, files.length) : SDKUI_Localizator.DownloadInterrupted,
451
+ });
433
452
  try {
434
- setShowWaitPanel(true);
435
- setShowPrimary(items.length > 1);
436
- setShowSecondary(true);
437
- setWaitPanelTitle("Download");
438
- abortController = new AbortController();
439
- let i = 0;
440
- setWaitPanelMaxValuePrimary(items.length);
441
- let firstBlock = true;
442
- let maxFileSize = 0;
443
- for (const item of items) {
444
- if (item.isDirectory)
445
- continue;
446
- if (abortController.signal.aborted) {
447
- ShowAlert({ message: 'Operazione interrotta', mode: 'warning', title: "Download files", duration: 3000 });
453
+ for (const item of files) {
454
+ if (waitPanel.aborted) {
455
+ showInterrupted();
448
456
  return;
449
457
  }
450
- setWaitPanelTextPrimary(`Download ${item.name}`);
458
+ if (isBatch)
459
+ waitPanel.setSubtitle(`Download ${item.name}`);
451
460
  const ad = item.dataItem.dataItem;
452
461
  const aid = ad?.id;
453
462
  const subFolder = item.parentPath === ad.name ? '' : item.parentPath.replace(ad.name + '/', '');
454
463
  const tms = props.tmSession ?? SDK_Globals.tmSession;
455
- const file = await tms?.NewAreaEngine().RetrieveFileAsync(aid, subFolder, item.name, FileFormats.None, abortController.signal, (pd) => {
456
- if (firstBlock) {
457
- maxFileSize = pd.ProgressBarMaximum ?? 0;
458
- setWaitPanelMaxValueSecondary(maxFileSize);
459
- firstBlock = false;
460
- }
461
- setWaitPanelValueSecondary(pd.ProgressBarValue);
462
- setWaitPanelTextSecondary(`Downloading... ${Globalization.getNumberDisplayValue(pd.ProgressBarValue, true)} / ${Globalization.getNumberDisplayValue(maxFileSize, true)}`);
463
- if (pd.ProgressBarValue === pd.ProgressBarMaximum) {
464
- setWaitPanelMaxValueSecondary(0);
465
- setWaitPanelValueSecondary(0);
466
- setWaitPanelTextSecondary('');
467
- firstBlock = true;
468
- }
469
- });
470
- setWaitPanelValuePrimary(i + 1);
464
+ let file;
465
+ try {
466
+ file = await tms?.NewAreaEngine().RetrieveFileAsync(aid, subFolder, item.name, FileFormats.None, waitPanel.signal, (pd) => publishBytes(pd.ProgressBarValue, pd.ProgressBarMaximum ?? 0));
467
+ }
468
+ finally {
469
+ if (isBatch)
470
+ publishBytes(0, 0);
471
+ }
471
472
  const fileURL = window.URL.createObjectURL(file);
472
473
  const alink2 = document.createElement('a');
473
474
  alink2.href = fileURL;
@@ -475,25 +476,22 @@ const TMAreaManager = (props = { selectionMode: 'multiple', isPathChooser: false
475
476
  alink2.target = "_blank";
476
477
  alink2.rel = "noreferrer";
477
478
  alink2.click();
478
- i++;
479
+ downloaded++;
480
+ if (isBatch)
481
+ await waitPanel.step();
479
482
  }
483
+ if (files.length > 0)
484
+ ShowAlert({ mode: 'success', message: SDKUI_Localizator.OperationSuccess, title: "Download", duration: 3000 });
480
485
  }
481
486
  catch (ex) {
482
487
  const err = ex;
483
- if (err.name === 'CanceledError') {
484
- ShowAlert({ message: err.message, mode: 'warning', duration: 3000, title: 'Abort' });
485
- }
488
+ if (waitPanel.aborted || err.name === 'CanceledError')
489
+ showInterrupted();
486
490
  else
487
491
  TMExceptionBoxManager.show({ exception: ex });
488
492
  }
489
493
  finally {
490
- setWaitPanelTextPrimary('');
491
- setWaitPanelMaxValuePrimary(0);
492
- setWaitPanelValuePrimary(0);
493
- setWaitPanelTextSecondary('');
494
- setWaitPanelMaxValueSecondary(0);
495
- setWaitPanelValueSecondary(0);
496
- setShowWaitPanel(false);
494
+ waitPanel.close();
497
495
  }
498
496
  };
499
497
  // Asynchronous function to retrieve the items in a given directory (parentDirectory)
@@ -767,7 +765,7 @@ const TMAreaManager = (props = { selectionMode: 'multiple', isPathChooser: false
767
765
  }
768
766
  };
769
767
  const folderColor = getFileManagerFolderColor(SDK_Globals.tmSession?.SessionDescr?.appModuleID ?? AppModules.SURFER);
770
- return (_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: _jsxs(StyledFileManagerWrapper, { $folderColor: folderColor, style: { width: "100%", height: "100%" }, children: [_jsxs(FileManager, { width: props.width, ref: fileManagerRef, height: `calc(${props.height} - 30px)`, onItemMoving: onItemCopying, onItemCopying: onItemCopying, onFileUploading: onFileUploading, fileSystemProvider: areaProvider, customizeThumbnail: customizeIcon, rootFolderName: SDK_Localizator.Areas, onSelectionChanged: onSelectionChanged, onDirectoryCreating: onDirectoryCreating, onFocusedItemChanged: onFocusedItemChanged, onSelectedFileOpened: onSelectedFileOpened, onContextMenuItemClick: onContextMenuItemClick,
768
+ return (_jsx(TMLayoutContainer, { direction: 'vertical', children: _jsxs(StyledFileManagerWrapper, { $folderColor: folderColor, style: { width: "100%", height: "100%" }, children: [_jsxs(FileManager, { width: props.width, ref: fileManagerRef, height: `calc(${props.height} - 30px)`, onItemMoving: onItemCopying, onItemCopying: onItemCopying, onFileUploading: onFileUploading, fileSystemProvider: areaProvider, customizeThumbnail: customizeIcon, rootFolderName: SDK_Localizator.Areas, onSelectionChanged: onSelectionChanged, onDirectoryCreating: onDirectoryCreating, onFocusedItemChanged: onFocusedItemChanged, onSelectedFileOpened: onSelectedFileOpened, onContextMenuItemClick: onContextMenuItemClick,
771
769
  /* onItemMoved={() => setCounter(counter => counter + 1)}
772
770
  onItemCopied={() => setCounter(counter => counter + 1)}
773
771
  onItemDeleted={() => setCounter(counter => counter - 1)}
@@ -1,10 +1,11 @@
1
- import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useRef, useState, useMemo } from 'react';
3
3
  import TMModal from './TMModal';
4
4
  import styled from 'styled-components';
5
- import { SDKUI_Localizator, TMLayoutWaitingContainer } from '../..';
5
+ import { SDKUI_Localizator } from '../..';
6
6
  import { getButtonAttributes, getSelectedItem } from '../../helper/dcmtsHelper';
7
7
  import { DeviceType, useDeviceType } from './TMDeviceProvider';
8
+ import TMWaitPanelManager from './waitPanel/TMWaitPanelManager';
8
9
  const IframeContainer = styled.div `
9
10
  display: flex;
10
11
  height: 100%;
@@ -53,12 +54,6 @@ const TMCustomButton = (props) => {
53
54
  const [loading, setLoading] = useState(true);
54
55
  const [error, setError] = useState(false);
55
56
  const itemsToProcess = useMemo(() => selectedItems && selectedItems.length > 0 ? selectedItems : [attributes], [selectedItems, attributes]);
56
- // Stati per il wait panel
57
- const [showWaitPanel, setShowWaitPanel] = useState(itemsToProcess.length > 0 && !RunOnce);
58
- const [waitPanelText, setWaitPanelText] = useState(SDKUI_Localizator.CustomButtonActions.replaceParams(1, itemsToProcess.length));
59
- const [waitPanelValue, setWaitPanelValue] = useState(0);
60
- const [waitPanelMaxValue, setWaitPanelMaxValue] = useState(itemsToProcess.length);
61
- const [abortController, setAbortController] = useState(undefined);
62
57
  // Aggiungi timestamp all'URL per evitare cache
63
58
  const iframeUrl = useMemo(() => {
64
59
  if (!scriptUrl)
@@ -83,41 +78,44 @@ const TMCustomButton = (props) => {
83
78
  setLoading(false);
84
79
  setError(true);
85
80
  };
86
- const executeSequentially = async (controller) => {
81
+ const executeSequentially = async () => {
87
82
  if (!itemsToProcess)
88
83
  return;
89
- for (const [index, item] of itemsToProcess.entries()) {
90
- if (controller.signal.aborted)
91
- break;
92
- setWaitPanelText(SDKUI_Localizator.CustomButtonActions.replaceParams(index + 1, itemsToProcess.length));
93
- setWaitPanelValue(index);
94
- // Attendi che l'iframe sia pronto e invia il messaggio
95
- await new Promise((resolve) => {
96
- const checkIframe = setInterval(() => {
97
- if (iframeRef.current?.contentWindow) {
98
- clearInterval(checkIframe);
99
- if (selectedItems && selectedItems.length > 0) {
100
- //devo convertire item in formData
101
- const processedItem = getSelectedItem(args, formData, item);
102
- postMessageIframe(processedItem);
103
- }
104
- else {
105
- postMessageIframe(item);
84
+ const waitPanel = TMWaitPanelManager.show({
85
+ title: SDKUI_Localizator.CustomButtonAction,
86
+ max: itemsToProcess.length,
87
+ cancelable: true,
88
+ });
89
+ try {
90
+ for (const [index, item] of itemsToProcess.entries()) {
91
+ if (waitPanel.aborted)
92
+ break;
93
+ waitPanel.setSubtitle(SDKUI_Localizator.CustomButtonActions.replaceParams(index + 1, itemsToProcess.length));
94
+ // Attendi che l'iframe sia pronto e invia il messaggio
95
+ await new Promise((resolve) => {
96
+ const checkIframe = setInterval(() => {
97
+ if (iframeRef.current?.contentWindow) {
98
+ clearInterval(checkIframe);
99
+ if (selectedItems && selectedItems.length > 0) {
100
+ //devo convertire item in formData
101
+ const processedItem = getSelectedItem(args, formData, item);
102
+ postMessageIframe(processedItem);
103
+ }
104
+ else {
105
+ postMessageIframe(item);
106
+ }
107
+ // Attendi prima di passare al prossimo
108
+ setTimeout(resolve, 500);
106
109
  }
107
- //imposta 100% se sono all'ultimo item
108
- if (index === itemsToProcess.length - 1)
109
- setWaitPanelValue(index + 1);
110
- // Attendi prima di passare al prossimo
111
- setTimeout(() => {
112
- setWaitPanelValue(index + 1);
113
- resolve();
114
- }, 500);
115
- }
116
- }, 100);
117
- });
110
+ }, 100);
111
+ });
112
+ await waitPanel.step();
113
+ }
114
+ }
115
+ finally {
116
+ waitPanel.close();
117
+ onClose?.();
118
118
  }
119
- setShowWaitPanel(false);
120
- onClose?.();
121
119
  };
122
120
  const postMessageIframe = (data) => {
123
121
  console.log("TMCustomButton - postMessageIframe - data:", data);
@@ -130,14 +128,7 @@ const TMCustomButton = (props) => {
130
128
  return;
131
129
  if (!RunOnce && itemsToProcess.length > 0) {
132
130
  // esegui per ogni item selezionato
133
- const controller = new AbortController();
134
- controller.signal.addEventListener('abort', () => {
135
- setShowWaitPanel(false);
136
- onClose?.();
137
- });
138
- setAbortController(controller);
139
- setWaitPanelMaxValue(itemsToProcess.length);
140
- executeSequentially(controller);
131
+ executeSequentially();
141
132
  }
142
133
  else {
143
134
  // Modalità RunOnce: invia dati all'iframe quando è caricato
@@ -157,6 +148,6 @@ const TMCustomButton = (props) => {
157
148
  }
158
149
  }, []);
159
150
  const iframeContent = (_jsxs(IframeContainer, { style: !RunOnce ? { visibility: 'hidden' } : {}, children: [loading && _jsx(LoadingOverlay, {}), error && _jsx("div", { children: "Si \u00E8 verificato un errore nel caricamento del contenuto." }), !error && _jsx(StyledIframe, { ref: iframeRef, loading: 'lazy', onLoad: handleLoad, onError: handleError, src: iframeUrl })] }));
160
- return isModal && RunOnce ? (_jsx(TMModal, { title: button.title, width: isMobile ? '95%' : '60%', height: isMobile ? '95%' : '70%', resizable: isMobile ? false : true, expandable: isMobile ? false : true, onClose: onClose, children: iframeContent })) : !RunOnce && (_jsxs(_Fragment, { children: [_jsx(TMLayoutWaitingContainer, { showWaitPanel: showWaitPanel, waitPanelTitle: SDKUI_Localizator.CustomButtonAction, showWaitPanelPrimary: true, waitPanelTextPrimary: waitPanelText, waitPanelValuePrimary: waitPanelValue, waitPanelMaxValuePrimary: waitPanelMaxValue, showWaitPanelSecondary: false, isCancelable: true, abortController: abortController, children: undefined }), iframeContent] }));
151
+ return isModal && RunOnce ? (_jsx(TMModal, { title: button.title, width: isMobile ? '95%' : '60%', height: isMobile ? '95%' : '70%', resizable: isMobile ? false : true, expandable: isMobile ? false : true, onClose: onClose, children: iframeContent })) : !RunOnce && iframeContent;
161
152
  };
162
153
  export default TMCustomButton;
@@ -4,6 +4,17 @@ import { List } from 'react-window';
4
4
  import styled from 'styled-components';
5
5
  import { IconArrowLeft, IconArrowRight, IconChevronDown, IconChevronRight, SDKUI_Localizator } from '../../helper';
6
6
  import TMButton from './TMButton';
7
+ // Campi in cui frecce, +, - e spazio servono alla digitazione (checkbox e bottoni esclusi)
8
+ const NON_TEXT_INPUT_TYPES = new Set(['checkbox', 'radio', 'button', 'submit', 'reset', 'image', 'file', 'color', 'range']);
9
+ const isEditableElement = (el) => {
10
+ if (el.isContentEditable)
11
+ return true;
12
+ if (el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement)
13
+ return true;
14
+ if (el instanceof HTMLInputElement)
15
+ return !NON_TEXT_INPUT_TYPES.has(el.type);
16
+ return false;
17
+ };
7
18
  // Componente riga virtualizzata memoizzato
8
19
  const VirtualRowComponent = memo(({ ariaAttributes, index, style, flattenedItems, focusedItemKey, selectedItemKeys, allowMultipleSelection, hasVisibleItems, handleNodeToggle, handleNodeClick, handleCheckboxChange, hasPartialChildSelection, itemRender, onItemContextMenu }) => {
9
20
  const item = flattenedItems[index];
@@ -184,6 +195,9 @@ const TMTreeView = ({ dataSource = [], focusedItem, selectedItems = [], allowMul
184
195
  const handleKeyDown = (event) => {
185
196
  if (!focusedItem)
186
197
  return;
198
+ // Non rubare i tasti ai campi editabili (es. input di un pannello sovrapposto)
199
+ if (event.target instanceof HTMLElement && isEditableElement(event.target))
200
+ return;
187
201
  let newFocusedItem = null;
188
202
  let handled = false;
189
203
  switch (event.key) {
@@ -0,0 +1,12 @@
1
+ import React from 'react';
2
+ export interface ITMWaitPanelConfirmProps {
3
+ /** Deve stare sopra l'overlay del pannello: il manager passa il suo z-index + 1. */
4
+ zIndex?: number;
5
+ /** Conferma: l'operazione va interrotta. */
6
+ onYes?: () => void;
7
+ /** Annulla: l'operazione riprende. Usata anche dalla X. */
8
+ onNo?: () => void;
9
+ }
10
+ /** Il ref punta all'overlay, che il manager mostra e nasconde via `style.display`. */
11
+ declare const TMWaitPanelConfirm: React.ForwardRefExoticComponent<ITMWaitPanelConfirmProps & React.RefAttributes<HTMLDivElement>>;
12
+ export default TMWaitPanelConfirm;
@@ -0,0 +1,185 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { forwardRef, useRef } from 'react';
3
+ import styled, { keyframes } from 'styled-components';
4
+ import toppy from '../../../assets/Toppy-generico.png';
5
+ import { AppModules, SDK_Globals } from '@topconsultnpm/sdk-ts';
6
+ import { getAppModuleGradient, SDKUI_Localizator } from '../../../helper';
7
+ /** Modale di conferma dell'interruzione, sopra al wait panel: non usa TMMessageBox perche' deve stare sopra gli overlay DevExtreme. */
8
+ /** Gradiente dell'applicazione per la barra del titolo. SDK_Globals.tmSession lancia senza sessione. */
9
+ const getTitleBarGradient = () => {
10
+ let appModuleID = AppModules.SURFER;
11
+ try {
12
+ appModuleID = SDK_Globals.tmSession?.SessionDescr?.appModuleID ?? AppModules.SURFER;
13
+ }
14
+ catch { /* nessuna sessione: resta il default */ }
15
+ return getAppModuleGradient(appModuleID);
16
+ };
17
+ const fadeIn = keyframes `
18
+ from { opacity: 0; }
19
+ to { opacity: 1; }
20
+ `;
21
+ const StyledConfirmOverlay = styled.div `
22
+ display: none;
23
+ position: fixed;
24
+ inset: 0;
25
+ z-index: ${props => props.$zIndex};
26
+ align-items: center;
27
+ justify-content: center;
28
+
29
+ &::before {
30
+ content: '';
31
+ position: absolute;
32
+ inset: 0;
33
+ background-color: rgba(17, 24, 39, 0.35);
34
+ animation: ${fadeIn} 120ms ease-out;
35
+ }
36
+ `;
37
+ const StyledConfirmCard = styled.div `
38
+ position: relative;
39
+ display: flex;
40
+ flex-direction: column;
41
+ width: 420px;
42
+ max-width: calc(100% - 24px);
43
+ max-height: calc(100% - 24px);
44
+ overflow-y: auto;
45
+ box-sizing: border-box;
46
+ background: #ffffff;
47
+ border-radius: 16px;
48
+ box-shadow: 0 18px 48px rgba(16, 24, 40, 0.28);
49
+ overflow: hidden; /* tiene la barra del titolo dentro il raggio della card */
50
+ font-family: inherit;
51
+ `;
52
+ const StyledTitleBar = styled.div `
53
+ display: flex;
54
+ flex-direction: row;
55
+ align-items: center;
56
+ justify-content: space-between;
57
+ gap: 8px;
58
+ padding: 6px 8px 6px 14px;
59
+ background: ${props => props.$gradient};
60
+ color: #ffffff;
61
+ cursor: move;
62
+ user-select: none;
63
+ touch-action: none; /* necessario perche' i pointer event di drag arrivino su touch */
64
+ `;
65
+ const StyledTitle = styled.div `
66
+ font-size: 14px;
67
+ font-weight: 600;
68
+ white-space: nowrap;
69
+ overflow: hidden;
70
+ text-overflow: ellipsis;
71
+ `;
72
+ const StyledCloseButton = styled.button `
73
+ display: flex;
74
+ align-items: center;
75
+ justify-content: center;
76
+ width: 26px;
77
+ height: 26px;
78
+ flex: 0 0 auto;
79
+ padding: 0;
80
+ border: none;
81
+ border-radius: 6px;
82
+ background: transparent;
83
+ color: inherit;
84
+ cursor: pointer;
85
+
86
+ &:hover { background: rgba(255, 255, 255, 0.18); }
87
+ &:active { background: rgba(255, 255, 255, 0.28); }
88
+ `;
89
+ const StyledBody = styled.div `
90
+ display: flex;
91
+ flex-direction: row;
92
+ align-items: center;
93
+ gap: 16px;
94
+ padding: 20px 24px 0 24px;
95
+ `;
96
+ const StyledToppyClip = styled.div `
97
+ height: 61px;
98
+ width: 68px;
99
+ overflow: hidden;
100
+ flex: 0 0 auto;
101
+ `;
102
+ const StyledToppy = styled.img `
103
+ display: block;
104
+ width: 68px;
105
+ height: 79px;
106
+ user-select: none;
107
+ -webkit-user-drag: none;
108
+ `;
109
+ const StyledQuestion = styled.div `
110
+ flex: 1;
111
+ min-width: 0;
112
+ font-size: 17px;
113
+ font-weight: 600;
114
+ color: #1f2937;
115
+ line-height: 1.3;
116
+ overflow-wrap: anywhere;
117
+ `;
118
+ const StyledFooter = styled.div `
119
+ display: flex;
120
+ flex-direction: row;
121
+ justify-content: flex-end;
122
+ gap: 10px;
123
+ padding: 18px 24px;
124
+ `;
125
+ const StyledButton = styled.button `
126
+ min-width: 92px;
127
+ padding: 9px 18px;
128
+ border-radius: 10px;
129
+ font-size: 13px;
130
+ font-family: inherit;
131
+ cursor: pointer;
132
+ transition: background-color 120ms ease, border-color 120ms ease;
133
+
134
+ border: 1px solid ${props => (props.$primary ? '#b3172f' : '#d5dbe5')};
135
+ background: ${props => (props.$primary ? '#c81e39' : '#ffffff')};
136
+ color: ${props => (props.$primary ? '#ffffff' : '#374151')};
137
+
138
+ &:hover { background: ${props => (props.$primary ? '#b3172f' : '#f3f5f9')}; }
139
+ &:active { transform: translateY(1px); }
140
+ `;
141
+ const IconClose = () => (_jsxs("svg", { width: '15', height: '15', viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: '2.4', strokeLinecap: 'round', children: [_jsx("line", { x1: '18', y1: '6', x2: '6', y2: '18' }), _jsx("line", { x1: '6', y1: '6', x2: '18', y2: '18' })] }));
142
+ /** Il ref punta all'overlay, che il manager mostra e nasconde via `style.display`. */
143
+ const TMWaitPanelConfirm = forwardRef(({ zIndex = 2147483602, onYes, onNo }, ref) => {
144
+ const cardRef = useRef(null);
145
+ // Scostamento fra il puntatore e l'angolo della card, fissato al primo pointerdown.
146
+ const grabRef = useRef(null);
147
+ const handlePointerDown = (e) => {
148
+ const card = cardRef.current;
149
+ if (!card || e.button !== 0)
150
+ return;
151
+ // Niente drag dalla X: con la pointer capture il click finirebbe sulla barra e non sul bottone.
152
+ if (e.target.closest('button'))
153
+ return;
154
+ // La card nasce centrata dal flex: al primo drag passo a coordinate esplicite.
155
+ const rect = card.getBoundingClientRect();
156
+ card.style.position = 'absolute';
157
+ card.style.margin = '0';
158
+ // Pixel interi: una posizione frazionaria fa ricampionare Toppy e lo sgrana.
159
+ card.style.left = `${Math.round(rect.left)}px`;
160
+ card.style.top = `${Math.round(rect.top)}px`;
161
+ grabRef.current = { dx: e.clientX - rect.left, dy: e.clientY - rect.top };
162
+ e.currentTarget.setPointerCapture(e.pointerId);
163
+ };
164
+ const handlePointerMove = (e) => {
165
+ const grab = grabRef.current;
166
+ const card = cardRef.current;
167
+ if (!grab || !card)
168
+ return;
169
+ // Clamp al viewport: una volta uscita non si riuscirebbe piu' ad afferrarla.
170
+ const maxLeft = Math.max(0, window.innerWidth - card.offsetWidth);
171
+ const maxTop = Math.max(0, window.innerHeight - card.offsetHeight);
172
+ card.style.left = `${Math.round(Math.min(Math.max(0, e.clientX - grab.dx), maxLeft))}px`;
173
+ card.style.top = `${Math.round(Math.min(Math.max(0, e.clientY - grab.dy), maxTop))}px`;
174
+ };
175
+ const handlePointerUp = (e) => {
176
+ if (!grabRef.current)
177
+ return;
178
+ grabRef.current = null;
179
+ if (e.currentTarget.hasPointerCapture(e.pointerId))
180
+ e.currentTarget.releasePointerCapture(e.pointerId);
181
+ };
182
+ return (_jsx(StyledConfirmOverlay, { ref: ref, $zIndex: zIndex, children: _jsxs(StyledConfirmCard, { ref: cardRef, role: 'alertdialog', children: [_jsxs(StyledTitleBar, { $gradient: getTitleBarGradient(), onPointerDown: handlePointerDown, onPointerMove: handlePointerMove, onPointerUp: handlePointerUp, onPointerCancel: handlePointerUp, children: [_jsx(StyledTitle, { children: SDKUI_Localizator.Abort }), _jsx(StyledCloseButton, { type: 'button', "aria-label": SDKUI_Localizator.No, onClick: onNo, children: _jsx(IconClose, {}) })] }), _jsxs(StyledBody, { children: [_jsx(StyledToppyClip, { children: _jsx(StyledToppy, { src: toppy, alt: '', draggable: false }) }), _jsx(StyledQuestion, { children: SDKUI_Localizator.Abort_Confirm })] }), _jsxs(StyledFooter, { children: [_jsx(StyledButton, { type: 'button', $primary: true, onClick: onYes, children: SDKUI_Localizator.Yes }), _jsx(StyledButton, { type: 'button', onClick: onNo, children: SDKUI_Localizator.No })] })] }) }));
183
+ });
184
+ TMWaitPanelConfirm.displayName = 'TMWaitPanelConfirm';
185
+ export default TMWaitPanelConfirm;