@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.
@@ -1,16 +1,18 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import React, { useCallback, useEffect, useMemo, useState } from 'react';
3
3
  import { DcmtTypeListCacheService, SDK_Globals, DataColumnTypes, MetadataFormats, MetadataDataDomains, RelationCacheService, RelationTypes, UserListCacheService } from "@topconsultnpm/sdk-ts";
4
- import { genUniqueId, IconFolder, IconBackhandIndexPointingRight, IconCircleInfo, getDcmtCicoStatus, IconChevronDown, IconChevronRight, SDKUI_Localizator, buildDcmtDisplayName } from '../../../helper';
4
+ import { genUniqueId, IconFolder, IconBackhandIndexPointingRight, IconCircleInfo, getDcmtCicoStatus, IconChevronDown, IconChevronRight, SDKUI_Localizator, buildDcmtDisplayName, SDKUI_Globals } from '../../../helper';
5
5
  import ShowAlert from '../../base/TMAlert';
6
6
  import TMToppyMessage from '../../../helper/TMToppyMessage';
7
7
  import { TMColors } from '../../../utils/theme';
8
- import { StyledDivHorizontal, StyledBadge, StyledToolbarForm } from '../../base/Styled';
8
+ import { StyledDivHorizontal, StyledBadge } from '../../base/Styled';
9
9
  import TMTreeView from '../../base/TMTreeView';
10
10
  import { TMWaitPanel } from '../../base/TMWaitPanel';
11
11
  import TMDataListItemViewer from '../../viewers/TMDataListItemViewer';
12
12
  import TMDcmtIcon from './TMDcmtIcon';
13
13
  import TMDataUserIdItemViewer from '../../viewers/TMDataUserIdItemViewer';
14
+ import TMTooltip from '../../base/TMTooltip';
15
+ import TMSpinner from '../../base/TMSpinner';
14
16
  /**
15
17
  * Check if document type has detail relations
16
18
  */
@@ -124,13 +126,16 @@ export const searchResultToDataSource = async (searchResult, hideSysMetadata) =>
124
126
  }
125
127
  return output;
126
128
  };
127
- const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndicator = true, allowShowZeroDcmts = true, initialShowZeroDcmts = false, allowedTIDs, allowMultipleSelection = false, focusedItem, selectedItems, onFocusedItemChanged, onSelectedItemsChanged, onAllItemsChanged, onDocumentDoubleClick, customItemRender, customDocumentStyle, customMainContainerContent, customDocumentContent, showMetadataNames = false, maxDepthLevel = 2, invertMasterNavigation = true, additionalStaticItems, showMainDocument = true, labelMainContainer, onNoRelationsFound, onItemContextMenu, focusedItemFormData = [], showExpandAllButton = false, defaultExpandAll = false }) => {
129
+ export const DEFAULT_RELATION_EXPAND_LEVEL = 4; // Default maximum depth level for expansion (can be adjusted)
130
+ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndicator = true, allowShowZeroDcmts = true, initialShowZeroDcmts = false, allowedTIDs, allowMultipleSelection = false, focusedItem, selectedItems, onFocusedItemChanged, onSelectedItemsChanged, onAllItemsChanged, onDocumentDoubleClick, customItemRender, customDocumentStyle, customMainContainerContent, customDocumentContent, showMetadataNames = false, maxDepthLevel = 2, invertMasterNavigation = true, additionalStaticItems, showMainDocument = true, labelMainContainer, onNoRelationsFound, onItemContextMenu, focusedItemFormData = [], showExpandAllButton = false, defaultExpandAll = false, onLoadingStateChanged }) => {
128
131
  // State
129
132
  const [dcmtTypes, setDcmtTypes] = useState([]);
130
133
  const [treeData, setTreeData] = useState([]);
131
134
  const [showZeroDcmts, setShowZeroDcmts] = useState(initialShowZeroDcmts);
132
135
  const [staticItemsState, setStaticItemsState] = useState([]);
133
- const [allExpanded, setAllExpanded] = useState(defaultExpandAll);
136
+ const getInitialExpandLevel = () => SDKUI_Globals.userSettings?.searchSettings?.relationExpandLevel ?? DEFAULT_RELATION_EXPAND_LEVEL;
137
+ const [expandLevel, setExpandLevel] = useState(getInitialExpandLevel);
138
+ const [isAllCollapsed, setIsAllCollapsed] = useState(false);
134
139
  const initialExpandAllAppliedRef = React.useRef(false);
135
140
  const [showWaitPanel, setShowWaitPanel] = useState(false);
136
141
  const [waitPanelTextPrimary, setWaitPanelTextPrimary] = useState('');
@@ -164,6 +169,18 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
164
169
  };
165
170
  fetchAllUsers();
166
171
  }, []);
172
+ // Notify parent when loading state changes
173
+ useEffect(() => {
174
+ const isLoading = showWaitPanel || showExpansionWaitPanel;
175
+ onLoadingStateChanged?.(isLoading);
176
+ }, [showWaitPanel, showExpansionWaitPanel, onLoadingStateChanged]);
177
+ // Sincronizza expandLevel quando userSettings.searchSettings.relationExpandLevel cambia
178
+ useEffect(() => {
179
+ const globalExpandLevel = SDKUI_Globals.userSettings?.searchSettings?.relationExpandLevel;
180
+ if (globalExpandLevel !== undefined && globalExpandLevel !== expandLevel) {
181
+ setExpandLevel(globalExpandLevel);
182
+ }
183
+ }, [SDKUI_Globals.userSettings?.searchSettings?.relationExpandLevel]);
167
184
  /**
168
185
  * Generate a stable key from inputDcmts to detect real changes
169
186
  */
@@ -192,7 +209,10 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
192
209
  return [];
193
210
  if (!mDID)
194
211
  return [];
195
- if (maxLevel <= 0)
212
+ // maxLevel = 0 means unlimited depth (expand as much as possible)
213
+ // maxLevel > 0 means specific depth limit
214
+ // maxLevel < 0 means stop recursion
215
+ if (maxLevel < 0)
196
216
  return [];
197
217
  const dcmtTypeHasRel = await hasDetailRelations(mTID);
198
218
  if (!dcmtTypeHasRel)
@@ -275,7 +295,10 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
275
295
  return [];
276
296
  if (!dDID)
277
297
  return [];
278
- if (maxLevel <= 0)
298
+ // maxLevel = 0 means unlimited depth (expand as much as possible)
299
+ // maxLevel > 0 means specific depth limit
300
+ // maxLevel < 0 means stop recursion
301
+ if (maxLevel < 0)
279
302
  return [];
280
303
  const dcmtTypeHasRel = await hasMasterRelations(dTID);
281
304
  if (!dcmtTypeHasRel)
@@ -1214,13 +1237,231 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
1214
1237
  return { ...node, expanded: canExpand ? expanded : node.expanded, items: newItems };
1215
1238
  });
1216
1239
  }, []);
1217
- const handleToggleExpandAll = useCallback(() => {
1218
- const next = !allExpanded;
1219
- setAllExpanded(next);
1220
- setTreeData(prev => setExpandedRecursively(prev, next));
1221
- setStaticItemsState(prev => setExpandedRecursively(prev, next));
1222
- userInteractedWithStaticItemsRef.current = true;
1223
- }, [allExpanded, setExpandedRecursively]);
1240
+ // Collassa l'albero impostando tutti i nodi come non espansi
1241
+ const handleCollapseTree = useCallback(() => {
1242
+ // Funzione helper per collassare ricorsivamente tutti i nodi
1243
+ const collapseAllNodes = (nodes) => {
1244
+ return nodes.map(node => ({
1245
+ ...node,
1246
+ expanded: false,
1247
+ items: node.items ? collapseAllNodes(node.items) : node.items
1248
+ }));
1249
+ };
1250
+ // Collassa tutti i nodi dell'albero
1251
+ setTreeData(prevData => collapseAllNodes(prevData));
1252
+ setStaticItemsState(prevData => collapseAllNodes(prevData));
1253
+ setIsAllCollapsed(true);
1254
+ }, []);
1255
+ /**
1256
+ * Espande ricorsivamente l'albero fino a `expandLevel`.
1257
+ * Carica i figli lazy (API) solo se non già in memoria (isLoaded).
1258
+ * Il contatore totalNodes cresce dinamicamente per una % progresso accurata.
1259
+ *
1260
+ * @param forceReload - Se true (Ctrl+click), ricarica tutto ignorando la cache
1261
+ */
1262
+ const handleExpandToLevel = useCallback(async (forceReload = false) => {
1263
+ // Mostra spinner per feedback immediato all'utente
1264
+ TMSpinner.show({ description: forceReload ? SDKUI_Localizator.FullReloadInProgress : SDKUI_Localizator.ExpansionInProgress });
1265
+ // Piccolo delay per permettere allo spinner di renderizzarsi prima del processing pesante
1266
+ await new Promise(resolve => setTimeout(resolve, 50));
1267
+ /**
1268
+ * Se forceReload è true, prima resettiamo tutti i nodi come se non fossero mai stati caricati.
1269
+ * Questo significa rimuovere isLoaded e items da tutti i nodi documento,
1270
+ * così l'espansione successiva li ricaricherà tutti da API.
1271
+ */
1272
+ const resetLoadedState = (nodes) => {
1273
+ return nodes.map(node => {
1274
+ const resetNode = { ...node };
1275
+ // Reset isLoaded per forzare il ricaricamento
1276
+ if (resetNode.isDcmt || resetNode.isExpandible) {
1277
+ resetNode.isLoaded = false;
1278
+ // Manteniamo items solo per i container (cartelle),
1279
+ // perché i container non vengono ricaricati via API
1280
+ // I documenti invece devono ricaricare i loro figli (correlazioni)
1281
+ if (resetNode.isDcmt) {
1282
+ resetNode.items = undefined;
1283
+ resetNode.itemsCount = undefined;
1284
+ }
1285
+ }
1286
+ // Reset ricorsivo sui figli
1287
+ if (resetNode.items && Array.isArray(resetNode.items)) {
1288
+ resetNode.items = resetLoadedState(resetNode.items);
1289
+ }
1290
+ return resetNode;
1291
+ });
1292
+ };
1293
+ // Se forceReload, reset dello stato prima di iniziare
1294
+ let workingTreeData = treeData;
1295
+ let workingStaticData = staticItemsState;
1296
+ if (forceReload) {
1297
+ workingTreeData = resetLoadedState(treeData);
1298
+ workingStaticData = resetLoadedState(staticItemsState);
1299
+ // Aggiorna subito lo stato per riflettere il reset
1300
+ setTreeData(workingTreeData);
1301
+ setStaticItemsState(workingStaticData);
1302
+ }
1303
+ const targetLevel = expandLevel;
1304
+ const newAbortController = new AbortController();
1305
+ setExpansionAbortController(newAbortController);
1306
+ // CONTATORI DINAMICI:
1307
+ // - totalNodes: cresce quando scopriamo nuovi nodi (evita che percentuale > 100%)
1308
+ // - processedNodes: quanti nodi abbiamo già processato
1309
+ // - maxReachedProgress: percentuale massima raggiunta (evita che la barra torni indietro)
1310
+ let totalNodes = 0;
1311
+ let processedNodes = 0;
1312
+ let hasApiCalls = false;
1313
+ let maxReachedProgress = 0;
1314
+ /**
1315
+ * Conta i nodi espandibili in un array (solo primo livello, non ricorsivo).
1316
+ * Usato per aggiornare totalNodes man mano che scopriamo nuovi figli.
1317
+ */
1318
+ const countNodesAtLevel = (nodes) => {
1319
+ let count = 0;
1320
+ for (const node of nodes) {
1321
+ if (node.isContainer || node.isExpandible || node.isDcmt) {
1322
+ count++;
1323
+ }
1324
+ }
1325
+ return count;
1326
+ };
1327
+ // Conta iniziale: solo i nodi al livello root
1328
+ totalNodes = countNodesAtLevel([...workingTreeData, ...workingStaticData]);
1329
+ /**
1330
+ * FUNZIONE RICORSIVA DI ESPANSIONE
1331
+ *
1332
+ * @param nodes - Array di nodi da processare
1333
+ * @param currentLevel - Livello corrente (0 = root)
1334
+ * @returns Array di nodi con expanded=true e figli caricati
1335
+ *
1336
+ * FLUSSO:
1337
+ * 1. Se targetLevel > 0 e currentLevel >= targetLevel → STOP, ritorna i nodi così come sono
1338
+ * (Se targetLevel = 0 → espandi senza limiti, non applicare mai il limite)
1339
+ * 2. Per ogni nodo espandibile:
1340
+ * a) Imposta expanded = true
1341
+ * b) Se i figli non sono caricati → chiama API per caricarli
1342
+ * c) Aggiorna totalNodes con i nuovi figli scoperti
1343
+ * d) CHIAMATA RICORSIVA su expandedNode.items con currentLevel + 1
1344
+ * → QUESTO È IL PUNTO CHIAVE: i nuovi nodi vengono espansi!
1345
+ */
1346
+ const expandToLevel = async (nodes, currentLevel) => {
1347
+ // CONDIZIONE DI USCITA: abbiamo raggiunto il livello target
1348
+ // targetLevel = 0 significa espandi senza limiti (non applicare mai il limite)
1349
+ // targetLevel > 0 significa applica il limite normalmente
1350
+ if (targetLevel > 0 && currentLevel >= targetLevel)
1351
+ return nodes;
1352
+ // Controlla se l'utente ha annullato l'operazione
1353
+ if (newAbortController.signal.aborted)
1354
+ return nodes;
1355
+ const result = [];
1356
+ for (const node of nodes) {
1357
+ // Controlla abort per ogni nodo (permette cancellazione veloce)
1358
+ if (newAbortController.signal.aborted) {
1359
+ result.push(node);
1360
+ continue;
1361
+ }
1362
+ // Crea copia del nodo (immutabilità React)
1363
+ let expandedNode = { ...node };
1364
+ // Verifica se questo nodo può essere espanso
1365
+ const canExpand = node.isContainer || node.isExpandible || node.isDcmt;
1366
+ if (canExpand) {
1367
+ // PASSO 1: Imposta il nodo come espanso
1368
+ expandedNode.expanded = true;
1369
+ // PASSO 2: Verifica se i figli sono già caricati o vanno caricati via API
1370
+ // Se forceReload è true, ricarica sempre (ignora isLoaded e items esistenti)
1371
+ const hasLoadedItems = !forceReload && node.items && Array.isArray(node.items) && node.items.length > 0;
1372
+ const actualHasItems = node.items && Array.isArray(node.items) && node.items.length > 0;
1373
+ const needsLoading = (forceReload || (!hasLoadedItems && !node.isLoaded)) && (node.isExpandible || node.isDcmt);
1374
+ if (needsLoading && node.tid && node.did) {
1375
+ // ═══════════════════════════════════════════════════════════════
1376
+ // CASO A: I figli NON sono in memoria → CHIAMATA API
1377
+ // ═══════════════════════════════════════════════════════════════
1378
+ // Mostra pannello di attesa solo quando ci sono chiamate API
1379
+ if (!hasApiCalls) {
1380
+ hasApiCalls = true;
1381
+ TMSpinner.hide();
1382
+ setShowExpansionWaitPanel(true);
1383
+ setExpansionWaitPanelMaxValue(100);
1384
+ setExpansionWaitPanelValue(0);
1385
+ }
1386
+ try {
1387
+ let loadedItems = [];
1388
+ // Carica i documenti correlati (detail o master in base alla modalità)
1389
+ if (isForMaster && !invertMasterNavigation) {
1390
+ loadedItems = await getDetailDcmtsAsync(node.tid, node.did, 1);
1391
+ }
1392
+ else if (isForMaster) {
1393
+ loadedItems = await getMasterDcmtsAsync(node.tid, node.did, 1);
1394
+ }
1395
+ else {
1396
+ loadedItems = await getDetailDcmtsAsync(node.tid, node.did, 1);
1397
+ }
1398
+ // Applica le regole di visibilità (showZeroDcmts)
1399
+ expandedNode.items = updateHiddenProperty(loadedItems);
1400
+ expandedNode.isLoaded = true;
1401
+ expandedNode.itemsCount = loadedItems.length;
1402
+ // IMPORTANTE: Aggiungi i nuovi nodi scoperti al contatore totale
1403
+ // Questo viene fatto SOLO se non siamo all'ultimo livello
1404
+ // (perché i nodi dell'ultimo livello non verranno espansi)
1405
+ // targetLevel = 0 significa illimitato, quindi aggiungi sempre
1406
+ if (targetLevel === 0 || currentLevel + 1 < targetLevel) {
1407
+ const newExpandableCount = countNodesAtLevel(loadedItems);
1408
+ totalNodes += newExpandableCount;
1409
+ }
1410
+ }
1411
+ catch (error) {
1412
+ console.error('Errore nel caricamento dei figli del nodo:', error);
1413
+ }
1414
+ }
1415
+ else if (actualHasItems && (targetLevel === 0 || currentLevel + 1 < targetLevel)) {
1416
+ // ═══════════════════════════════════════════════════════════════
1417
+ // CASO B: I figli esistono già in memoria (non ricaricati via API)
1418
+ // Aggiungiamo comunque il loro conteggio a totalNodes
1419
+ // Usiamo actualHasItems invece di hasLoadedItems per gestire anche forceReload
1420
+ // ═══════════════════════════════════════════════════════════════
1421
+ const childCount = countNodesAtLevel(node.items);
1422
+ totalNodes += childCount;
1423
+ }
1424
+ // Aggiorna il progresso DOPO aver processato questo nodo
1425
+ processedNodes++;
1426
+ // Calcola percentuale e aggiorna solo se è più alta di prima
1427
+ // (evita che la barra di progresso torni indietro)
1428
+ const currentProgress = totalNodes > 0 ? Math.floor((processedNodes / totalNodes) * 100) : 0;
1429
+ if (currentProgress > maxReachedProgress) {
1430
+ maxReachedProgress = currentProgress;
1431
+ setExpansionWaitPanelValue(maxReachedProgress);
1432
+ }
1433
+ setExpansionWaitPanelText(SDKUI_Localizator.ExpansionProgress.replaceParams(processedNodes.toString(), totalNodes.toString()));
1434
+ // ═══════════════════════════════════════════════════════════════════
1435
+ // PASSO 3: CHIAMATA RICORSIVA SUI FIGLI
1436
+ // Questo è il punto chiave: i nuovi nodi appena caricati vengono
1437
+ // passati a expandToLevel con currentLevel + 1, quindi anche loro
1438
+ // verranno espansi (se non abbiamo raggiunto il livello target)
1439
+ // ═══════════════════════════════════════════════════════════════════
1440
+ if (expandedNode.items && Array.isArray(expandedNode.items) && expandedNode.items.length > 0) {
1441
+ expandedNode.items = await expandToLevel(expandedNode.items, currentLevel + 1);
1442
+ }
1443
+ }
1444
+ result.push(expandedNode);
1445
+ }
1446
+ return result;
1447
+ };
1448
+ try {
1449
+ // Avvia l'espansione dal livello 0 (root)
1450
+ const expandedTree = await expandToLevel(workingTreeData, 0);
1451
+ setTreeData(expandedTree);
1452
+ // Espandi anche gli item statici (se presenti)
1453
+ const expandedStatic = await expandToLevel(workingStaticData, 0);
1454
+ setStaticItemsState(expandedStatic);
1455
+ setIsAllCollapsed(false);
1456
+ userInteractedWithStaticItemsRef.current = true;
1457
+ }
1458
+ finally {
1459
+ // Cleanup: nascondi spinner e pannello di attesa
1460
+ TMSpinner.hide();
1461
+ setShowExpansionWaitPanel(false);
1462
+ setExpansionAbortController(undefined);
1463
+ }
1464
+ }, [expandLevel, treeData, staticItemsState, isForMaster, invertMasterNavigation, getDetailDcmtsAsync, getMasterDcmtsAsync, updateHiddenProperty]);
1224
1465
  /**
1225
1466
  * Apply defaultExpandAll once on initial tree load.
1226
1467
  */
@@ -1246,9 +1487,11 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
1246
1487
  setStaticItemsState(staticItems); // Preserve static items state (expanded/collapsed)
1247
1488
  // Mark that user has interacted with the tree (expanded/collapsed nodes)
1248
1489
  userInteractedWithStaticItemsRef.current = true;
1490
+ // Reset collapsed state when user manually expands a node
1491
+ setIsAllCollapsed(false);
1249
1492
  }, []);
1250
1493
  if (showWaitPanel) {
1251
- return _jsx("div", { style: { padding: '20px', textAlign: 'center' }, children: _jsx(TMWaitPanel, { title: 'Caricamento documenti dettaglio', showPrimary: true, textPrimary: waitPanelTextPrimary, valuePrimary: waitPanelValuePrimary, maxValuePrimary: waitPanelMaxValuePrimary, isCancelable: true, abortController: abortController, onAbortClick: (abortController) => { setTimeout(() => { abortController?.abort(); }, 1000); } }) });
1494
+ return _jsx("div", { style: { padding: '20px', textAlign: 'center' }, children: _jsx(TMWaitPanel, { title: SDKUI_Localizator.LoadingDetailDocuments, showPrimary: true, textPrimary: waitPanelTextPrimary, valuePrimary: waitPanelValuePrimary, maxValuePrimary: waitPanelMaxValuePrimary, isCancelable: true, abortController: abortController, onAbortClick: () => { setTimeout(() => { abortController.abort(); }, 1000); } }) });
1252
1495
  }
1253
1496
  if (mergedTreeData.length === 0) {
1254
1497
  // Se NON isForMaster e tutti i GetMetadataAsync sono falliti
@@ -1264,27 +1507,50 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
1264
1507
  if (isForMaster && totalMasterDocuments === 0) {
1265
1508
  return _jsx(TMToppyMessage, { message: SDKUI_Localizator.NoMasterDocumentsAvailable });
1266
1509
  }
1267
- return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', height: '100%', minHeight: 0, width: '100%' }, children: [showExpandAllButton && (_jsx(StyledToolbarForm, { style: { flexShrink: 0 }, children: _jsxs("button", { type: "button", onClick: handleToggleExpandAll, title: allExpanded ? SDKUI_Localizator.CollapseAll : SDKUI_Localizator.ExpandAll, style: {
1268
- display: 'inline-flex',
1269
- alignItems: 'center',
1270
- gap: '4px',
1271
- padding: '4px 8px',
1272
- fontSize: '0.8rem',
1273
- color: TMColors.primary,
1274
- background: 'transparent',
1275
- border: `1px solid ${TMColors.border_normal}`,
1276
- borderRadius: '4px',
1277
- cursor: 'pointer',
1278
- whiteSpace: 'nowrap',
1279
- outline: 'none'
1280
- }, children: [_jsx("span", { style: { display: 'flex', alignItems: 'center' }, children: allExpanded ? _jsx(IconChevronRight, { fontSize: 14 }) : _jsx(IconChevronDown, { fontSize: 14 }) }), _jsx("span", { children: allExpanded ? SDKUI_Localizator.CollapseAll : SDKUI_Localizator.ExpandAll })] }) })), _jsx("div", { style: { flex: 1, minHeight: 0, overflow: 'auto' }, children: _jsx(TMTreeView, { dataSource: mergedTreeData, itemRender: finalItemRender, calculateItemsForNode: calculateItemsForNode, onDataChanged: handleDataChanged, focusedItem: focusedItem, onFocusedItemChanged: handleFocusedItemChanged, allowMultipleSelection: allowMultipleSelection, selectedItems: selectedItems, itemsPerPage: 100, onSelectionChanged: handleSelectedItemsChanged, onItemContextMenu: onItemContextMenu, shouldDelayFocusOnEvent: (node, e) => {
1510
+ return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', height: '100%', minHeight: 0, width: '100%' }, children: [showExpandAllButton && (_jsxs("div", { style: { flexShrink: 0, display: 'flex', alignItems: 'center', gap: '6px', height: '40px', paddingLeft: '10px', backgroundColor: TMColors.toolbar_background }, children: [_jsx(TMTooltip, { content: expandLevel === 0
1511
+ ? `${SDKUI_Localizator.ExpandAllLevels} (${SDKUI_Localizator.CtrlClickToReloadAll})`
1512
+ : `${SDKUI_Localizator.ExpandToLevel.replaceParams(expandLevel.toString())} (${SDKUI_Localizator.CtrlClickToReloadAll})`, children: _jsxs("button", { type: "button", onClick: (e) => handleExpandToLevel(e.ctrlKey), style: {
1513
+ display: 'inline-flex',
1514
+ alignItems: 'center',
1515
+ justifyContent: 'center',
1516
+ gap: '5px',
1517
+ height: '28px',
1518
+ padding: '0 10px',
1519
+ fontSize: '0.8rem',
1520
+ fontWeight: 500,
1521
+ color: 'white',
1522
+ background: TMColors.primary,
1523
+ border: 'none',
1524
+ borderRadius: '4px',
1525
+ cursor: 'pointer',
1526
+ whiteSpace: 'nowrap',
1527
+ outline: 'none',
1528
+ transition: 'background 0.2s'
1529
+ }, children: [_jsx(IconChevronDown, { fontSize: 14 }), _jsx("span", { children: expandLevel === 0 ? SDKUI_Localizator.ExpandAllLevels : SDKUI_Localizator.ExpandLevels.replaceParams(expandLevel.toString()) })] }) }), _jsx(TMTooltip, { content: SDKUI_Localizator.CollapseTree, children: _jsxs("button", { type: "button", onClick: handleCollapseTree, disabled: isAllCollapsed, style: {
1530
+ display: 'inline-flex',
1531
+ alignItems: 'center',
1532
+ justifyContent: 'center',
1533
+ gap: '5px',
1534
+ height: '28px',
1535
+ padding: '0 10px',
1536
+ fontSize: '0.8rem',
1537
+ fontWeight: 500,
1538
+ color: isAllCollapsed ? '#999' : 'white',
1539
+ background: isAllCollapsed ? '#e0e0e0' : TMColors.secondary || '#6c757d',
1540
+ border: 'none',
1541
+ borderRadius: '4px',
1542
+ cursor: isAllCollapsed ? 'not-allowed' : 'pointer',
1543
+ whiteSpace: 'nowrap',
1544
+ outline: 'none',
1545
+ transition: 'background 0.2s'
1546
+ }, children: [_jsx(IconChevronRight, { fontSize: 14 }), _jsx("span", { children: SDKUI_Localizator.CollapseAll })] }) })] })), _jsx("div", { style: { flex: 1, minHeight: 0, overflow: 'auto' }, children: _jsx(TMTreeView, { dataSource: mergedTreeData, itemRender: finalItemRender, calculateItemsForNode: calculateItemsForNode, onDataChanged: handleDataChanged, focusedItem: focusedItem, onFocusedItemChanged: handleFocusedItemChanged, allowMultipleSelection: allowMultipleSelection, selectedItems: selectedItems, itemsPerPage: 100, onSelectionChanged: handleSelectedItemsChanged, onItemContextMenu: showExpansionWaitPanel ? undefined : onItemContextMenu, enableVirtualization: true, shouldDelayFocusOnEvent: (node, e) => {
1281
1547
  // Ritarda il focus quando si clicca sull'icona del documento
1282
1548
  // per permettere al doppio click di funzionare
1283
1549
  const target = e.target;
1284
1550
  return !!target.closest('.tm-dcmt-icon');
1285
- } }) }), showExpansionWaitPanel && (_jsx(TMWaitPanel, { title: isForMaster ? 'Caricamento documenti master' : 'Caricamento documenti dettaglio', showPrimary: true, textPrimary: expansionWaitPanelText, valuePrimary: expansionWaitPanelValue, maxValuePrimary: expansionWaitPanelMaxValue, isCancelable: true, abortController: expansionAbortController, onAbortClick: (abortController) => {
1551
+ } }) }), showExpansionWaitPanel && (_jsx(TMWaitPanel, { title: isForMaster ? SDKUI_Localizator.LoadingMasterDocuments : SDKUI_Localizator.LoadingDetailDocuments, showPrimary: true, textPrimary: expansionWaitPanelText, valuePrimary: expansionWaitPanelValue, maxValuePrimary: expansionWaitPanelMaxValue, isCancelable: true, abortController: expansionAbortController, onAbortClick: () => {
1286
1552
  setTimeout(() => {
1287
- abortController?.abort();
1553
+ expansionAbortController?.abort();
1288
1554
  }, 100);
1289
1555
  } }))] }));
1290
1556
  };
@@ -84,6 +84,7 @@ export declare class SearchSettings {
84
84
  height: string;
85
85
  };
86
86
  };
87
+ relationExpandLevel: number;
87
88
  }
88
89
  export declare class FloatingMenuBarSettings {
89
90
  orientation?: 'horizontal' | 'vertical';
@@ -122,6 +122,7 @@ export class SearchSettings {
122
122
  this.maxDcmtsToBeReturned = DEFAULT_MAX_DCMTS_TO_BE_RETURNED;
123
123
  this.floatingMenuBar = new FloatingMenuBarSettings();
124
124
  this.panelLayout = {};
125
+ this.relationExpandLevel = 4; // Livello di espansione predefinito per le correlazioni
125
126
  }
126
127
  }
127
128
  export class FloatingMenuBarSettings {
@@ -110,7 +110,9 @@ export declare class SDKUI_Localizator {
110
110
  static get Close(): "Ausgang" | "Close" | "Salida" | "Sortie" | "Saída" | "Chiudi";
111
111
  static get Closed(): "Geschlossen" | "Closed" | "Cerrada" | "Fermée" | "Fechada" | "Chiusa";
112
112
  static get CloseTask(): string;
113
+ static get Collapse(): "Reduzieren" | "Collapse" | "Contraer" | "Réduire" | "Recolher" | "Comprimi";
113
114
  static get CollapseAll(): "Alle reduzieren" | "Collapse all" | "Contraer todo" | "Tout réduire" | "Recolher tudo" | "Comprimi tutto";
115
+ static get CollapseTree(): "Gesamten Baum reduzieren" | "Collapse entire tree" | "Contraer todo el árbol" | "Réduire tout l'arbre" | "Recolher toda a árvore" | "Comprimi tutto l'albero";
114
116
  static get Columns_All_Hide(): "Alle Spalten ausblenden" | "Hide all columns" | "Ocultar todas las columnas" | "Masquer toutes les colonnes" | "Ocultar todas as colunas" | "Nascondi tutte le colonne";
115
117
  static get Columns_All_Show(): "Alle Spalten anzeigen" | "Show all columns" | "Mostrar todas las columnas" | "Afficher toutes les colonnes" | "Mostrar todas as colunas" | "Visualizza tutte le colonne";
116
118
  static get Comment(): string;
@@ -273,6 +275,17 @@ export declare class SDKUI_Localizator {
273
275
  static get ExtractedOn(): "Ausgezogen am" | "Extracted on" | "Extraído el" | "Extrait le" | "Extraído em" | "Estratto il";
274
276
  static get EvaluateResult(): "Bewerten Sie das Ergebnis" | "Evaluate result" | "Valorar el resultado" | "Évalue les résultats" | "Avalia os resultados" | "Valuta il risultato";
275
277
  static get ExpandAll(): "Alle erweitern" | "Expand all" | "Expandir todo" | "Tout développer" | "Expandir tudo" | "Espandi tutto";
278
+ static get ExpandLevels(): "Erweitern ({{0}} Ebenen)" | "Expand ({{0}} levels)" | "Expandir ({{0}} niveles)" | "Développer ({{0}} niveaux)" | "Expandir ({{0}} níveis)" | "Espandi ({{0}} livelli)";
279
+ static get ExpandAllLevels(): "Erweitern (alle Ebenen)" | "Expand (all levels)" | "Expandir (todos los niveles)" | "Développer (tous les niveaux)" | "Expandir (todos os níveis)" | "Espandi (tutti i livelli)";
280
+ static get CtrlClickToReloadAll(): "Strg+Klick = alles neu laden" | "Ctrl+click = reload all" | "Ctrl+clic = recargar todo" | "Ctrl+clic = tout recharger" | "Ctrl+clique = recarregar tudo" | "Ctrl+click = ricarica tutto";
281
+ static get ExpandComplete(): "Erweiterung abgeschlossen" | "Expand complete" | "Expansión completada" | "Expansion terminée" | "Expansão concluída" | "Espansione completata";
282
+ static get ExpandToLevel(): "Erweitern bis Ebene {{0}}" | "Expand to level {{0}}" | "Expandir hasta el nivel {{0}}" | "Développer jusqu'au niveau {{0}}" | "Expandir até o nível {{0}}" | "Espandi fino al livello {{0}}";
283
+ static get ExpansionLevel(): "Erweiterungsebene" | "Expansion level" | "Nivel de expansión" | "Niveau d'expansion" | "Nível de expansão" | "Livello di espansione";
284
+ static get ExpansionLevelSettings(): "Erweiterungsebene Einstellungen" | "Expansion level settings" | "Configuración del nivel de expansión" | "Paramètres du niveau d'expansion" | "Configurações do nível de expansão" | "Impostazioni livello espansione";
285
+ static get ExpansionSettings(): "Erweiterungseinstellungen" | "Expansion settings" | "Configuración de expansión" | "Paramètres d'expansion" | "Configurações de expansão" | "Impostazioni espansione";
286
+ static get ExpansionInProgress(): "Erweiterung läuft..." | "Expansion in progress..." | "Expansión en curso..." | "Expansion en cours..." | "Expansão em andamento..." | "Espansione in corso...";
287
+ static get FullReloadInProgress(): "Vollständiges Neuladen läuft..." | "Full reload in progress..." | "Recarga completa en curso..." | "Rechargement complet en cours..." | "Recarregamento completo em andamento..." | "Ricaricamento completo in corso...";
288
+ static get ExpansionProgress(): "Erweiterung {{0}} von {{1}}..." | "Expansion {{0}} of {{1}}..." | "Expansión {{0}} de {{1}}..." | "Expansion {{0}} sur {{1}}..." | "Expansão {{0}} de {{1}}..." | "Espansione {{0}} di {{1}}...";
276
289
  static get Expected(): "Erwartet" | "Expected" | "Esperado" | "Attendu" | "Atteso";
277
290
  static get Expiration(): "Ablaufdatum" | "Expiration" | "Fecha de expiración" | "Date d'expiration" | "Data de expiração" | "Scadenza";
278
291
  static get ExpirationDate(): "Ablaufdatum" | "Expiration date" | "Fecha de vencimiento" | "Date d'échéance" | "Data de validade" | "Data di scadenza";
@@ -422,7 +435,10 @@ export declare class SDKUI_Localizator {
422
435
  static get List(): "Liste" | "List" | "Lista";
423
436
  static get List_Hide(): "Liste ausblenden" | "Hide list" | "Ocultar lista" | "Masquer la liste" | "Nascondi la lista";
424
437
  static get List_Show(): "liste anzeigen" | "Show list" | "Ver lista" | "Afficher la liste" | "Visualizza la lista";
438
+ static get Levels(): "Ebenen" | "Levels" | "Niveles" | "Niveaux" | "Níveis" | "Livelli";
425
439
  static get Loading(): "Laden" | "Loading" | "Carga" | "Chargement" | "Caricamento";
440
+ static get LoadingDetailDocuments(): "Laden der Detaildokumente" | "Loading detail documents" | "Cargando documentos de detalle" | "Chargement des documents de détail" | "Carregando documentos de detalhe" | "Caricamento documenti dettaglio";
441
+ static get LoadingMasterDocuments(): "Laden der Masterdokumente" | "Loading master documents" | "Cargando documentos maestros" | "Chargement des documents maîtres" | "Carregando documentos mestres" | "Caricamento documenti master";
426
442
  static get Localization(): "Lokalisierung" | "Localization" | "Localización" | "Localisation" | "Localização" | "Localizzazione";
427
443
  static get LoadingWorkGroups(): string;
428
444
  static get LoadingParticipants(): string;