@topconsultnpm/sdkui-react-beta 6.14.136 → 6.14.138

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.
@@ -0,0 +1,13 @@
1
+ interface ShowConfirmOptions {
2
+ message: string;
3
+ title: string;
4
+ okButtonText?: string;
5
+ cancelButtonText?: string;
6
+ }
7
+ /**
8
+ * Mostra una finestra di conferma personalizzata tramite un componente modale.
9
+ * @param options Opzioni per la finestra di conferma (messaggio, titolo, testi dei bottoni).
10
+ * @returns Una Promise che si risolve a `true` se l'utente conferma, `false` se annulla.
11
+ */
12
+ export declare const ShowConfirm: (options: ShowConfirmOptions) => Promise<boolean>;
13
+ export {};
@@ -0,0 +1,119 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useEffect } from 'react';
3
+ import ReactDOM from 'react-dom/client';
4
+ import { createPortal } from 'react-dom';
5
+ import styled from 'styled-components';
6
+ import TMModal from './TMModal';
7
+ import TMButton from './TMButton';
8
+ import Toppy from '../../assets/Toppy-generico.png';
9
+ import { TMColors } from '../../utils/theme';
10
+ // *** Stili per la modale (immutati) ***
11
+ const StyledModalBodyWrapper = styled.div `
12
+ display: flex;
13
+ flex-direction: column;
14
+ height: 100%;
15
+ `;
16
+ const StyledModalFooter = styled.div `
17
+ padding: 12px;
18
+ height: auto;
19
+ display: flex;
20
+ justify-content: flex-end;
21
+ flex-direction: row;
22
+ gap: 10px;
23
+ `;
24
+ const StyledModalContentContainer = styled.div `
25
+ width: 100%;
26
+ padding: 10px;
27
+ flex: 1;
28
+ overflow-y: auto;
29
+ display: flex;
30
+ flex-direction: row;
31
+ align-items: center;
32
+ gap: 10px;
33
+ `;
34
+ // *** Componente della modale di conferma ***
35
+ const TMConfirmModal = ({ isOpen, title, message, okButtonText = 'OK', cancelButtonText = 'Annulla', onConfirm, onCancel, onClose }) => {
36
+ // Stato per la larghezza e l'altezza della modale
37
+ const [modalWidth, setModalWidth] = useState('520px');
38
+ const [modalHeight, setModalHeight] = useState('240px');
39
+ useEffect(() => {
40
+ const handleResize = () => {
41
+ if (window.innerWidth <= 768) {
42
+ setModalWidth('300px');
43
+ setModalHeight('200px');
44
+ }
45
+ else {
46
+ setModalWidth('520px');
47
+ setModalHeight('240px');
48
+ }
49
+ };
50
+ // Imposta la dimensione iniziale
51
+ handleResize();
52
+ // Aggiungi il listener per l'evento di ridimensionamento della finestra
53
+ window.addEventListener('resize', handleResize);
54
+ // Rimuovi il listener quando il componente viene smontato
55
+ return () => window.removeEventListener('resize', handleResize);
56
+ }, []);
57
+ return (_jsx(TMModal, { title: title, width: modalWidth, height: modalHeight, resizable: false, onClose: onClose, children: _jsxs(StyledModalBodyWrapper, { children: [_jsxs(StyledModalContentContainer, { children: [_jsx("img", { width: 60, height: 75, src: Toppy, alt: "Toppy" }), _jsx("p", { style: { color: TMColors.primaryColor }, children: message })] }), _jsxs(StyledModalFooter, { children: [_jsx(TMButton, { btnStyle: 'text', showTooltip: false, caption: okButtonText, onClick: onConfirm }), _jsx(TMButton, { btnStyle: 'text', showTooltip: false, caption: cancelButtonText, onClick: onCancel })] })] }) }));
58
+ };
59
+ // *** Variabili globali per la gestione della Promise e del root dinamico ***
60
+ let resolvePromise = null;
61
+ let confirmRoot = null;
62
+ let portalContainer = null;
63
+ /**
64
+ * Funzione interna per creare/aggiornare/rimuovere la modale tramite un Portal.
65
+ * Questa non è esportata e viene gestita solo da `ShowConfirm`.
66
+ */
67
+ const updateConfirmModal = (options, isOpen, onConfirm, onCancel, onClose) => {
68
+ if (!portalContainer) {
69
+ // Crea il div in cui il portal verrà renderizzato la prima volta
70
+ portalContainer = document.createElement('div');
71
+ portalContainer.id = 'confirm-modal-portal-root';
72
+ document.body.appendChild(portalContainer);
73
+ confirmRoot = ReactDOM.createRoot(portalContainer);
74
+ }
75
+ if (isOpen && options && confirmRoot) {
76
+ // Renderizza il componente usando un Portal
77
+ confirmRoot.render(createPortal(_jsx(TMConfirmModal, { isOpen: isOpen, title: options.title, message: options.message, okButtonText: options.okButtonText, cancelButtonText: options.cancelButtonText, onConfirm: onConfirm, onCancel: onCancel, onClose: onClose }), portalContainer));
78
+ }
79
+ else if (confirmRoot) {
80
+ // Se non è più aperto, renderizza null per smontare il componente dal portal
81
+ confirmRoot.render(null);
82
+ // È buona pratica pulire anche il container dal DOM se non è più necessario
83
+ if (portalContainer && portalContainer.parentNode) {
84
+ confirmRoot.unmount(); // Smonta il root completamente
85
+ portalContainer.parentNode.removeChild(portalContainer);
86
+ portalContainer = null;
87
+ confirmRoot = null;
88
+ }
89
+ }
90
+ };
91
+ /**
92
+ * Mostra una finestra di conferma personalizzata tramite un componente modale.
93
+ * @param options Opzioni per la finestra di conferma (messaggio, titolo, testi dei bottoni).
94
+ * @returns Una Promise che si risolve a `true` se l'utente conferma, `false` se annulla.
95
+ */
96
+ export const ShowConfirm = (options) => {
97
+ return new Promise((resolve, reject) => {
98
+ if (resolvePromise) {
99
+ // Se c'è già una modale di conferma aperta, rifiuta la nuova richiesta.
100
+ // Potresti voler mettere in coda le richieste o gestirle diversamente.
101
+ reject(new Error('Another confirm modal is already open.'));
102
+ return;
103
+ }
104
+ resolvePromise = resolve;
105
+ const cleanupAndResolve = (value) => {
106
+ if (resolvePromise) {
107
+ resolvePromise(value);
108
+ resolvePromise = null;
109
+ // Chiudi e pulisci la modale
110
+ updateConfirmModal(null, false, () => { }, () => { }, () => { });
111
+ }
112
+ };
113
+ const handleConfirm = () => cleanupAndResolve(true);
114
+ const handleCancel = () => cleanupAndResolve(false);
115
+ const handleClose = () => cleanupAndResolve(false); // Gestione della chiusura tramite 'X' o ESC
116
+ // Mostra la modale
117
+ updateConfirmModal(options, true, handleConfirm, handleCancel, handleClose);
118
+ });
119
+ };
@@ -19,8 +19,9 @@ interface ITMTreeViewProps<T extends ITMTreeItem> {
19
19
  onSelectionChanged?: (selectedItems: T[]) => void;
20
20
  onNodeUpdate?: (updatedNode: T) => void;
21
21
  onDataChanged?: (items: T[]) => void;
22
+ shouldDelayFocusOnEvent?: (node: T, event: React.MouseEvent) => boolean;
22
23
  }
23
- declare const TMTreeView: <T extends ITMTreeItem>({ dataSource, focusedItem, selectedItems, allowMultipleSelection, onDataChanged, calculateItemsForNode, itemRender, onNodeUpdate, onFocusedItemChanged, onSelectionChanged }: ITMTreeViewProps<T>) => import("react/jsx-runtime").JSX.Element;
24
+ declare const TMTreeView: <T extends ITMTreeItem>({ dataSource, focusedItem, selectedItems, allowMultipleSelection, onDataChanged, calculateItemsForNode, itemRender, onNodeUpdate, onFocusedItemChanged, onSelectionChanged, shouldDelayFocusOnEvent }: ITMTreeViewProps<T>) => import("react/jsx-runtime").JSX.Element;
24
25
  export default TMTreeView;
25
26
  export declare const StyledTreeNode: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components/dist/types").Substitute<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, {
26
27
  $isSelected?: boolean;
@@ -1,9 +1,9 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useCallback, useEffect } from 'react';
2
+ import { useCallback, useEffect, useRef } from 'react';
3
3
  import styled from 'styled-components';
4
4
  import { IconChevronDown, IconChevronRight } from '../../helper';
5
5
  import { TMColors } from '../../utils/theme';
6
- const TMTreeView = ({ dataSource = [], focusedItem, selectedItems = [], allowMultipleSelection, onDataChanged, calculateItemsForNode, itemRender, onNodeUpdate, onFocusedItemChanged, onSelectionChanged }) => {
6
+ const TMTreeView = ({ dataSource = [], focusedItem, selectedItems = [], allowMultipleSelection, onDataChanged, calculateItemsForNode, itemRender, onNodeUpdate, onFocusedItemChanged, onSelectionChanged, shouldDelayFocusOnEvent }) => {
7
7
  useEffect(() => {
8
8
  const handleKeyDown = (event) => {
9
9
  if (!focusedItem)
@@ -102,10 +102,53 @@ const TMTreeView = ({ dataSource = [], focusedItem, selectedItems = [], allowMul
102
102
  onDataChanged?.(updatedData);
103
103
  });
104
104
  };
105
- const handleNodeClick = (node, ctrlKey) => {
106
- onFocusedItemChanged?.(node);
107
- handleNodeToggle(node.key, ctrlKey);
108
- };
105
+ // Ref per distinguere click singolo/doppio sulla riga (esempio TMDcmtIcon)
106
+ const clickTimeoutRef = useRef(null);
107
+ const lastClickedNodeKeyRef = useRef(undefined);
108
+ const handleNodeClick = useCallback((node, e) => {
109
+ const targetElement = e.target;
110
+ // Se il click non è sulla riga (es. click sul paddiing esterno), ignora.
111
+ if (e.currentTarget !== targetElement && !e.currentTarget.contains(targetElement)) {
112
+ return;
113
+ }
114
+ // Se shouldDelayFocusOnEvent non è fornita, il comportamento predefinito è il focus immediato (false)
115
+ const delayFocus = shouldDelayFocusOnEvent ? shouldDelayFocusOnEvent(node, e) : false;
116
+ // Reset del timer se il click è su un nodo diverso dal precedente
117
+ if (lastClickedNodeKeyRef.current && lastClickedNodeKeyRef.current !== node.key) {
118
+ if (clickTimeoutRef.current) {
119
+ clearTimeout(clickTimeoutRef.current);
120
+ }
121
+ clickTimeoutRef.current = null;
122
+ }
123
+ if (delayFocus) {
124
+ // Logica per il ritardo del focus (es. click singolo su icona desktop, gestito dal padre)
125
+ if (clickTimeoutRef.current) {
126
+ // Questo è il secondo click di un potenziale doppio click.
127
+ // Annulla l'azione di focus dal click singolo precedente.
128
+ clearTimeout(clickTimeoutRef.current);
129
+ clickTimeoutRef.current = null;
130
+ lastClickedNodeKeyRef.current = undefined;
131
+ return; // Non attivare onFocusedItemChanged per il doppio click
132
+ }
133
+ // Primo click di un potenziale doppio click o un click singolo che necessita di ritardo
134
+ lastClickedNodeKeyRef.current = node.key;
135
+ clickTimeoutRef.current = setTimeout(() => {
136
+ // Se il timer scade, significa che è stato solo un click singolo.
137
+ onFocusedItemChanged?.(node);
138
+ clickTimeoutRef.current = null;
139
+ lastClickedNodeKeyRef.current = undefined;
140
+ }, 200);
141
+ }
142
+ else {
143
+ // Logica per il focus immediato (per tutti gli altri click, o se shouldDelayFocusOnEvent è false)
144
+ if (clickTimeoutRef.current) {
145
+ clearTimeout(clickTimeoutRef.current);
146
+ clickTimeoutRef.current = null;
147
+ lastClickedNodeKeyRef.current = undefined;
148
+ }
149
+ onFocusedItemChanged?.(node); // Chiama onFocusedItemChanged immediatamente
150
+ }
151
+ }, [onFocusedItemChanged, calculateItemsForNode, onNodeUpdate]);
109
152
  const handleCheckboxChange = (node, checked) => {
110
153
  let newSelectedItems;
111
154
  if (checked) {
@@ -179,7 +222,7 @@ const TMTreeView = ({ dataSource = [], focusedItem, selectedItems = [], allowMul
179
222
  if (input) {
180
223
  input.indeterminate = isIndeterminate(node);
181
224
  }
182
- } })), _jsx("div", { style: { display: 'flex', alignItems: 'center', width: '100%', height: '100%' }, onClick: () => { onFocusedItemChanged?.(node); }, children: itemRender(node) })] }), node.expanded && node.items && (_jsx("div", { style: { paddingLeft: 20, width: '100%' }, children: renderTree(node.items) }))] }, node.key)));
225
+ } })), _jsx("div", { style: { display: 'flex', alignItems: 'center', width: '100%', height: '100%' }, onClick: (e) => { handleNodeClick(node, e); }, children: itemRender(node) })] }), node.expanded && node.items && (_jsx("div", { style: { paddingLeft: 20, width: '100%' }, children: renderTree(node.items) }))] }, node.key)));
183
226
  }, [handleNodeClick, handleNodeToggle, handleCheckboxChange, focusedItem, selectedItems, allowMultipleSelection]);
184
227
  return (_jsx("div", { style: { height: '100%', width: '100%', overflowY: 'auto', padding: '2px 5px 2px 2px' }, children: renderTree(dataSource) }));
185
228
  };
@@ -1,10 +1,12 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useMemo } from 'react';
2
+ import { useCallback, useMemo } from 'react';
3
3
  import styled from 'styled-components';
4
4
  import { getFileIcon } from '../../../helper';
5
5
  import TMTooltip from '../../base/TMTooltip';
6
6
  import { DownloadTypes } from '../../../ts';
7
7
  import { DeviceType, useDeviceType } from '../../base/TMDeviceProvider';
8
+ import { useDcmtOperations } from '../../../hooks/useDcmtOperations';
9
+ import { TMWaitPanel } from '../../base/TMWaitPanel';
8
10
  const StyledCellRenderDcmtIcon = styled.div `
9
11
  display: flex;
10
12
  flex-direction: row;
@@ -14,24 +16,40 @@ const StyledCellRenderDcmtIcon = styled.div `
14
16
  position: relative;
15
17
  `;
16
18
  const TMDcmtIcon = ({ fileExtension, fileCount, isLexProt, isSigned, isMail, isShared, tid, did, downloadMode = "none", onDownloadDcmtsAsync }) => {
19
+ const { downloadDcmtsAsync, showWaitPanel, waitPanelTitle, showSecondary, waitPanelTextSecondary, waitPanelValueSecondary, waitPanelMaxValueSecondary, abortController } = useDcmtOperations();
17
20
  const deviceType = useDeviceType();
18
21
  let isMobile = useMemo(() => { return deviceType === DeviceType.MOBILE; }, [deviceType]);
22
+ const icon = useMemo(() => {
23
+ return getFileIcon(isMail == 2 ? "PEC" : fileExtension, fileCount);
24
+ }, [fileExtension, fileCount, isMail]);
25
+ // Gestore unificato per l'azione di download (click singolo su mobile, doppio click su desktop)
26
+ const handleDownloadAction = useCallback(async (e) => {
27
+ e.stopPropagation();
28
+ e.preventDefault();
29
+ if (tid && did) {
30
+ let dcmt = [{ TID: tid, DID: did, FILEEXT: fileExtension }];
31
+ // Usa la funzione passata tramite prop se disponibile, altrimenti usa l'hook interno
32
+ if (onDownloadDcmtsAsync) {
33
+ await onDownloadDcmtsAsync?.(dcmt, DownloadTypes.Dcmt, downloadMode);
34
+ }
35
+ else {
36
+ await downloadDcmtsAsync(dcmt, DownloadTypes.Dcmt, downloadMode);
37
+ }
38
+ }
39
+ }, [tid, did, downloadMode, onDownloadDcmtsAsync, downloadDcmtsAsync]);
19
40
  const isDownloadable = downloadMode !== "none" &&
20
41
  tid !== undefined &&
21
42
  did !== undefined &&
22
- onDownloadDcmtsAsync !== undefined;
23
- const handleClick = async (e) => {
24
- if (isDownloadable) {
25
- e.stopPropagation();
26
- let dcmt = [{ TID: tid, DID: did, FILEEXT: fileExtension }];
27
- await onDownloadDcmtsAsync?.(dcmt, DownloadTypes.Dcmt, downloadMode);
28
- }
29
- };
43
+ fileCount && fileCount > 0;
30
44
  return (_jsxs(StyledCellRenderDcmtIcon, { className: "tm-dcmt-icon", style: {
31
45
  cursor: isMobile
32
46
  ? (isDownloadable ? "pointer" : undefined)
33
47
  : (isDownloadable ? "default" : undefined)
34
- }, onClick: isMobile ? handleClick : undefined, onDoubleClick: !isMobile ? handleClick : undefined, children: [getFileIcon(isMail == 2 ? "PEC" : fileExtension, fileCount), isLexProt == 1 && _jsx("div", { style: { position: 'absolute', left: '-7px', top: isShared ? undefined : '2px' }, children: _jsx(TMTooltip, { content: "Protezione LEX", children: _jsx(IconLexProtLock, { color: 'blue', fontSize: 13 }) }) }), isShared == 1 && _jsx("div", { style: { position: 'absolute', top: '-7px', left: '-5px' }, children: _jsx(TMTooltip, { content: "Documento condiviso", children: _jsx(IconShared, { fontSize: 16 }) }) }), isSigned == 1 && _jsx("div", { style: { position: 'absolute', bottom: '-4px', right: '-7px' }, children: _jsx(TMTooltip, { content: "Documento firmato", children: _jsx(IconSignature, { fontSize: 28 }) }) })] }));
48
+ }, ...(deviceType === DeviceType.MOBILE
49
+ ? { onClick: handleDownloadAction } // Su mobile, un singolo click sull'icona avvia il download
50
+ : { onDoubleClick: handleDownloadAction } // Su desktop, un doppio click sull'icona avvia il download
51
+ ), children: [icon, isLexProt == 1 && _jsx("div", { style: { position: 'absolute', left: '-7px', top: isShared ? undefined : '2px' }, children: _jsx(TMTooltip, { content: "Protezione LEX", children: _jsx(IconLexProtLock, { color: 'blue', fontSize: 13 }) }) }), isShared == 1 && _jsx("div", { style: { position: 'absolute', top: '-7px', left: '-5px' }, children: _jsx(TMTooltip, { content: "Documento condiviso", children: _jsx(IconShared, { fontSize: 16 }) }) }), isSigned == 1 && _jsx("div", { style: { position: 'absolute', bottom: '-4px', right: '-7px' }, children: _jsx(TMTooltip, { content: "Documento firmato", children: _jsx(IconSignature, { fontSize: 28 }) }) }), showWaitPanel &&
52
+ _jsx(TMWaitPanel, { title: waitPanelTitle, showSecondary: showSecondary, textSecondary: waitPanelTextSecondary, valueSecondary: waitPanelValueSecondary, maxValueSecondary: waitPanelMaxValueSecondary, isCancelable: true, abortController: abortController, onAbortClick: (abortController) => { setTimeout(() => { abortController?.abort(); }, 1000); } })] }));
35
53
  };
36
54
  export default TMDcmtIcon;
37
55
  function IconLexProtLock(props) {
@@ -11,7 +11,7 @@ import { FormModes, SearchResultContext } from '../../../ts';
11
11
  import { TMColors } from '../../../utils/theme';
12
12
  import { StyledDivHorizontal, StyledBadge } from '../../base/Styled';
13
13
  import ShowAlert from '../../base/TMAlert';
14
- import { DeviceType } from '../../base/TMDeviceProvider';
14
+ import { DeviceType, useDeviceType } from '../../base/TMDeviceProvider';
15
15
  import { TMExceptionBoxManager } from '../../base/TMPopUp';
16
16
  import TMSpinner from '../../base/TMSpinner';
17
17
  import { TMLayoutWaitingContainer } from '../../base/TMWaitPanel';
@@ -223,34 +223,9 @@ const TMMasterDetailDcmts = ({ deviceType, inputDcmts, isForMaster, showCurrentD
223
223
  const renderItem = useCallback((itemData) => {
224
224
  return (!itemData ? _jsx(_Fragment, {}) :
225
225
  _jsxs("div", { style: { minWidth: '90px', width: '100%', display: 'flex', marginLeft: itemData.did ? '5px' : '0px', gap: itemData.did ? '15px' : '10px', alignItems: 'center', opacity: !itemData.isDcmt && (itemData.items?.length ?? 0) <= 0 ? 0.5 : 1 }, children: [itemData.did && showCurrentDcmtIndicator && itemData.did === inputDcmts?.[0].DID ? _jsx(IconBackhandIndexPointingRight, { fontSize: 22, overflow: 'visible' }) : _jsx(_Fragment, {}), itemData.did
226
- ? _jsx(TMDcmtIcon, { fileExtension: itemData.values?.FILEEXT?.value, fileCount: itemData.values?.FILECOUNT?.value, isLexProt: itemData.values?.IsLexProt?.value, isMail: itemData.values?.ISMAIL?.value, isShared: itemData.values?.ISSHARED?.value, isSigned: itemData.values?.ISSIGNED?.value })
226
+ ? _jsx(TMDcmtIcon, { tid: itemData.values?.TID.value, did: itemData.values?.DID.value, fileExtension: itemData.values?.FILEEXT?.value, fileCount: itemData.values?.FILECOUNT?.value, isLexProt: itemData.values?.IsLexProt?.value, isMail: itemData.values?.ISMAIL?.value, isShared: itemData.values?.ISSHARED?.value, isSigned: itemData.values?.ISSIGNED?.value, downloadMode: 'openInNewWindow' })
227
227
  : itemData.dtd && _jsx(TMDcmtTypeTooltip, { dtd: itemData.dtd, children: _jsx("div", { style: { position: 'relative' }, children: _jsx(IconFolder, { color: itemData.isManyToMany ? '#ff8f44' : TMColors.iconLight, fontSize: 24 }) }) }), itemData.did
228
228
  ?
229
- // <TMTooltip content={
230
- // <StyledTooltipContainer style={{ gap: '3px' }}>
231
- // <TMTidViewer tid={itemData.tid} showIcon showId />
232
- // <StyledTooltipSeparatorItem />
233
- // {
234
- // getMetadataKeys(itemData.values).map((key, index) => {
235
- // let md = itemData.values?.[key].md as MetadataDescriptor;
236
- // let value = itemData.values?.[key].value;
237
- // return (
238
- // <StyledDivHorizontal key={`${key}_${index}`} style={{ gap: '3px' }} >
239
- // <p style={{ fontSize: '1rem' }}>{`${key}: `}</p>
240
- // {
241
- // md.dataDomain == MetadataDataDomains.DataList ?
242
- // <TMDataListItemViewer dataListId={md.dataListID} viewMode={md.dataListViewMode} value={value} />
243
- // : md.dataDomain == MetadataDataDomains.UserID ?
244
- // <TMUserIdViewer userId={value as number} showIcon noneSelectionText='' />
245
- // :
246
- // <p style={{ fontSize: '1rem', fontWeight: 600 }}>{value}</p>
247
- // }
248
- // </StyledDivHorizontal>
249
- // )
250
- // })
251
- // }
252
- // </StyledTooltipContainer>
253
- // }>
254
229
  _jsx(StyledDivHorizontal, { style: { whiteSpace: 'nowrap', textOverflow: 'ellipsis' }, children: getDcmtDisplayValue(itemData.values).map((key, index) => {
255
230
  let md = itemData.values?.[key].md;
256
231
  let value = itemData.values?.[key].value;
@@ -261,7 +236,6 @@ const TMMasterDetailDcmts = ({ deviceType, inputDcmts, isForMaster, showCurrentD
261
236
  :
262
237
  _jsx("p", { style: { fontSize: '1rem' }, children: value })] }, `${key}_${index}`));
263
238
  }) })
264
- // </TMTooltip>
265
239
  : _jsxs(StyledDivHorizontal, { style: { gap: 5, overflow: 'hidden', whiteSpace: 'nowrap', textOverflow: 'ellipsis' }, children: [_jsx("p", { style: { whiteSpace: 'nowrap', textOverflow: 'ellipsis' }, children: itemData.name }), _jsx(StyledBadge, { "$backgroundColor": TMColors.info, children: itemData.items?.length })] })] }));
266
240
  }, []);
267
241
  /** Function for calculate related documents */
@@ -565,6 +539,17 @@ export function IconBackhandIndexPointingRight(props) {
565
539
  }
566
540
  const TMTreeViewWrapper = ({ data, allowMultipleSelection, focusedItem, selectedItems, renderItem, calculateItemsForNode, onFocusedItemChanged, onSelectionChanged, onDataChanged }) => {
567
541
  const { setPanelVisibilityById, setToolbarButtonVisibility } = useTMPanelManagerContext();
542
+ const deviceType = useDeviceType();
543
+ // Determina se il focus deve essere ritardato in base all'elemento cliccato
544
+ const handleShouldDelayFocusOnEvent = useCallback((node, event) => {
545
+ const targetElement = event.target;
546
+ // Se il click è sull'icona TMDcmtIcon (identificata dalla sua classe CSS)
547
+ // E siamo su un dispositivo NON mobile (su mobile, TMDcmtIcon gestisce già il click con stopPropagation)
548
+ if (targetElement.closest('.tm-dcmt-icon') && deviceType !== DeviceType.MOBILE) {
549
+ return true;
550
+ }
551
+ return false;
552
+ }, [deviceType]);
568
553
  return (_jsx(TMTreeView, { dataSource: data, allowMultipleSelection: allowMultipleSelection, calculateItemsForNode: calculateItemsForNode, itemRender: renderItem, focusedItem: focusedItem, selectedItems: selectedItems, onFocusedItemChanged: (item) => {
569
554
  onFocusedItemChanged?.(item);
570
555
  if (item?.isDcmt) {
@@ -579,7 +564,7 @@ const TMTreeViewWrapper = ({ data, allowMultipleSelection, focusedItem, selected
579
564
  setToolbarButtonVisibility('tmSearchResult', true);
580
565
  setToolbarButtonVisibility('tmDcmtForm', false);
581
566
  }
582
- }, onSelectionChanged: onSelectionChanged, onDataChanged: onDataChanged }));
567
+ }, onSelectionChanged: onSelectionChanged, onDataChanged: onDataChanged, shouldDelayFocusOnEvent: handleShouldDelayFocusOnEvent }));
583
568
  };
584
569
  const TMFormOrResultWrapper = ({ deviceType, focusedItem, onTaskCreateRequest }) => {
585
570
  const { setPanelVisibilityById } = useTMPanelManagerContext();
@@ -79,6 +79,7 @@ export declare class SDKUI_Localizator {
79
79
  static get CompleteError(): "Kompletter Fehler" | "Complete error" | "Error completo" | "Erreur complète" | "Erro completo" | "Errore completo";
80
80
  static get Configure(): "Konfigurieren" | "Configure" | "Configurar" | "Configura";
81
81
  static get Confirm(): string;
82
+ static get ConfirmDownload(): string;
82
83
  static get ConfirmPassword(): "Bestätige das Passwort" | "Confirm password" | "Confirmar Contraseña" | "Confirmez le mot de passe" | "Confirme sua senha" | "Conferma password";
83
84
  static get ConfirmOnCancel(): "Wenn wir fortfahren, gehen die vorgenommenen Änderungen verloren. Fortfahren?" | "All modifications will be lost. Continue?" | "Si sigue adelante, se perderán las modificaciones aportadas. ¿Seguir?" | "Continuant, les changements seront perdus. Continuer?" | "Continuando as alterações feitas serão perdidas. Continuar?" | "Proseguendo le modifiche apportate andranno perse. Proseguire?";
84
85
  static get Continue(): string;
@@ -169,6 +170,7 @@ export declare class SDKUI_Localizator {
169
170
  static get FEFormats_SDI_HTML(): "SdI Style Sheet (HTML)" | "Hoja de estilo SdI (HTML)" | "Feuille de style SdI (HTML)" | "Folha de estilo SdI (HTML)" | "Foglio di stile SdI (HTML)";
170
171
  static get FEFormats_SDI_PDF(): "SdI Style Sheet (PDF)" | "Hoja de estilo SdI (PDF)" | "Feuille de style SdI (PDF)" | "Folha de estilo SdI (PDF)" | "Foglio di stile SdI (PDF)";
171
172
  static get FileCheck(): string;
173
+ static get FileExtensionNotViewable(): string;
172
174
  static get FileConversion(): string;
173
175
  static get FileManager_QuestionAlreadyExistsFile(): "Ziel enthält bereits eine Datei mit der Bezeichnung {{0}}, ersetzen durch die neue Datei?" | "The destination already contains a file called {{0}}, replace with the new file?" | "El destino ya contiene un archivo llamado {{0}}, ¿sustituir con el nuevo archivo?" | "La destination contient déjà un fichier appelé {{0}}, remplacer avec le nouveau fichier?" | "O destino já contém um ficheiro chamado {{0}}, substitua com o novo arquivo?" | "La destinazione contiene già un file denominato {{0}}, sostituire con il nuovo file?";
174
176
  static get FileManager_QuestionAlreadyExistsFiles(): "Ziel enthält {{0}} Datei mit dem gleichen Namen, ersetzen durch neue Dateien?" | "Destination contains {{0}} files with the same name, replace with new files?" | "El destino contiene {{0}} archivos con el mismo nombre, ¿sustituir con los nuevos archivos?" | "La destination contient {{0}} fichier portant le même nom, remplacer avec les nouveaux fichiers?" | "O destino contém ficheiros {{0}} com o mesmo nome, substitua por novos arquivos?" | "La destinazione contiene {{0}} file con lo stesso nome, sostituire con i nuovi file?";
@@ -739,6 +739,16 @@ export class SDKUI_Localizator {
739
739
  default: return "Conferma";
740
740
  }
741
741
  }
742
+ static get ConfirmDownload() {
743
+ switch (this._cultureID) {
744
+ case CultureIDs.De_DE: return "Download bestätigen";
745
+ case CultureIDs.En_US: return "Confirm Download";
746
+ case CultureIDs.Es_ES: return "Confirmar Descarga";
747
+ case CultureIDs.Fr_FR: return "Confirmer le téléchargement";
748
+ case CultureIDs.Pt_PT: return "Confirmar Download";
749
+ default: return "Conferma Download";
750
+ }
751
+ }
742
752
  static get ConfirmPassword() {
743
753
  switch (this._cultureID) {
744
754
  case CultureIDs.De_DE: return "Bestätige das Passwort";
@@ -1651,6 +1661,16 @@ export class SDKUI_Localizator {
1651
1661
  default: return "Controllo file";
1652
1662
  }
1653
1663
  }
1664
+ static get FileExtensionNotViewable() {
1665
+ switch (this._cultureID) {
1666
+ case CultureIDs.De_DE: return "Die Datei mit der Erweiterung \"{{0}}\" kann möglicherweise nicht direkt im Browser angezeigt werden. Möchten Sie mit dem Download fortfahren?";
1667
+ case CultureIDs.En_US: return "The file with extension \"{{0}}\" might not be viewable directly in the browser. Do you want to proceed with the download?";
1668
+ case CultureIDs.Es_ES: return "El archivo con extensión \"{{0}}\" podría no ser visible directamente en el navegador. ¿Desea proceder con la descarga?";
1669
+ case CultureIDs.Fr_FR: return "Le fichier avec l'extension \"{{0}}\" pourrait ne pas être visualisable directement dans le navigateur. Voulez-vous procéder au téléchargement ?";
1670
+ case CultureIDs.Pt_PT: return "O arquivo com a extensão \"{{0}}\" pode não ser visível diretamente no navegador. Deseja prosseguir com o download?";
1671
+ default: return "Il file con estensione \"{{0}}\" potrebbe non essere visualizzabile direttamente nel browser. Vuoi procedere con il download?";
1672
+ }
1673
+ }
1654
1674
  static get FileConversion() {
1655
1675
  switch (this._cultureID) {
1656
1676
  case CultureIDs.De_DE: return "Dateikonvertierung";
@@ -6,6 +6,7 @@ import { Globalization, getExceptionMessage, dialogConfirmOperation, extensionHa
6
6
  import { DcmtOperationTypes, DownloadTypes, FileExtensionHandler } from '../ts';
7
7
  import { useFileDialog } from './useInputDialog';
8
8
  import { isXMLFileExt } from '../helper/dcmtsHelper';
9
+ import { ShowConfirm } from '../components/base/TMConfirm';
9
10
  let abortController = new AbortController();
10
11
  export function useDcmtOperations() {
11
12
  const [showWaitPanel, setShowWaitPanel] = useState(false);
@@ -24,6 +25,21 @@ export function useDcmtOperations() {
24
25
  return;
25
26
  if (inputDcmts.length <= 0)
26
27
  return;
28
+ if (inputDcmts.length === 1 && downloadMode === "openInNewWindow") {
29
+ if (!inputDcmts[0].FILEEXT)
30
+ return;
31
+ let readyToShow = extensionHandler(inputDcmts[0].FILEEXT) === FileExtensionHandler.READY_TO_SHOW;
32
+ if (!readyToShow) {
33
+ const confirmDownload = await ShowConfirm({
34
+ message: SDKUI_Localizator.FileExtensionNotViewable.replaceParams(inputDcmts[0].FILEEXT),
35
+ title: SDKUI_Localizator.ConfirmDownload,
36
+ okButtonText: 'Download',
37
+ cancelButtonText: SDKUI_Localizator.Cancel
38
+ });
39
+ if (!confirmDownload)
40
+ return;
41
+ }
42
+ }
27
43
  let operationTitle = 'Download File';
28
44
  setShowWaitPanel(true);
29
45
  setShowPrimary(inputDcmts.length > 1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@topconsultnpm/sdkui-react-beta",
3
- "version": "6.14.136",
3
+ "version": "6.14.138",
4
4
  "description": "",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1",
@@ -42,7 +42,7 @@
42
42
  "lib"
43
43
  ],
44
44
  "dependencies": {
45
- "@topconsultnpm/sdk-ts-beta": "6.14.19",
45
+ "@topconsultnpm/sdk-ts-beta": "6.14.20",
46
46
  "buffer": "^6.0.3",
47
47
  "devextreme": "24.2.6",
48
48
  "devextreme-react": "24.2.6",