@topconsultnpm/sdkui-react 6.21.0-dev4.15 → 6.21.0-dev4.17
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/TMTreeView.d.ts +16 -13
- package/lib/components/base/TMTreeView.js +230 -64
- package/lib/components/features/documents/TMFileUploader.js +1 -1
- package/lib/components/features/documents/TMMasterDetailDcmts.js +47 -10
- package/lib/components/features/documents/TMRelationViewer.d.ts +10 -1
- package/lib/components/features/documents/TMRelationViewer.js +296 -30
- package/lib/helper/SDKUI_Globals.d.ts +1 -0
- package/lib/helper/SDKUI_Globals.js +1 -0
- package/lib/helper/SDKUI_Localizator.d.ts +16 -0
- package/lib/helper/SDKUI_Localizator.js +160 -0
- package/lib/helper/helpers.js +9 -0
- package/package.json +2 -1
|
@@ -11,25 +11,28 @@ export interface ITMTreeItem {
|
|
|
11
11
|
currentPage?: number;
|
|
12
12
|
totalItemsCount?: number;
|
|
13
13
|
}
|
|
14
|
-
interface ITMTreeViewProps
|
|
15
|
-
dataSource?:
|
|
16
|
-
focusedItem?:
|
|
17
|
-
selectedItems?:
|
|
14
|
+
interface ITMTreeViewProps {
|
|
15
|
+
dataSource?: ITMTreeItem[];
|
|
16
|
+
focusedItem?: ITMTreeItem | null;
|
|
17
|
+
selectedItems?: ITMTreeItem[];
|
|
18
18
|
allowMultipleSelection?: boolean;
|
|
19
|
-
calculateItemsForNode?: (node:
|
|
20
|
-
itemRender: (item:
|
|
21
|
-
onFocusedItemChanged?: (item:
|
|
22
|
-
onSelectionChanged?: (selectedItems:
|
|
23
|
-
onNodeUpdate?: (updatedNode:
|
|
24
|
-
onDataChanged?: (items:
|
|
25
|
-
shouldDelayFocusOnEvent?: (node:
|
|
26
|
-
onItemContextMenu?: (item:
|
|
19
|
+
calculateItemsForNode?: (node: ITMTreeItem) => Promise<ITMTreeItem[] | undefined>;
|
|
20
|
+
itemRender: (item: ITMTreeItem | null) => JSX.Element;
|
|
21
|
+
onFocusedItemChanged?: (item: ITMTreeItem | null) => void;
|
|
22
|
+
onSelectionChanged?: (selectedItems: ITMTreeItem[]) => void;
|
|
23
|
+
onNodeUpdate?: (updatedNode: ITMTreeItem) => void;
|
|
24
|
+
onDataChanged?: (items: ITMTreeItem[]) => void;
|
|
25
|
+
shouldDelayFocusOnEvent?: (node: ITMTreeItem, event: React.MouseEvent) => boolean;
|
|
26
|
+
onItemContextMenu?: (item: ITMTreeItem, e: React.MouseEvent) => void;
|
|
27
27
|
autoSelectChildren?: boolean;
|
|
28
28
|
itemsPerPage?: number;
|
|
29
29
|
showLoadMoreButton?: boolean;
|
|
30
|
+
enableVirtualization?: boolean;
|
|
30
31
|
}
|
|
31
|
-
declare const TMTreeView:
|
|
32
|
+
declare const TMTreeView: ({ dataSource, focusedItem, selectedItems, allowMultipleSelection, onDataChanged, calculateItemsForNode, itemRender, onNodeUpdate, onFocusedItemChanged, onSelectionChanged, shouldDelayFocusOnEvent, onItemContextMenu, autoSelectChildren, itemsPerPage, showLoadMoreButton, enableVirtualization }: ITMTreeViewProps) => import("react/jsx-runtime").JSX.Element;
|
|
32
33
|
export default TMTreeView;
|
|
34
|
+
export declare const StyledTreeContainer: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never> & Partial<Pick<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>>> & string;
|
|
35
|
+
export declare const StyledItemContent: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never> & Partial<Pick<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>>> & string;
|
|
33
36
|
export declare const StyledTreeNode: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("styled-components").FastOmit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "$isSelected"> & {
|
|
34
37
|
$isSelected?: boolean;
|
|
35
38
|
}, never> & Partial<Pick<import("styled-components").FastOmit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "$isSelected"> & {
|
|
@@ -1,9 +1,147 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useCallback, useEffect, useRef } from 'react';
|
|
2
|
+
import React, { useCallback, useEffect, useMemo, useRef, memo } from 'react';
|
|
3
|
+
import { List } from 'react-window';
|
|
3
4
|
import styled from 'styled-components';
|
|
4
5
|
import { IconArrowLeft, IconArrowRight, IconChevronDown, IconChevronRight, SDKUI_Localizator } from '../../helper';
|
|
5
6
|
import TMButton from './TMButton';
|
|
6
|
-
|
|
7
|
+
// Componente riga virtualizzata memoizzato
|
|
8
|
+
const VirtualRowComponent = memo(({ ariaAttributes, index, style, flattenedItems, focusedItemKey, selectedItemKeys, allowMultipleSelection, hasVisibleItems, handleNodeToggle, handleNodeClick, handleCheckboxChange, hasPartialChildSelection, itemRender, onItemContextMenu }) => {
|
|
9
|
+
const item = flattenedItems[index];
|
|
10
|
+
if (!item)
|
|
11
|
+
return null;
|
|
12
|
+
const { node, depth } = item;
|
|
13
|
+
const isSelected = node.key === focusedItemKey;
|
|
14
|
+
return (_jsx("div", { ...ariaAttributes, "data-node-key": node.key, style: {
|
|
15
|
+
...style,
|
|
16
|
+
paddingLeft: depth * 20,
|
|
17
|
+
display: 'flex',
|
|
18
|
+
alignItems: 'center',
|
|
19
|
+
width: 'fit-content',
|
|
20
|
+
minWidth: '100%',
|
|
21
|
+
paddingRight: 10,
|
|
22
|
+
boxSizing: 'border-box'
|
|
23
|
+
}, children: _jsxs(StyledTreeNode, { "$isSelected": isSelected, children: [_jsx("div", { style: {
|
|
24
|
+
display: 'flex',
|
|
25
|
+
alignItems: 'center',
|
|
26
|
+
justifyContent: 'center',
|
|
27
|
+
minHeight: '18px',
|
|
28
|
+
minWidth: '18px',
|
|
29
|
+
maxHeight: '18px',
|
|
30
|
+
maxWidth: '18px',
|
|
31
|
+
flexShrink: 0
|
|
32
|
+
}, onClick: (e) => { handleNodeToggle(node.key, e.ctrlKey); }, children: hasVisibleItems(node)
|
|
33
|
+
? node.expanded
|
|
34
|
+
? _jsx(IconChevronDown, { cursor: 'pointer', fontSize: 14 })
|
|
35
|
+
: _jsx(IconChevronRight, { cursor: 'pointer', fontSize: 14 })
|
|
36
|
+
: _jsx("div", { style: { height: '18px', width: '18px' } }) }), allowMultipleSelection && (_jsx("input", { type: "checkbox", checked: selectedItemKeys.has(node.key), onChange: (e) => handleCheckboxChange(node, e.target.checked), onClick: (e) => e.stopPropagation(), style: { flexShrink: 0 }, ref: input => {
|
|
37
|
+
if (input) {
|
|
38
|
+
// Imposta lo stato visuale "trattino" sulla checkbox quando il container
|
|
39
|
+
// ha alcuni figli selezionati ma non tutti. Necessario usare ref perché
|
|
40
|
+
// "indeterminate" è una proprietà DOM, non un attributo HTML.
|
|
41
|
+
input.indeterminate = hasPartialChildSelection(node);
|
|
42
|
+
}
|
|
43
|
+
} })), _jsx(StyledItemContent, { onClick: (e) => { handleNodeClick(node, e); }, onContextMenu: (e) => {
|
|
44
|
+
if (onItemContextMenu) {
|
|
45
|
+
e.preventDefault();
|
|
46
|
+
onItemContextMenu(node, e);
|
|
47
|
+
}
|
|
48
|
+
}, children: itemRender(node) })] }) }));
|
|
49
|
+
}, (prevProps, nextProps) => {
|
|
50
|
+
// Custom comparison: re-render solo se i dati rilevanti per questa riga cambiano
|
|
51
|
+
const prevItem = prevProps.flattenedItems[prevProps.index];
|
|
52
|
+
const nextItem = nextProps.flattenedItems[nextProps.index];
|
|
53
|
+
if (!prevItem || !nextItem)
|
|
54
|
+
return prevItem === nextItem;
|
|
55
|
+
const prevNode = prevItem.node;
|
|
56
|
+
const nextNode = nextItem.node;
|
|
57
|
+
// Controlla se questa riga specifica è cambiata
|
|
58
|
+
return (prevNode.key === nextNode.key &&
|
|
59
|
+
prevNode.expanded === nextNode.expanded &&
|
|
60
|
+
prevItem.depth === nextItem.depth &&
|
|
61
|
+
(prevNode.key === prevProps.focusedItemKey) === (nextNode.key === nextProps.focusedItemKey) &&
|
|
62
|
+
prevProps.selectedItemKeys.has(prevNode.key) === nextProps.selectedItemKeys.has(nextNode.key) &&
|
|
63
|
+
prevProps.allowMultipleSelection === nextProps.allowMultipleSelection);
|
|
64
|
+
});
|
|
65
|
+
const TMTreeView = ({ dataSource = [], focusedItem, selectedItems = [], allowMultipleSelection, onDataChanged, calculateItemsForNode, itemRender, onNodeUpdate, onFocusedItemChanged, onSelectionChanged, shouldDelayFocusOnEvent, onItemContextMenu, autoSelectChildren = true, itemsPerPage = 100, showLoadMoreButton = true, enableVirtualization = false }) => {
|
|
66
|
+
// Altezza fissa per ogni riga nella virtualizzazione
|
|
67
|
+
const VIRTUAL_ROW_HEIGHT = 26;
|
|
68
|
+
const listRef = useRef(null);
|
|
69
|
+
const containerRef = useRef(null);
|
|
70
|
+
const [containerSize, setContainerSize] = React.useState({ width: 0, height: 0 });
|
|
71
|
+
// Ref per selectedItems - permette alle callback memoizzate di accedere sempre al valore più recente
|
|
72
|
+
const selectedItemsRef = useRef(selectedItems);
|
|
73
|
+
selectedItemsRef.current = selectedItems;
|
|
74
|
+
// Ref per handleCheckboxChange - permette all'useEffect di accedere alla funzione prima che sia definita
|
|
75
|
+
const handleCheckboxChangeRef = useRef(() => { });
|
|
76
|
+
// Filtra gli items da mostrare in base alla paginazione (definito prima per essere usato da flattenTreeWithDepth)
|
|
77
|
+
const getVisibleItems = useCallback((node) => {
|
|
78
|
+
if (!node.items)
|
|
79
|
+
return [];
|
|
80
|
+
const totalItems = node.items.length;
|
|
81
|
+
// Se non c'è paginazione attiva o gli items sono pochi, mostra tutti
|
|
82
|
+
if (totalItems <= itemsPerPage || !showLoadMoreButton) {
|
|
83
|
+
return node.items;
|
|
84
|
+
}
|
|
85
|
+
// Altrimenti mostra solo gli items della pagina corrente
|
|
86
|
+
const currentPage = node.currentPage ?? 0;
|
|
87
|
+
const startIndex = currentPage * itemsPerPage;
|
|
88
|
+
const endIndex = startIndex + itemsPerPage;
|
|
89
|
+
return node.items.slice(startIndex, endIndex);
|
|
90
|
+
}, [itemsPerPage, showLoadMoreButton]);
|
|
91
|
+
// Misura le dimensioni del container per react-window
|
|
92
|
+
useEffect(() => {
|
|
93
|
+
if (!enableVirtualization || !containerRef.current)
|
|
94
|
+
return;
|
|
95
|
+
const resizeObserver = new ResizeObserver((entries) => {
|
|
96
|
+
for (const entry of entries) {
|
|
97
|
+
const { width, height } = entry.contentRect;
|
|
98
|
+
setContainerSize({ width, height });
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
resizeObserver.observe(containerRef.current);
|
|
102
|
+
return () => resizeObserver.disconnect();
|
|
103
|
+
}, [enableVirtualization]);
|
|
104
|
+
// Flatten tree con informazioni di profondità per la virtualizzazione
|
|
105
|
+
const flattenTreeWithDepth = useCallback((nodes, depth = 0, parentKey) => {
|
|
106
|
+
let flatList = [];
|
|
107
|
+
nodes.forEach(node => {
|
|
108
|
+
if (!node.hidden) {
|
|
109
|
+
flatList.push({ node, depth, parentKey });
|
|
110
|
+
if (node.expanded && node.items) {
|
|
111
|
+
const visibleItems = getVisibleItems(node);
|
|
112
|
+
flatList = flatList.concat(flattenTreeWithDepth(visibleItems, depth + 1, node.key));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
return flatList;
|
|
117
|
+
}, [getVisibleItems]);
|
|
118
|
+
// Lista piatta per la virtualizzazione
|
|
119
|
+
const flattenedItems = useMemo(() => {
|
|
120
|
+
if (!enableVirtualization)
|
|
121
|
+
return [];
|
|
122
|
+
return flattenTreeWithDepth(dataSource);
|
|
123
|
+
}, [enableVirtualization, dataSource, flattenTreeWithDepth]);
|
|
124
|
+
// Scroll verso l'elemento focalizzato quando cambia (navigazione con frecce)
|
|
125
|
+
useEffect(() => {
|
|
126
|
+
if (!focusedItem?.key)
|
|
127
|
+
return;
|
|
128
|
+
if (enableVirtualization) {
|
|
129
|
+
// Lista virtualizzata: usa scrollToRow per garantire che l'elemento sia visibile
|
|
130
|
+
const index = flattenedItems.findIndex(item => item.node.key === focusedItem.key);
|
|
131
|
+
if (index >= 0 && listRef.current) {
|
|
132
|
+
listRef.current.scrollToRow({ index, align: 'smart' });
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
// Lista standard: usa scrollIntoView
|
|
137
|
+
if (containerRef.current) {
|
|
138
|
+
const element = containerRef.current.querySelector(`[data-node-key="${focusedItem.key}"]`);
|
|
139
|
+
if (element) {
|
|
140
|
+
element.scrollIntoView({ block: 'nearest', inline: 'nearest' });
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}, [focusedItem?.key, enableVirtualization, flattenedItems]);
|
|
7
145
|
useEffect(() => {
|
|
8
146
|
const handleKeyDown = (event) => {
|
|
9
147
|
if (!focusedItem)
|
|
@@ -33,9 +171,14 @@ const TMTreeView = ({ dataSource = [], focusedItem, selectedItems = [], allowMul
|
|
|
33
171
|
handled = true;
|
|
34
172
|
}
|
|
35
173
|
break;
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
174
|
+
case ' ':
|
|
175
|
+
case 'Spacebar': // IE/Edge legacy
|
|
176
|
+
if (allowMultipleSelection) {
|
|
177
|
+
const isCurrentlySelected = selectedItemsRef.current.some(item => item.key === focusedItem.key);
|
|
178
|
+
handleCheckboxChangeRef.current(focusedItem, !isCurrentlySelected);
|
|
179
|
+
handled = true;
|
|
180
|
+
}
|
|
181
|
+
break;
|
|
39
182
|
// case '*':
|
|
40
183
|
// handleExpandAllNodes();
|
|
41
184
|
// break;
|
|
@@ -57,7 +200,7 @@ const TMTreeView = ({ dataSource = [], focusedItem, selectedItems = [], allowMul
|
|
|
57
200
|
return () => {
|
|
58
201
|
window.removeEventListener('keydown', handleKeyDown, true);
|
|
59
202
|
};
|
|
60
|
-
}, [focusedItem, dataSource, onFocusedItemChanged]);
|
|
203
|
+
}, [focusedItem, dataSource, onFocusedItemChanged, allowMultipleSelection]);
|
|
61
204
|
const findNextItem = (nodes, currentItem) => {
|
|
62
205
|
const flatList = flattenTree(nodes);
|
|
63
206
|
const currentIndex = flatList.findIndex(item => item.key === currentItem.key);
|
|
@@ -162,7 +305,9 @@ const TMTreeView = ({ dataSource = [], focusedItem, selectedItems = [], allowMul
|
|
|
162
305
|
onFocusedItemChanged?.(node); // Chiama onFocusedItemChanged immediatamente
|
|
163
306
|
}
|
|
164
307
|
}, [onFocusedItemChanged, calculateItemsForNode, onNodeUpdate]);
|
|
165
|
-
const handleCheckboxChange = (node, checked) => {
|
|
308
|
+
const handleCheckboxChange = useCallback((node, checked) => {
|
|
309
|
+
// Usa la ref per accedere sempre al valore più recente di selectedItems
|
|
310
|
+
const currentSelectedItems = selectedItemsRef.current;
|
|
166
311
|
// Funzione helper per trovare il parent di un nodo e verificare se tutti i suoi figli sono selezionati
|
|
167
312
|
const findAndCheckParents = (items, targetNode, currentSelection) => {
|
|
168
313
|
const parentsToAdd = [];
|
|
@@ -199,11 +344,14 @@ const TMTreeView = ({ dataSource = [], focusedItem, selectedItems = [], allowMul
|
|
|
199
344
|
if (node.isContainer && autoSelectChildren) {
|
|
200
345
|
// Quando selezioni un container, aggiungi il container stesso + tutti i figli (se autoSelectChildren è true)
|
|
201
346
|
const allChildren = flattenTree(node.items || []);
|
|
202
|
-
|
|
347
|
+
// Rimuovi eventuali duplicati: filtra gli elementi già presenti nella selezione
|
|
348
|
+
const childrenKeys = new Set(allChildren.map(child => child.key));
|
|
349
|
+
const filteredSelectedItems = currentSelectedItems.filter(item => !childrenKeys.has(item.key) && item.key !== node.key);
|
|
350
|
+
newSelectedItems = [...filteredSelectedItems, node, ...allChildren];
|
|
203
351
|
}
|
|
204
|
-
else if (!
|
|
352
|
+
else if (!currentSelectedItems.some(item => item.key === node.key)) {
|
|
205
353
|
// Quando selezioni un figlio o un container con autoSelectChildren=false, aggiungi solo il nodo
|
|
206
|
-
newSelectedItems = [...
|
|
354
|
+
newSelectedItems = [...currentSelectedItems, node];
|
|
207
355
|
// Verifica se selezionando questo figlio sono ora selezionati tutti i figli del parent (solo se autoSelectChildren è true)
|
|
208
356
|
if (autoSelectChildren) {
|
|
209
357
|
const parentsToAdd = findAndCheckParents(dataSource, node, newSelectedItems);
|
|
@@ -211,17 +359,17 @@ const TMTreeView = ({ dataSource = [], focusedItem, selectedItems = [], allowMul
|
|
|
211
359
|
}
|
|
212
360
|
}
|
|
213
361
|
else {
|
|
214
|
-
newSelectedItems =
|
|
362
|
+
newSelectedItems = currentSelectedItems;
|
|
215
363
|
}
|
|
216
364
|
}
|
|
217
365
|
else if (node.isContainer && autoSelectChildren) {
|
|
218
366
|
// Quando deselezioni un container, rimuovi il container stesso + tutti i figli (solo se autoSelectChildren è true)
|
|
219
367
|
const childKeys = flattenTree(node.items || []).map(item => item.key);
|
|
220
|
-
newSelectedItems =
|
|
368
|
+
newSelectedItems = currentSelectedItems.filter(item => item.key !== node.key && !childKeys.includes(item.key));
|
|
221
369
|
}
|
|
222
370
|
else {
|
|
223
371
|
// Quando deselezioni un figlio o un container con autoSelectChildren=false, rimuovi solo il nodo
|
|
224
|
-
newSelectedItems =
|
|
372
|
+
newSelectedItems = currentSelectedItems.filter(item => item.key !== node.key);
|
|
225
373
|
// Se il figlio apparteneva a un parent che era selezionato, rimuovi anche il parent (solo se autoSelectChildren è true)
|
|
226
374
|
if (autoSelectChildren && !node.isContainer) {
|
|
227
375
|
const removeParentContainers = (items) => {
|
|
@@ -243,28 +391,10 @@ const TMTreeView = ({ dataSource = [], focusedItem, selectedItems = [], allowMul
|
|
|
243
391
|
}
|
|
244
392
|
}
|
|
245
393
|
onSelectionChanged?.(newSelectedItems);
|
|
246
|
-
};
|
|
247
|
-
//
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
// ...node,
|
|
251
|
-
// expanded: true,
|
|
252
|
-
// items: node.items ? expandAll(node.items as T[]) : node.items
|
|
253
|
-
// }));
|
|
254
|
-
// };
|
|
255
|
-
// setTreeData(prevData => expandAll(prevData));
|
|
256
|
-
// };
|
|
257
|
-
// const handleCollapseAllNodes = () => {
|
|
258
|
-
// const collapseAll = (nodes: T[]): T[] => {
|
|
259
|
-
// return nodes.map(node => ({
|
|
260
|
-
// ...node,
|
|
261
|
-
// expanded: false,
|
|
262
|
-
// items: node.items ? collapseAll(node.items as T[]) : node.items
|
|
263
|
-
// }));
|
|
264
|
-
// };
|
|
265
|
-
// setTreeData(prevData => collapseAll(prevData));
|
|
266
|
-
// };
|
|
267
|
-
const isIndeterminate = (node) => {
|
|
394
|
+
}, [autoSelectChildren, dataSource, onSelectionChanged]);
|
|
395
|
+
// Aggiorna la ref con l'ultima versione di handleCheckboxChange
|
|
396
|
+
handleCheckboxChangeRef.current = handleCheckboxChange;
|
|
397
|
+
const hasPartialChildSelection = (node) => {
|
|
268
398
|
// Lo stato indeterminate ha senso solo quando autoSelectChildren è true
|
|
269
399
|
if (!autoSelectChildren)
|
|
270
400
|
return false;
|
|
@@ -275,13 +405,6 @@ const TMTreeView = ({ dataSource = [], focusedItem, selectedItems = [], allowMul
|
|
|
275
405
|
const selectedCount = childKeys.filter(key => selectedChildKeys.includes(key)).length;
|
|
276
406
|
return selectedCount > 0 && selectedCount < childKeys.length;
|
|
277
407
|
};
|
|
278
|
-
const isFullySelected = (node) => {
|
|
279
|
-
if (!node.isContainer || !node.items)
|
|
280
|
-
return false;
|
|
281
|
-
const childKeys = flattenTree(node.items).map(item => item.key);
|
|
282
|
-
const selectedChildKeys = selectedItems.map(item => item.key);
|
|
283
|
-
return childKeys.every(key => selectedChildKeys.includes(key));
|
|
284
|
-
};
|
|
285
408
|
const hasVisibleItems = (node) => {
|
|
286
409
|
// If node has explicit isExpandible value, use that
|
|
287
410
|
if (node.isExpandible !== undefined) {
|
|
@@ -312,21 +435,6 @@ const TMTreeView = ({ dataSource = [], focusedItem, selectedItems = [], allowMul
|
|
|
312
435
|
const updatedData = updateNodePage(dataSource);
|
|
313
436
|
onDataChanged?.(updatedData);
|
|
314
437
|
}, [dataSource, onDataChanged, onNodeUpdate]);
|
|
315
|
-
// Filtra gli items da mostrare in base alla paginazione
|
|
316
|
-
const getVisibleItems = useCallback((node) => {
|
|
317
|
-
if (!node.items)
|
|
318
|
-
return [];
|
|
319
|
-
const totalItems = node.items.length;
|
|
320
|
-
// Se non c'è paginazione attiva o gli items sono pochi, mostra tutti
|
|
321
|
-
if (totalItems <= itemsPerPage || !showLoadMoreButton) {
|
|
322
|
-
return node.items;
|
|
323
|
-
}
|
|
324
|
-
// Altrimenti mostra solo gli items della pagina corrente
|
|
325
|
-
const currentPage = node.currentPage ?? 0;
|
|
326
|
-
const startIndex = currentPage * itemsPerPage;
|
|
327
|
-
const endIndex = startIndex + itemsPerPage;
|
|
328
|
-
return node.items.slice(startIndex, endIndex);
|
|
329
|
-
}, [itemsPerPage, showLoadMoreButton]);
|
|
330
438
|
// Verifica se c'è bisogno del paginatore
|
|
331
439
|
const needsPagination = useCallback((node) => {
|
|
332
440
|
if (!showLoadMoreButton || !node.items)
|
|
@@ -340,7 +448,7 @@ const TMTreeView = ({ dataSource = [], focusedItem, selectedItems = [], allowMul
|
|
|
340
448
|
return Math.ceil(node.items.length / itemsPerPage);
|
|
341
449
|
}, [itemsPerPage]);
|
|
342
450
|
const renderTree = useCallback((nodes) => {
|
|
343
|
-
return nodes.map(node => !node.hidden && (_jsxs("div", { style: { width: '100%', margin: 0, padding: 0 }, children: [_jsxs(StyledTreeNode, { "$isSelected": node.key === focusedItem?.key, children: [_jsx("div", { style: {
|
|
451
|
+
return nodes.map(node => !node.hidden && (_jsxs("div", { style: { width: 'fit-content', minWidth: '100%', margin: 0, padding: 0 }, children: [_jsxs(StyledTreeNode, { "$isSelected": node.key === focusedItem?.key, "data-node-key": node.key, children: [_jsx("div", { style: {
|
|
344
452
|
display: 'flex',
|
|
345
453
|
alignItems: 'center',
|
|
346
454
|
justifyContent: 'center',
|
|
@@ -355,28 +463,86 @@ const TMTreeView = ({ dataSource = [], focusedItem, selectedItems = [], allowMul
|
|
|
355
463
|
: _jsx(IconChevronRight, { cursor: 'pointer', fontSize: 14 })
|
|
356
464
|
: _jsx("div", { style: { height: '18px', width: '18px' } }) }), allowMultipleSelection && (_jsx("input", { type: "checkbox", checked: selectedItems.some(item => item.key === node.key), onChange: (e) => handleCheckboxChange(node, e.target.checked), onClick: (e) => e.stopPropagation(), style: { flexShrink: 0 }, ref: input => {
|
|
357
465
|
if (input) {
|
|
358
|
-
|
|
466
|
+
// Imposta lo stato visuale "trattino" (➖) sulla checkbox quando il container
|
|
467
|
+
// ha alcuni figli selezionati ma non tutti. Necessario usare ref perché
|
|
468
|
+
// "indeterminate" è una proprietà DOM, non un attributo HTML.
|
|
469
|
+
input.indeterminate = hasPartialChildSelection(node);
|
|
359
470
|
}
|
|
360
|
-
} })), _jsx(
|
|
471
|
+
} })), _jsx(StyledItemContent, { onClick: (e) => { handleNodeClick(node, e); }, onContextMenu: (e) => {
|
|
361
472
|
if (onItemContextMenu) {
|
|
362
473
|
e.preventDefault();
|
|
363
474
|
onItemContextMenu(node, e);
|
|
364
475
|
}
|
|
365
|
-
}, children: itemRender(node) })] }), node.expanded && node.items && (_jsxs("div", { style: { paddingLeft: 20, width: '100%' }, children: [renderTree(getVisibleItems(node)), needsPagination(node) && (_jsxs(StyledStickyPaginator, { children: [_jsx(TMButton, { btnStyle: 'icon', onClick: () => handlePageChange(node.key, (node.currentPage ?? 0) - 1), showTooltip: false, caption: "\u25C4", icon: _jsx(IconArrowLeft, { color: 'white' }), disabled: (node.currentPage ?? 0) <= 0 }), _jsx("span", { style: { fontSize: '11px', whiteSpace: 'nowrap', fontWeight: 500, overflow: 'hidden', textOverflow: 'ellipsis', minWidth: 0, color: 'white' }, children: SDKUI_Localizator.PaginationInfo.replaceParams((node.currentPage ?? 0) + 1, getTotalPages(node), node.items?.length ?? 0) }), _jsx(TMButton, { btnStyle: 'icon', onClick: () => handlePageChange(node.key, (node.currentPage ?? 0) + 1), showTooltip: false, caption: "\u25BA", icon: _jsx(IconArrowRight, { color: 'white' }), disabled: (node.currentPage ?? 0) >= getTotalPages(node) - 1 })] }))] }))] }, node.key)));
|
|
476
|
+
}, children: itemRender(node) })] }), node.expanded && node.items && (_jsxs("div", { style: { paddingLeft: 20, width: 'fit-content', minWidth: '100%' }, children: [renderTree(getVisibleItems(node)), needsPagination(node) && (_jsxs(StyledStickyPaginator, { children: [_jsx(TMButton, { btnStyle: 'icon', onClick: () => handlePageChange(node.key, (node.currentPage ?? 0) - 1), showTooltip: false, caption: "\u25C4", icon: _jsx(IconArrowLeft, { color: 'white' }), disabled: (node.currentPage ?? 0) <= 0 }), _jsx("span", { style: { fontSize: '11px', whiteSpace: 'nowrap', fontWeight: 500, overflow: 'hidden', textOverflow: 'ellipsis', minWidth: 0, color: 'white' }, children: SDKUI_Localizator.PaginationInfo.replaceParams((node.currentPage ?? 0) + 1, getTotalPages(node), node.items?.length ?? 0) }), _jsx(TMButton, { btnStyle: 'icon', onClick: () => handlePageChange(node.key, (node.currentPage ?? 0) + 1), showTooltip: false, caption: "\u25BA", icon: _jsx(IconArrowRight, { color: 'white' }), disabled: (node.currentPage ?? 0) >= getTotalPages(node) - 1 })] }))] }))] }, node.key)));
|
|
366
477
|
}, [handleNodeClick, handleNodeToggle, handleCheckboxChange, focusedItem, selectedItems, allowMultipleSelection, getVisibleItems, needsPagination, handlePageChange, getTotalPages, onItemContextMenu]);
|
|
367
|
-
|
|
478
|
+
// Calcola selectedItemKeys ad ogni render per garantire sincronizzazione con selectedItems
|
|
479
|
+
const selectedItemKeys = new Set(selectedItems.map(s => s.key));
|
|
480
|
+
// Memoizza rowPropsData per evitare re-render della List
|
|
481
|
+
const rowPropsData = useMemo(() => ({
|
|
482
|
+
flattenedItems,
|
|
483
|
+
focusedItemKey: focusedItem?.key,
|
|
484
|
+
selectedItemKeys,
|
|
485
|
+
allowMultipleSelection,
|
|
486
|
+
hasVisibleItems,
|
|
487
|
+
handleNodeToggle,
|
|
488
|
+
handleNodeClick,
|
|
489
|
+
handleCheckboxChange,
|
|
490
|
+
hasPartialChildSelection: hasPartialChildSelection,
|
|
491
|
+
itemRender,
|
|
492
|
+
onItemContextMenu
|
|
493
|
+
}), [
|
|
494
|
+
flattenedItems,
|
|
495
|
+
focusedItem?.key,
|
|
496
|
+
selectedItemKeys,
|
|
497
|
+
allowMultipleSelection,
|
|
498
|
+
hasVisibleItems,
|
|
499
|
+
handleNodeToggle,
|
|
500
|
+
handleNodeClick,
|
|
501
|
+
handleCheckboxChange,
|
|
502
|
+
hasPartialChildSelection,
|
|
503
|
+
itemRender,
|
|
504
|
+
onItemContextMenu
|
|
505
|
+
]);
|
|
506
|
+
// Render virtualizzato con react-window v2
|
|
507
|
+
if (enableVirtualization) {
|
|
508
|
+
return (_jsx(StyledTreeContainer, { ref: containerRef, children: containerSize.height > 0 && (_jsx(List, { listRef: listRef, rowCount: flattenedItems.length, rowHeight: VIRTUAL_ROW_HEIGHT, rowComponent: VirtualRowComponent, rowProps: rowPropsData, style: { height: containerSize.height, width: '100%' }, overscanCount: 5 })) }));
|
|
509
|
+
}
|
|
510
|
+
// Render standard (non virtualizzato) con scrolling orizzontale
|
|
511
|
+
return (_jsx(StyledTreeContainer, { ref: containerRef, children: _jsx("div", { children: renderTree(dataSource) }) }));
|
|
368
512
|
};
|
|
369
513
|
export default TMTreeView;
|
|
514
|
+
// Container principale con scrolling orizzontale
|
|
515
|
+
export const StyledTreeContainer = styled.div `
|
|
516
|
+
height: 100%;
|
|
517
|
+
width: 100%;
|
|
518
|
+
overflow-y: auto;
|
|
519
|
+
overflow-x: auto;
|
|
520
|
+
padding: 0px 0px 2px 2px;
|
|
521
|
+
|
|
522
|
+
/* Contenitore interno che si espande per accomodare nodi annidati */
|
|
523
|
+
& > div {
|
|
524
|
+
width: fit-content;
|
|
525
|
+
min-width: 100%;
|
|
526
|
+
}
|
|
527
|
+
`;
|
|
528
|
+
// Contenuto dell'item - niente ellipsis per permettere scrolling orizzontale del container
|
|
529
|
+
export const StyledItemContent = styled.div `
|
|
530
|
+
display: flex;
|
|
531
|
+
align-items: center;
|
|
532
|
+
flex-shrink: 0;
|
|
533
|
+
white-space: nowrap;
|
|
534
|
+
`;
|
|
370
535
|
export const StyledTreeNode = styled.div `
|
|
371
536
|
display: flex;
|
|
372
537
|
flex-direction: row;
|
|
373
|
-
width:
|
|
374
|
-
min-width:
|
|
538
|
+
width: fit-content;
|
|
539
|
+
min-width: 100%;
|
|
375
540
|
min-height: 22px;
|
|
376
541
|
max-height: 30px;
|
|
377
542
|
gap: 5px;
|
|
378
543
|
align-items: center;
|
|
379
544
|
padding: 0;
|
|
545
|
+
padding-right: 10px; /* Spazio extra a destra per evitare che il testo tocchi il bordo */
|
|
380
546
|
margin: 0;
|
|
381
547
|
background: ${(props) => props.$isSelected ? 'oklch(from var(--dx-color-primary) l c h / .2) !important' : 'transparent'};
|
|
382
548
|
|
|
@@ -99,7 +99,7 @@ const TMFileUploader = ({ fromDTD, deviceType = DeviceType.DESKTOP, onClose, onF
|
|
|
99
99
|
let content = !uploadedFile ?
|
|
100
100
|
_jsxs("div", { style: { display: 'flex', gap: 10, width: '100%', height: '100%' }, children: [_jsx(HiddenInput, { id: "fileInput", type: "file", onChange: handleInputChange }), _jsxs(UploadContainer, { ref: uploaderRef, tabIndex: 0, onDragOver: handleDragOver, onDragLeave: handleDragLeave, onDrop: handleDrop, style: { backgroundColor: dragOver ? '#76b1e6' : 'white' }, onDoubleClick: browseHandler, "$isRequired": isRequired, children: [_jsxs("div", { style: { display: 'flex', gap: '10px', flexDirection: 'column', position: 'absolute', right: 5, top: 5 }, children: [_jsx(TMButton, { btnStyle: 'icon', caption: 'Sfoglia', color: isRequired && !uploadedFile ? 'error' : 'primary', onClick: browseHandler, icon: _jsx(IconFolderOpen, { fontSize: 22 }) }), showScannerIcon && isScannerLicenseConfigured() && onScanRequest && _jsx(TMButton, { btnStyle: 'icon', caption: 'Scanner', color: 'primary', onClick: () => { onScanRequest((file) => { onFileUpload?.(file); }); }, icon: _jsx(IconScanner, { fontSize: 22 }) }), showScannerIcon && isScannerLicenseConfigured() && !onScanRequest && _jsx(TMButton, { btnStyle: 'icon', caption: 'Scanner', color: 'primary', onClick: () => { ShowAlert({ message: SDKUI_Localizator.ScanFeatureUnavailableInThisContext, mode: 'info', duration: 3000, title: 'Scanner' }); }, icon: _jsx(IconScanner, { fontSize: 22 }) })] }), _jsx("p", { style: { fontSize: '1.2rem', fontWeight: 'bold' }, children: deviceType === DeviceType.MOBILE ? SDKUI_Localizator.ClickToBrowseFile : SDKUI_Localizator.DragOrDoubleClickToBrowseFile }), isRequired && _jsxs("p", { style: { fontWeight: 'bold' }, children: [" ", SDKUI_Localizator.RequiredField, " "] })] })] }) :
|
|
101
101
|
_jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 10, width: '100%', height: '100%' }, children: [_jsxs("div", { style: { backgroundColor: 'white', padding: '5px 10px', borderRadius: 8, display: 'flex', alignItems: 'center', justifyContent: 'space-between', color: TMColors.primaryColor }, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 5 }, children: [_jsx("p", { children: "File name:" }), _jsxs("div", { style: { fontWeight: 'bold' }, children: [fileName, " ", _jsxs("span", { children: [" ", ` (${formatBytes(fileSize)})`, " "] })] })] }), uploadedFile && _jsx(TMButton, { btnStyle: 'icon', color: 'error', caption: 'Pulisci', onClick: () => clearFile(true), icon: _jsx(IconClear, { fontSize: 22 }) })] }), extensionHandler(fileExt) === FileExtensionHandler.READY_TO_SHOW ? _jsx(TMFileViewer, { fileBlob: uploadedFile, isResizingActive: isResizingActive }) :
|
|
102
|
-
_jsx("div", { style: { backgroundColor: '#f6dbdb', padding: '5px 10px', borderRadius: 8, display: 'flex', alignItems: 'center', justifyContent: 'space-between', color: TMColors.error }, children: _jsxs("div", { children: [" ",
|
|
102
|
+
_jsx("div", { style: { backgroundColor: '#f6dbdb', padding: '5px 10px', borderRadius: 8, display: 'flex', alignItems: 'center', justifyContent: 'space-between', color: TMColors.error }, children: _jsxs("div", { children: [" ", SDKUI_Localizator.PreviewNotAvailable, fileExt && _jsx("b", { children: ` (*.${fileExt})` })] }) })] });
|
|
103
103
|
const innerContent = (_jsxs("div", { style: { width: '100%', height: '100%', padding: '2px', display: 'flex', flexDirection: 'column', gap: 10 }, children: [enableDragDropOverlay && _jsx(TMDragDropOverlay, { handleFile: handleFile, refocusAfterFileInput: refocusAfterFileInput }), content] }));
|
|
104
104
|
const toolbar = useMemo(() => {
|
|
105
105
|
return (_jsxs(_Fragment, { children: [(isPdfEditorAvailable(fromDTD, fileExt) && openFileUploaderPdfEditor) && (_jsx(TMCommandsContextMenu, { target: "#TMPanel-FileUploader-Commands-Header", menuItems: [
|
|
@@ -7,14 +7,14 @@ import { IconMultipleSelection, IconCheckFile, IconDetailDcmts, SDKUI_Localizato
|
|
|
7
7
|
import { FormModes, SearchResultContext } from '../../../ts';
|
|
8
8
|
import { TMColors } from '../../../utils/theme';
|
|
9
9
|
import ShowAlert from '../../base/TMAlert';
|
|
10
|
-
import { DeviceType } from '../../base/TMDeviceProvider';
|
|
10
|
+
import { DeviceType, useDeviceType } from '../../base/TMDeviceProvider';
|
|
11
11
|
import { TMSaveFormButtonPrevious, TMSaveFormButtonNext } from '../../forms/TMSaveForm';
|
|
12
12
|
import TMPanelManagerContainer from '../../layout/panelManager/TMPanelManagerContainer';
|
|
13
13
|
import { TMPanelManagerProvider, useTMPanelManagerContext } from '../../layout/panelManager/TMPanelManagerContext';
|
|
14
14
|
import TMSearchResult from '../search/TMSearchResult';
|
|
15
15
|
import TMDcmtForm from './TMDcmtForm';
|
|
16
16
|
import { TMNothingToShow } from './TMDcmtPreview';
|
|
17
|
-
import { Spinner, TMButton } from '../..';
|
|
17
|
+
import { Spinner, TMButton, TMLayoutWaitingContainer } from '../..';
|
|
18
18
|
import { useDocumentOperations } from '../../../hooks/useDocumentOperations';
|
|
19
19
|
import TMToppyMessage from '../../../helper/TMToppyMessage';
|
|
20
20
|
import TMPanel from '../../base/TMPanel';
|
|
@@ -29,6 +29,8 @@ const TMMasterDetailDcmts = ({ allTasks = [], getAllTasks, deleteTaskByIdsCallba
|
|
|
29
29
|
const [isCheckingFirstLoad, setIsCheckingFirstLoad] = useState(true);
|
|
30
30
|
const [contextMenuVisible, setContextMenuVisible] = useState(false);
|
|
31
31
|
const [contextMenuPosition, setContextMenuPosition] = useState({ x: 0, y: 0 });
|
|
32
|
+
// Track if TMRelationViewer is loading (used to disable context menu during loading)
|
|
33
|
+
const [isRelationViewerLoading, setIsRelationViewerLoading] = useState(false);
|
|
32
34
|
const [dtdFocused, setDtdFocused] = useState();
|
|
33
35
|
const [refreshKey, setRefreshKey] = useState(0);
|
|
34
36
|
// Separate refresh key for TMFormOrResultWrapper only (doesn't affect tmTreeView)
|
|
@@ -215,6 +217,7 @@ const TMMasterDetailDcmts = ({ allTasks = [], getAllTasks, deleteTaskByIdsCallba
|
|
|
215
217
|
onRefreshAfterAddDcmtToFavs,
|
|
216
218
|
},
|
|
217
219
|
});
|
|
220
|
+
const { dcmtOperations: { abortController, showWaitPanel, showPrimary, waitPanelTitle, waitPanelTextPrimary, waitPanelValuePrimary, waitPanelMaxValuePrimary, showSecondary, waitPanelTextSecondary, waitPanelValueSecondary, waitPanelMaxValueSecondary, }, } = features;
|
|
218
221
|
// Load dtdMaster when inputDcmts changes
|
|
219
222
|
useEffect(() => {
|
|
220
223
|
const loadDtdMaster = async () => {
|
|
@@ -267,14 +270,14 @@ const TMMasterDetailDcmts = ({ allTasks = [], getAllTasks, deleteTaskByIdsCallba
|
|
|
267
270
|
const commandsMenuItems = [
|
|
268
271
|
{
|
|
269
272
|
icon: _jsx(IconMultipleSelection, { color: allowMultipleSelection ? TMColors.tertiary : TMColors.text_normal }),
|
|
270
|
-
name: "
|
|
273
|
+
name: allowMultipleSelection ? "Disabilita selezione multipla" : "Abilita selezione multipla",
|
|
271
274
|
onClick: () => {
|
|
272
275
|
setAllowMultipleSelection(prev => !prev);
|
|
273
276
|
}
|
|
274
277
|
},
|
|
275
278
|
{
|
|
276
|
-
icon: _jsx(IconCheckFile, {}),
|
|
277
|
-
name: "Consenti dettagli con 0 documenti",
|
|
279
|
+
icon: _jsx(IconCheckFile, { color: showZeroDcmts ? TMColors.tertiary : TMColors.text_normal }),
|
|
280
|
+
name: showZeroDcmts ? "Nascondi dettagli con 0 documenti" : "Consenti dettagli con 0 documenti",
|
|
278
281
|
onClick: () => {
|
|
279
282
|
setShowZeroDcmts(prev => !prev);
|
|
280
283
|
}
|
|
@@ -296,17 +299,22 @@ const TMMasterDetailDcmts = ({ allTasks = [], getAllTasks, deleteTaskByIdsCallba
|
|
|
296
299
|
_jsx(TMNothingToShow, { text: getTitle(), secondText: SDKUI_Localizator.NoDataToDisplay, icon: isForMaster ? _jsx(IconDetailDcmts, { fontSize: 96, transform: 'scale(-1, 1)' }) : _jsx(IconDetailDcmts, { fontSize: 96 }) })
|
|
297
300
|
:
|
|
298
301
|
_jsxs("div", { ref: floatingBarContainerRef, style: { width: "100%", height: "100%" }, onContextMenu: (e) => {
|
|
302
|
+
// Disable context menu when loading
|
|
303
|
+
if (isRelationViewerLoading) {
|
|
304
|
+
e.preventDefault();
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
299
307
|
// Mostra context menu anche sullo spazio bianco (quando non si clicca su un item)
|
|
300
308
|
e.preventDefault();
|
|
301
309
|
setContextMenuPosition({ x: e.clientX, y: e.clientY });
|
|
302
310
|
setContextMenuVisible(true);
|
|
303
311
|
}, children: [_jsx(TMRelationViewerWrapper, { refreshKey: refreshKey, inputDcmts: inputDcmts, isForMaster: isForMaster, showCurrentDcmtIndicator: showCurrentDcmtIndicator, showZeroDcmts: showZeroDcmts,
|
|
304
312
|
// customItemRender={customItemRender}
|
|
305
|
-
allowMultipleSelection: allowMultipleSelection, focusedItem: focusedItem, selectedItems: selectedItems, onFocusedItemChanged: handleFocusedItemChanged, onSelectedItemsChanged: handleSelectedItemsChanged, onNoRelationsFound: handleNoRelationsFound, onItemContextMenu: onItemContextMenu, focusedItemFormData: focusedItemFormData }), _jsx(TMContextMenu, { items: operationItems, externalControl: {
|
|
313
|
+
allowMultipleSelection: allowMultipleSelection, focusedItem: focusedItem, selectedItems: selectedItems, onFocusedItemChanged: handleFocusedItemChanged, onSelectedItemsChanged: handleSelectedItemsChanged, onNoRelationsFound: handleNoRelationsFound, onItemContextMenu: isRelationViewerLoading ? undefined : onItemContextMenu, focusedItemFormData: focusedItemFormData, onLoadingStateChanged: setIsRelationViewerLoading }), _jsx(TMContextMenu, { items: operationItems, externalControl: {
|
|
306
314
|
visible: contextMenuVisible,
|
|
307
315
|
position: contextMenuPosition,
|
|
308
316
|
onClose: () => setContextMenuVisible(false)
|
|
309
|
-
} })] }) }), [inputDcmts, isForMaster, showCurrentDcmtIndicator, showZeroDcmts, allowMultipleSelection, focusedItem, selectedItems, handleFocusedItemChanged, handleSelectedItemsChanged, handleNoRelationsFound, onItemContextMenu, contextMenuVisible, contextMenuPosition, refreshKey, focusedItemFormData]);
|
|
317
|
+
} })] }) }), [inputDcmts, isForMaster, showCurrentDcmtIndicator, showZeroDcmts, allowMultipleSelection, focusedItem, selectedItems, handleFocusedItemChanged, handleSelectedItemsChanged, handleNoRelationsFound, onItemContextMenu, contextMenuVisible, contextMenuPosition, refreshKey, focusedItemFormData, isRelationViewerLoading]);
|
|
310
318
|
const tmFormOrResult = useMemo(() => _jsx(TMFormOrResultWrapper, { refreshKey: refreshKeyFormOrResult, deviceType: deviceType, focusedItem: focusedItem, onTaskCreateRequest: onTaskCreateRequest, allTasks: allTasks, getAllTasks: getAllTasks, deleteTaskByIdsCallback: deleteTaskByIdsCallback, addTaskCallback: addTaskCallback, editTaskCallback: editTaskCallback, handleNavigateToWGs: handleNavigateToWGs, handleNavigateToDossiers: handleNavigateToDossiers, handleNavigateToReference: handleNavigateToReference, onRefreshAfterAddDcmtToFavs: onRefreshAfterAddDcmtToFavs, editPdfForm: editPdfForm, openS4TViewer: openS4TViewer, onOpenS4TViewerRequest: onOpenS4TViewerRequest, onOpenPdfEditorRequest: onOpenPdfEditorRequest, onRefreshSearchResults: onRefreshAllPanels }), [focusedItem, deviceType, allTasks, handleNavigateToWGs, handleNavigateToDossiers, handleNavigateToReference, editPdfForm, openS4TViewer, onOpenS4TViewerRequest, onOpenPdfEditorRequest, onRefreshAfterAddDcmtToFavs, refreshKeyFormOrResult]);
|
|
311
319
|
const initialPanelDimensions = {
|
|
312
320
|
'tmTreeView': { width: '50%', height: '100%' },
|
|
@@ -384,7 +392,7 @@ const TMMasterDetailDcmts = ({ allTasks = [], getAllTasks, deleteTaskByIdsCallba
|
|
|
384
392
|
toolbarOptions: { icon: _jsx(IconSearchCheck, { fontSize: 24 }), visible: false, orderNumber: 2, isActive: allInitialPanelVisibility['tmFormOrResult'] }
|
|
385
393
|
}
|
|
386
394
|
], [tmTreeView, tmFormOrResult, focusedItem?.isDcmt, dtdMaster]);
|
|
387
|
-
return (_jsxs("div", { style: { width: '100%', height: '100%', position: 'relative' }, children: [isCheckingFirstLoad && (_jsx(Spinner, { description: SDKUI_Localizator.Loading, flat: true })), _jsxs("div", { style: isCheckingFirstLoad ? { position: 'absolute', width: 0, height: 0, overflow: 'hidden', opacity: 0, pointerEvents: 'none' } : { width: '100%', height: '100%' }, children: [_jsx(TMPanelManagerProvider, { panels: initialPanels, initialVisibility: allInitialPanelVisibility, defaultDimensions: initialPanelDimensions, initialDimensions: initialPanelDimensions, initialMobilePanelId: 'tmTreeView', children: _jsx(TMPanelManagerContainer, { panels: initialPanels, direction: "horizontal", showToolbar: true }) }), renderDcmtOperations, renderFloatingBar] })] }));
|
|
395
|
+
return (_jsxs("div", { style: { width: '100%', height: '100%', position: 'relative' }, children: [isCheckingFirstLoad && (_jsx(Spinner, { description: SDKUI_Localizator.Loading, flat: true })), _jsxs("div", { style: isCheckingFirstLoad ? { position: 'absolute', width: 0, height: 0, overflow: 'hidden', opacity: 0, pointerEvents: 'none' } : { width: '100%', height: '100%' }, children: [_jsx(TMLayoutWaitingContainer, { direction: 'vertical', showWaitPanel: showWaitPanel, showWaitPanelPrimary: showPrimary, showWaitPanelSecondary: showSecondary, waitPanelTitle: waitPanelTitle, waitPanelTextPrimary: waitPanelTextPrimary, waitPanelValuePrimary: waitPanelValuePrimary, waitPanelMaxValuePrimary: waitPanelMaxValuePrimary, waitPanelTextSecondary: waitPanelTextSecondary, waitPanelValueSecondary: waitPanelValueSecondary, waitPanelMaxValueSecondary: waitPanelMaxValueSecondary, isCancelable: true, abortController: abortController, children: _jsx(TMPanelManagerProvider, { panels: initialPanels, initialVisibility: allInitialPanelVisibility, defaultDimensions: initialPanelDimensions, initialDimensions: initialPanelDimensions, initialMobilePanelId: 'tmTreeView', children: _jsx(TMPanelManagerContainer, { panels: initialPanels, direction: "horizontal", showToolbar: true }) }) }), renderDcmtOperations, renderFloatingBar] })] }));
|
|
388
396
|
};
|
|
389
397
|
export default TMMasterDetailDcmts;
|
|
390
398
|
/**
|
|
@@ -393,8 +401,37 @@ export default TMMasterDetailDcmts;
|
|
|
393
401
|
* - Panel visibility toggling
|
|
394
402
|
* - Focus delay handling
|
|
395
403
|
*/
|
|
396
|
-
const TMRelationViewerWrapper = ({ refreshKey, inputDcmts, isForMaster, showCurrentDcmtIndicator, showZeroDcmts, customItemRender, allowMultipleSelection, focusedItem, selectedItems, onFocusedItemChanged, onSelectedItemsChanged, onNoRelationsFound, onItemContextMenu, focusedItemFormData }) => {
|
|
404
|
+
const TMRelationViewerWrapper = ({ refreshKey, inputDcmts, isForMaster, showCurrentDcmtIndicator, showZeroDcmts, customItemRender, allowMultipleSelection, focusedItem, selectedItems, onFocusedItemChanged, onSelectedItemsChanged, onNoRelationsFound, onItemContextMenu, focusedItemFormData, onLoadingStateChanged }) => {
|
|
397
405
|
const { setPanelVisibilityById, setToolbarButtonVisibility } = useTMPanelManagerContext();
|
|
406
|
+
// Monitor device type changes to restore panel visibility when switching from mobile to desktop
|
|
407
|
+
const deviceType = useDeviceType();
|
|
408
|
+
const prevDeviceTypeRef = useRef(deviceType);
|
|
409
|
+
// Restore panel visibility when switching from mobile to desktop
|
|
410
|
+
useEffect(() => {
|
|
411
|
+
const prevDeviceType = prevDeviceTypeRef.current;
|
|
412
|
+
prevDeviceTypeRef.current = deviceType;
|
|
413
|
+
// When switching from mobile to desktop, restore panel visibility based on current focused item
|
|
414
|
+
if (prevDeviceType === DeviceType.MOBILE && deviceType !== DeviceType.MOBILE) {
|
|
415
|
+
// Small delay to let the panel manager complete its internal state update
|
|
416
|
+
setTimeout(() => {
|
|
417
|
+
// Force tmFormOrResult panel to be visible
|
|
418
|
+
setPanelVisibilityById('tmFormOrResult', true);
|
|
419
|
+
// Restore child panel visibility based on focused item
|
|
420
|
+
if (focusedItem?.isDcmt) {
|
|
421
|
+
setPanelVisibilityById('tmSearchResult', false);
|
|
422
|
+
setPanelVisibilityById('tmDcmtForm', true);
|
|
423
|
+
setToolbarButtonVisibility('tmSearchResult', false);
|
|
424
|
+
setToolbarButtonVisibility('tmDcmtForm', true);
|
|
425
|
+
}
|
|
426
|
+
else {
|
|
427
|
+
setPanelVisibilityById('tmSearchResult', true);
|
|
428
|
+
setPanelVisibilityById('tmDcmtForm', false);
|
|
429
|
+
setToolbarButtonVisibility('tmSearchResult', true);
|
|
430
|
+
setToolbarButtonVisibility('tmDcmtForm', false);
|
|
431
|
+
}
|
|
432
|
+
}, 50);
|
|
433
|
+
}
|
|
434
|
+
}, [deviceType, focusedItem, setPanelVisibilityById, setToolbarButtonVisibility]);
|
|
398
435
|
// Handle focused item changes with panel visibility management
|
|
399
436
|
const handleFocusedItemChanged = useCallback((item) => {
|
|
400
437
|
onFocusedItemChanged?.(item);
|
|
@@ -424,7 +461,7 @@ const TMRelationViewerWrapper = ({ refreshKey, inputDcmts, isForMaster, showCurr
|
|
|
424
461
|
onItemContextMenu?.(item, e);
|
|
425
462
|
}, 100);
|
|
426
463
|
}, [onItemContextMenu, handleFocusedItemChanged]);
|
|
427
|
-
return (_jsx(TMRelationViewer, { inputDcmts: inputDcmts, isForMaster: isForMaster, showCurrentDcmtIndicator: showCurrentDcmtIndicator, initialShowZeroDcmts: showZeroDcmts, customItemRender: customItemRender, allowMultipleSelection: allowMultipleSelection, focusedItem: focusedItem, selectedItems: selectedItems, onFocusedItemChanged: handleFocusedItemChanged, onSelectedItemsChanged: onSelectedItemsChanged, maxDepthLevel: 1, invertMasterNavigation: false, showExpandAllButton: true, onNoRelationsFound: onNoRelationsFound, onItemContextMenu: onContextMenu, focusedItemFormData: focusedItemFormData }, refreshKey));
|
|
464
|
+
return (_jsx(TMRelationViewer, { inputDcmts: inputDcmts, isForMaster: isForMaster, showCurrentDcmtIndicator: showCurrentDcmtIndicator, initialShowZeroDcmts: showZeroDcmts, customItemRender: customItemRender, allowMultipleSelection: allowMultipleSelection, focusedItem: focusedItem, selectedItems: selectedItems, onFocusedItemChanged: handleFocusedItemChanged, onSelectedItemsChanged: onSelectedItemsChanged, maxDepthLevel: 1, invertMasterNavigation: false, showExpandAllButton: true, onNoRelationsFound: onNoRelationsFound, onItemContextMenu: onContextMenu, focusedItemFormData: focusedItemFormData, onLoadingStateChanged: onLoadingStateChanged }, refreshKey));
|
|
428
465
|
};
|
|
429
466
|
const TMFormOrResultWrapper = ({ refreshKey, deviceType, focusedItem, onTaskCreateRequest, allTasks = [], getAllTasks, deleteTaskByIdsCallback, addTaskCallback, editTaskCallback, handleNavigateToWGs, handleNavigateToDossiers, onRefreshAfterAddDcmtToFavs, editPdfForm, openS4TViewer, onOpenS4TViewerRequest, onOpenPdfEditorRequest, onRefreshSearchAsyncDatagrid, onRefreshSearchResults, handleNavigateToReference, fetchRemoteCertificates }) => {
|
|
430
467
|
const { setPanelVisibilityById } = useTMPanelManagerContext();
|
|
@@ -73,7 +73,10 @@ export interface TMRelationViewerProps {
|
|
|
73
73
|
* Value rendering respects DataDomain (uses TMDataListItemViewer for lists, TMDataUserIdItemViewer for users, etc.)
|
|
74
74
|
*/
|
|
75
75
|
showMetadataNames?: boolean;
|
|
76
|
-
/**
|
|
76
|
+
/**
|
|
77
|
+
* Maximum depth level for recursive loading (default: 2).
|
|
78
|
+
* Use 0 for unlimited depth (expand as much as possible).
|
|
79
|
+
*/
|
|
77
80
|
maxDepthLevel?: number;
|
|
78
81
|
/**
|
|
79
82
|
* If true (default), when isForMaster=true shows: detail doc → master docs as children (inverted navigation)
|
|
@@ -123,6 +126,11 @@ export interface TMRelationViewerProps {
|
|
|
123
126
|
* (root container + first document + first correlation folder).
|
|
124
127
|
*/
|
|
125
128
|
defaultExpandAll?: boolean;
|
|
129
|
+
/**
|
|
130
|
+
* Callback invoked when loading state changes.
|
|
131
|
+
* Useful to disable interactions (like context menu) during loading.
|
|
132
|
+
*/
|
|
133
|
+
onLoadingStateChanged?: (isLoading: boolean) => void;
|
|
126
134
|
}
|
|
127
135
|
/**
|
|
128
136
|
* Check if document type has detail relations
|
|
@@ -140,5 +148,6 @@ export declare const getDisplayValueByColumn: (col: DataColumnDescriptor | undef
|
|
|
140
148
|
* Convert SearchResultDescriptor to structured data source with metadata
|
|
141
149
|
*/
|
|
142
150
|
export declare const searchResultToDataSource: (searchResult: SearchResultDescriptor | undefined, hideSysMetadata?: boolean) => Promise<any[]>;
|
|
151
|
+
export declare const DEFAULT_RELATION_EXPAND_LEVEL = 4;
|
|
143
152
|
declare const TMRelationViewer: React.FC<TMRelationViewerProps>;
|
|
144
153
|
export default TMRelationViewer;
|