@topconsultnpm/sdkui-react 6.22.0-dev3.9 → 6.22.0-dev4.11
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.
- package/lib/components/base/TMAlert.js +6 -8
- package/lib/components/base/TMAreaManager.js +53 -55
- package/lib/components/base/TMButton.js +4 -2
- package/lib/components/base/TMCounterContainer.d.ts +3 -1
- package/lib/components/base/TMCounterContainer.js +16 -1
- package/lib/components/base/TMCustomButton.js +40 -48
- package/lib/components/base/TMDataGrid.js +24 -4
- package/lib/components/base/TMPanel.js +173 -170
- package/lib/components/base/TMSpinner.d.ts +2 -1
- package/lib/components/base/TMSpinner.js +9 -7
- package/lib/components/base/TMTabControl.d.ts +5 -0
- package/lib/components/base/TMTabControl.js +18 -7
- package/lib/components/base/TMTreeView.js +20 -0
- package/lib/components/base/TMWaitPanel.js +1 -1
- package/lib/components/base/waitPanel/TMWaitPanelConfirm.d.ts +12 -0
- package/lib/components/base/waitPanel/TMWaitPanelConfirm.js +185 -0
- package/lib/components/base/waitPanel/TMWaitPanelManager.d.ts +179 -0
- package/lib/components/base/waitPanel/TMWaitPanelManager.js +373 -0
- package/lib/components/base/waitPanel/TMWaitPanelView.d.ts +58 -0
- package/lib/components/base/waitPanel/TMWaitPanelView.js +346 -0
- package/lib/components/base/waitPanel/index.d.ts +1 -0
- package/lib/components/base/waitPanel/index.js +1 -0
- package/lib/components/choosers/TMDistinctValues.js +3 -3
- package/lib/components/choosers/TMPathChooser.js +17 -34
- package/lib/components/editors/TMTextBox.d.ts +4 -0
- package/lib/components/editors/TMTextBox.js +99 -11
- package/lib/components/features/archive/TMArchive.js +7 -2
- package/lib/components/features/assistant/TMHelpOnLineChat.d.ts +28 -0
- package/lib/components/features/assistant/TMHelpOnLineChat.js +1231 -0
- package/lib/components/features/assistant/TMToppyDraggableHelpCenter.js +2 -2
- package/lib/components/features/documents/TMBatchUpdateForm.js +3 -4
- package/lib/components/features/documents/TMCopyToFolderForm.js +53 -84
- package/lib/components/features/documents/TMDcmtForm.js +50 -61
- package/lib/components/features/documents/TMDcmtIcon.d.ts +1 -3
- package/lib/components/features/documents/TMDcmtIcon.js +3 -5
- package/lib/components/features/documents/TMDcmtPreview.js +5 -3
- package/lib/components/features/documents/TMDcmtTasks.js +2 -9
- package/lib/components/features/documents/TMFileUploader.js +26 -26
- package/lib/components/features/documents/TMMasterDetailDcmts.d.ts +2 -0
- package/lib/components/features/documents/TMMasterDetailDcmts.js +53 -148
- package/lib/components/features/documents/TMMasterInfoFields.js +3 -2
- package/lib/components/features/documents/TMMergeToPdfForm.js +52 -56
- package/lib/components/features/documents/TMMultiMasterDetailDcmtsForm.js +14 -15
- package/lib/components/features/documents/TMMultiMasterDetailDcmtsUtils.d.ts +1 -1
- package/lib/components/features/documents/TMMultiMasterDetailDcmtsUtils.js +3 -3
- package/lib/components/features/documents/TMRelationViewer.d.ts +13 -14
- package/lib/components/features/documents/TMRelationViewer.js +206 -143
- package/lib/components/features/documents/relationTreeState.d.ts +32 -0
- package/lib/components/features/documents/relationTreeState.js +89 -0
- package/lib/components/features/search/TMSearch.js +3 -4
- package/lib/components/features/search/TMSearchQueryPanel.js +0 -4
- package/lib/components/features/search/TMSearchResult.d.ts +5 -0
- package/lib/components/features/search/TMSearchResult.js +51 -24
- package/lib/components/features/search/TMTreeSelector.js +23 -27
- package/lib/components/features/search/TMViewHistoryDcmt.js +15 -26
- package/lib/components/features/tasks/TMTasksView.js +97 -90
- package/lib/components/features/wg/TMWGsCopyMoveForm.js +104 -100
- package/lib/components/forms/TMResultDialog.js +1 -1
- package/lib/components/grids/TMBlogsPost.js +93 -97
- package/lib/components/index.d.ts +5 -1
- package/lib/components/index.js +4 -1
- package/lib/components/layout/panelManager/TMPanelManagerContainer.js +16 -15
- package/lib/components/layout/panelManager/TMPanelManagerContext.d.ts +1 -0
- package/lib/components/layout/panelManager/TMPanelManagerContext.js +5 -1
- package/lib/components/layout/panelManager/TMPanelManagerLoader.d.ts +6 -0
- package/lib/components/layout/panelManager/TMPanelManagerLoader.js +18 -0
- package/lib/components/layout/panelManager/TMPanelManagerToolbar.js +4 -8
- package/lib/components/layout/panelManager/TMPanelManagerWithPersistenceProvider.d.ts +1 -1
- package/lib/components/layout/panelManager/TMPanelManagerWithPersistenceProvider.js +2 -1
- package/lib/components/pages/TMPage.js +32 -31
- package/lib/components/query/TMQueryEditor.d.ts +3 -0
- package/lib/components/query/TMQueryEditor.js +2 -1
- package/lib/components/sidebar/TMHeader.d.ts +1 -0
- package/lib/components/sidebar/TMHeader.js +2 -2
- package/lib/helper/Enum_Localizator.js +1 -0
- package/lib/helper/SDKUI_Globals.d.ts +7 -2
- package/lib/helper/SDKUI_Globals.js +7 -2
- package/lib/helper/SDKUI_Localizator.d.ts +31 -6
- package/lib/helper/SDKUI_Localizator.js +301 -51
- package/lib/helper/ZipManager.d.ts +1 -1
- package/lib/helper/ZipManager.js +2 -1
- package/lib/helper/checkinCheckoutManager.js +2 -3
- package/lib/helper/dcmtsHelper.d.ts +2 -0
- package/lib/helper/dcmtsHelper.js +3 -1
- package/lib/hooks/useCheckInOutOperations.d.ts +0 -6
- package/lib/hooks/useCheckInOutOperations.js +16 -37
- package/lib/hooks/useDcmtOperations.d.ts +3 -19
- package/lib/hooks/useDcmtOperations.js +175 -229
- package/lib/hooks/useDocumentOperations.d.ts +1 -0
- package/lib/hooks/useDocumentOperations.js +16 -29
- package/lib/hooks/useMultiMasterDetailDcmts.d.ts +0 -15
- package/lib/hooks/useMultiMasterDetailDcmts.js +70 -68
- package/lib/hooks/usePreventFileDrop.js +9 -0
- package/lib/hooks/useWorkflowApprove.js +33 -17
- package/lib/services/caseflow/CaseFlowService.d.ts +69 -9
- package/lib/services/caseflow/CaseFlowService.js +126 -58
- package/package.json +2 -2
|
@@ -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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
435
|
-
|
|
436
|
-
|
|
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
|
-
|
|
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
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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)}
|
|
@@ -246,13 +246,15 @@ const TMButton = forwardRef((props, ref) => {
|
|
|
246
246
|
const [dropdownPos, setDropdownPos] = useState({ left: 0, top: 0, minWidth: 200 });
|
|
247
247
|
const splitContainerRef = useRef(null);
|
|
248
248
|
const dropdownRef = useRef(null);
|
|
249
|
+
const splitArrowRef = useRef(null);
|
|
249
250
|
const hasSecondaryActions = !!secondaryActions && secondaryActions.length > 0;
|
|
250
251
|
useEffect(() => {
|
|
251
252
|
if (!isDropdownOpen)
|
|
252
253
|
return;
|
|
253
254
|
const handlePointerDown = (e) => {
|
|
254
255
|
const target = e.target;
|
|
255
|
-
|
|
256
|
+
// Il pulsante principale chiude il menu: la sua azione non deve lasciarlo aperto
|
|
257
|
+
if (splitArrowRef.current?.contains(target) || dropdownRef.current?.contains(target))
|
|
256
258
|
return;
|
|
257
259
|
setIsDropdownOpen(false);
|
|
258
260
|
};
|
|
@@ -301,7 +303,7 @@ const TMButton = forwardRef((props, ref) => {
|
|
|
301
303
|
const buttonWithTooltip = showTooltip
|
|
302
304
|
? _jsx(TMTooltip, { hideAfterDelay: true, content: renderTooltip(), children: renderedButton() })
|
|
303
305
|
: renderedButton();
|
|
304
|
-
const renderSplitButton = () => (_jsxs(StyledSplitContainer, { ref: splitContainerRef, children: [buttonWithTooltip, _jsx(StyledSplitArrow, { type: 'button', $isDisabled: disabled, $isOpen: isDropdownOpen, "aria-haspopup": 'menu', "aria-expanded": isDropdownOpen, onClick: (e) => { e.stopPropagation(); toggleDropdown(); }, onMouseDown: (e) => e.stopPropagation(), children: _jsx(IconChevronDown, { fontSize: 14 }) }), isDropdownOpen && createPortal(_jsx(StyledDropdown, { ref: dropdownRef, role: 'menu', $left: dropdownPos.left, $top: dropdownPos.top, $minWidth: dropdownPos.minWidth, children: secondaryActions.map((action, idx) => (_jsxs(StyledDropdownItem, { type: 'button', role: 'menuitem', $disabled: action.disabled, disabled: action.disabled, onClick: () => {
|
|
306
|
+
const renderSplitButton = () => (_jsxs(StyledSplitContainer, { ref: splitContainerRef, children: [buttonWithTooltip, _jsx(StyledSplitArrow, { ref: splitArrowRef, type: 'button', $isDisabled: disabled, $isOpen: isDropdownOpen, "aria-haspopup": 'menu', "aria-expanded": isDropdownOpen, onClick: (e) => { e.stopPropagation(); toggleDropdown(); }, onMouseDown: (e) => e.stopPropagation(), children: _jsx(IconChevronDown, { fontSize: 14 }) }), isDropdownOpen && createPortal(_jsx(StyledDropdown, { ref: dropdownRef, role: 'menu', $left: dropdownPos.left, $top: dropdownPos.top, $minWidth: dropdownPos.minWidth, children: secondaryActions.map((action, idx) => (_jsxs(StyledDropdownItem, { type: 'button', role: 'menuitem', $disabled: action.disabled, disabled: action.disabled, onClick: () => {
|
|
305
307
|
if (action.disabled)
|
|
306
308
|
return;
|
|
307
309
|
setIsDropdownOpen(false);
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
export declare const formatCount: (value: number | string | undefined) => string;
|
|
2
3
|
export declare enum CounterItemKey {
|
|
3
4
|
allItems = "allItems",
|
|
4
5
|
visibleItems = "visibleItems",
|
|
5
|
-
selectedItems = "selectedItems"
|
|
6
|
+
selectedItems = "selectedItems",
|
|
7
|
+
pageSize = "pageSize"
|
|
6
8
|
}
|
|
7
9
|
export interface ITMCounterItemData {
|
|
8
10
|
key?: string;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import styled from "styled-components";
|
|
3
3
|
import TMTooltip from './TMTooltip';
|
|
4
|
+
import { DeviceType, useDeviceType } from './TMDeviceProvider';
|
|
4
5
|
/* Styled container component that holds all counter items */
|
|
5
6
|
const Container = styled.div `
|
|
6
7
|
background: ${({ bgColor }) => bgColor || 'linear-gradient(to right, #f8f9fa, #e9ecef)'};
|
|
@@ -35,6 +36,14 @@ const Counter = styled.div `
|
|
|
35
36
|
transition: color 0.3s ease-in-out;
|
|
36
37
|
}
|
|
37
38
|
`;
|
|
39
|
+
/* Formatta un conteggio con il separatore delle migliaia della lingua del browser; i valori non numerici restano invariati.
|
|
40
|
+
useGrouping 'always' forza il separatore anche sotto le 10.000 unità, che in italiano e spagnolo di default non viene messo (2000 invece di 2.000).
|
|
41
|
+
Il cast serve perché la lib ES2021 tipizza useGrouping solo come boolean. */
|
|
42
|
+
export const formatCount = (value) => {
|
|
43
|
+
if (value === undefined || value === '' || isNaN(Number(value)))
|
|
44
|
+
return String(value ?? '');
|
|
45
|
+
return Number(value).toLocaleString(navigator.language, { useGrouping: 'always' });
|
|
46
|
+
};
|
|
38
47
|
/* Enum for possible counter item keys */
|
|
39
48
|
export var CounterItemKey;
|
|
40
49
|
(function (CounterItemKey) {
|
|
@@ -44,15 +53,21 @@ export var CounterItemKey;
|
|
|
44
53
|
CounterItemKey["visibleItems"] = "visibleItems";
|
|
45
54
|
/* Key for selected item count */
|
|
46
55
|
CounterItemKey["selectedItems"] = "selectedItems";
|
|
56
|
+
/* Key for rows per page count */
|
|
57
|
+
CounterItemKey["pageSize"] = "pageSize";
|
|
47
58
|
})(CounterItemKey || (CounterItemKey = {}));
|
|
48
59
|
;
|
|
49
60
|
;
|
|
50
61
|
const TMCounterContainer = (props) => {
|
|
51
62
|
const { items, bgColorContainer, hoverColorItem, bgColorItem, textColorItem } = props;
|
|
63
|
+
// Su mobile lo spazio è poco: le righe per pagina non vengono mostrate
|
|
64
|
+
const isMobile = useDeviceType() === DeviceType.MOBILE;
|
|
52
65
|
return (items && _jsx(Container, { bgColor: bgColorContainer, children: [...items.values()].map((item) => {
|
|
53
66
|
if (!item.show)
|
|
54
67
|
return null;
|
|
55
|
-
|
|
68
|
+
if (isMobile && item.key === CounterItemKey.pageSize)
|
|
69
|
+
return null;
|
|
70
|
+
return _jsx(TMTooltip, { content: item.caption ?? '', children: _jsxs(Counter, { bgColor: bgColorContainer, color: bgColorItem, hoverColor: hoverColorItem, textColor: textColorItem, children: [item.icon ?? null, _jsx("span", { children: formatCount(item.value) })] }) }, item.key ?? '');
|
|
56
71
|
}) }));
|
|
57
72
|
};
|
|
58
73
|
export default TMCounterContainer;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import {
|
|
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
|
|
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,45 @@ const TMCustomButton = (props) => {
|
|
|
83
78
|
setLoading(false);
|
|
84
79
|
setError(true);
|
|
85
80
|
};
|
|
86
|
-
const executeSequentially = async (
|
|
81
|
+
const executeSequentially = async () => {
|
|
87
82
|
if (!itemsToProcess)
|
|
88
83
|
return;
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
84
|
+
const waitPanel = TMWaitPanelManager.show({
|
|
85
|
+
title: SDKUI_Localizator.CustomButtonAction,
|
|
86
|
+
max: itemsToProcess.length,
|
|
87
|
+
cancelable: true,
|
|
88
|
+
showCounters: false,
|
|
89
|
+
});
|
|
90
|
+
try {
|
|
91
|
+
for (const [index, item] of itemsToProcess.entries()) {
|
|
92
|
+
if (waitPanel.aborted)
|
|
93
|
+
break;
|
|
94
|
+
waitPanel.setSubtitle(SDKUI_Localizator.CustomButtonActions.replaceParams(index + 1, itemsToProcess.length));
|
|
95
|
+
// Attendi che l'iframe sia pronto e invia il messaggio
|
|
96
|
+
await new Promise((resolve) => {
|
|
97
|
+
const checkIframe = setInterval(() => {
|
|
98
|
+
if (iframeRef.current?.contentWindow) {
|
|
99
|
+
clearInterval(checkIframe);
|
|
100
|
+
if (selectedItems && selectedItems.length > 0) {
|
|
101
|
+
//devo convertire item in formData
|
|
102
|
+
const processedItem = getSelectedItem(args, formData, item);
|
|
103
|
+
postMessageIframe(processedItem);
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
postMessageIframe(item);
|
|
107
|
+
}
|
|
108
|
+
// Attendi prima di passare al prossimo
|
|
109
|
+
setTimeout(resolve, 500);
|
|
106
110
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
116
|
-
}, 100);
|
|
117
|
-
});
|
|
111
|
+
}, 100);
|
|
112
|
+
});
|
|
113
|
+
await waitPanel.step();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
finally {
|
|
117
|
+
waitPanel.close();
|
|
118
|
+
onClose?.();
|
|
118
119
|
}
|
|
119
|
-
setShowWaitPanel(false);
|
|
120
|
-
onClose?.();
|
|
121
120
|
};
|
|
122
121
|
const postMessageIframe = (data) => {
|
|
123
122
|
console.log("TMCustomButton - postMessageIframe - data:", data);
|
|
@@ -130,14 +129,7 @@ const TMCustomButton = (props) => {
|
|
|
130
129
|
return;
|
|
131
130
|
if (!RunOnce && itemsToProcess.length > 0) {
|
|
132
131
|
// esegui per ogni item selezionato
|
|
133
|
-
|
|
134
|
-
controller.signal.addEventListener('abort', () => {
|
|
135
|
-
setShowWaitPanel(false);
|
|
136
|
-
onClose?.();
|
|
137
|
-
});
|
|
138
|
-
setAbortController(controller);
|
|
139
|
-
setWaitPanelMaxValue(itemsToProcess.length);
|
|
140
|
-
executeSequentially(controller);
|
|
132
|
+
executeSequentially();
|
|
141
133
|
}
|
|
142
134
|
else {
|
|
143
135
|
// Modalità RunOnce: invia dati all'iframe quando è caricato
|
|
@@ -157,6 +149,6 @@ const TMCustomButton = (props) => {
|
|
|
157
149
|
}
|
|
158
150
|
}, []);
|
|
159
151
|
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 &&
|
|
152
|
+
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
153
|
};
|
|
162
154
|
export default TMCustomButton;
|
|
@@ -3,8 +3,8 @@ import React, { useCallback, useEffect, useImperativeHandle, useMemo, useRef, us
|
|
|
3
3
|
import styled, { createGlobalStyle, css } from 'styled-components';
|
|
4
4
|
import DataGrid, { Column, HeaderFilter, Selection, Scrolling, LoadPanel, SearchPanel, Pager, Sorting, Paging, FilterPanel, ColumnChooser, Grouping, GroupPanel, Summary, Editing, FilterRow, StateStoring, RowDragging, MasterDetail, Position, ColumnChooserSearch, ColumnChooserSelection } from 'devextreme-react/data-grid';
|
|
5
5
|
import DataSource from 'devextreme/data/data_source';
|
|
6
|
-
import { IconAll, IconSelected, IconVisible, SDKUI_Globals, SDKUI_Localizator } from '../../helper';
|
|
7
|
-
import TMCounterContainer, { CounterItemKey } from './TMCounterContainer';
|
|
6
|
+
import { IconAll, IconFormatPageSplit, IconSelected, IconVisible, SDKUI_Globals, SDKUI_Localizator } from '../../helper';
|
|
7
|
+
import TMCounterContainer, { CounterItemKey, formatCount } from './TMCounterContainer';
|
|
8
8
|
import TMContextMenu from '../NewComponents/ContextMenu/TMContextMenu';
|
|
9
9
|
import { useLongPress } from '../NewComponents/ContextMenu/useLongPress';
|
|
10
10
|
;
|
|
@@ -132,6 +132,9 @@ const TMDataGrid = React.forwardRef((props, ref) => {
|
|
|
132
132
|
setCustomContextMenuOnEmptyArea(false);
|
|
133
133
|
},
|
|
134
134
|
});
|
|
135
|
+
// Valori primitivi: paging di default viene ricreato a ogni render e non può stare tra le dipendenze dell'effetto
|
|
136
|
+
const pagingEnabled = !!paging && paging.enabled !== false;
|
|
137
|
+
const rowsPerPage = paging?.pageSize ?? pageSize;
|
|
135
138
|
// Creating a ref to store the timestamp of the last selection change
|
|
136
139
|
const lastSelectionChangeTime = useRef(Date.now());
|
|
137
140
|
useEffect(() => {
|
|
@@ -148,6 +151,11 @@ const TMDataGrid = React.forwardRef((props, ref) => {
|
|
|
148
151
|
[
|
|
149
152
|
CounterItemKey.selectedItems,
|
|
150
153
|
{ key: CounterItemKey.selectedItems, show: true, caption: SDKUI_Localizator.SelectedItems, value: selectedRowKeys.length, icon: _jsx(IconSelected, {}), order: 1 }
|
|
154
|
+
],
|
|
155
|
+
[
|
|
156
|
+
// Righe per pagina: visibile solo quando c'è il paginatore, cioè gli elementi non stanno in una pagina
|
|
157
|
+
CounterItemKey.pageSize,
|
|
158
|
+
{ key: CounterItemKey.pageSize, show: pagingEnabled && totalRecordCount > rowsPerPage, caption: SDKUI_Localizator.DocumentsPerPage, value: `${formatCount(rowsPerPage)} ${SDKUI_Localizator.PerPage}`, icon: _jsx(IconFormatPageSplit, {}), order: 2 }
|
|
151
159
|
]
|
|
152
160
|
]);
|
|
153
161
|
const mergedCounterItems = new Map(defaultCounterItems);
|
|
@@ -164,7 +172,7 @@ const TMDataGrid = React.forwardRef((props, ref) => {
|
|
|
164
172
|
}));
|
|
165
173
|
setCounterValues(sortedCounterItems);
|
|
166
174
|
}
|
|
167
|
-
}, [counterConfig, visibleItemsCount, selectedRowKeys]);
|
|
175
|
+
}, [counterConfig, visibleItemsCount, selectedRowKeys, pagingEnabled, rowsPerPage, totalRecordCount]);
|
|
168
176
|
// Handle selection changes and invoke callback if provided
|
|
169
177
|
const onSelectionChangedCallback = useCallback((e) => {
|
|
170
178
|
lastSelectionChangeTime.current = Date.now(); // Store current time of selection change
|
|
@@ -331,7 +339,8 @@ const TMDataGrid = React.forwardRef((props, ref) => {
|
|
|
331
339
|
if (!internalRef.current)
|
|
332
340
|
return;
|
|
333
341
|
// Update state with the current number of visible rows in the DataGrid
|
|
334
|
-
|
|
342
|
+
const grid = internalRef.current.instance();
|
|
343
|
+
setVisibleItemsCount(grid ? getVisibleItemsCount(grid) : 0);
|
|
335
344
|
// Focusing SearchPanel on content ready - solo se non è già stato fatto
|
|
336
345
|
if (showSearchPanel && searchPanelFocusTrigger && searchPanelFocusTrigger > 0 && !searchPanelFocusedRef.current) {
|
|
337
346
|
// Use a small delay to ensure the DOM is fully rendered before trying to focus
|
|
@@ -667,3 +676,14 @@ const getRecordCount = (dataSource) => {
|
|
|
667
676
|
}
|
|
668
677
|
return 0; // Default case when neither condition is met
|
|
669
678
|
};
|
|
679
|
+
/* Righe della pagina corrente: una per gruppo più le sue righe dati, che non contano se il gruppo è chiuso.
|
|
680
|
+
Stesso criterio di DevExtreme: un gruppo ha la proprietà items, valorizzata a null quando è chiuso. */
|
|
681
|
+
const countPageRows = (items) => items.reduce((count, item) => count + (item.items === undefined ? 1 : 1 + countPageRows(item.items ?? [])), 0);
|
|
682
|
+
/* Con il rendering virtuale getVisibleRows restituisce solo le righe disegnate a video, non tutte quelle della pagina:
|
|
683
|
+
in quel caso le righe si contano dagli elementi caricati per la pagina corrente. */
|
|
684
|
+
const getVisibleItemsCount = (grid) => {
|
|
685
|
+
if (grid.option('scrolling.rowRenderingMode') === 'virtual') {
|
|
686
|
+
return countPageRows(grid.getDataSource()?.items() ?? []);
|
|
687
|
+
}
|
|
688
|
+
return grid.getVisibleRows()?.length ?? 0;
|
|
689
|
+
};
|