@topconsultnpm/sdkui-react 6.19.0-dev1.5 → 6.19.0-dev1.50

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/lib/components/base/Styled.d.ts +1 -0
  2. package/lib/components/base/Styled.js +40 -0
  3. package/lib/components/base/TMFileManagerDataGridView.js +4 -1
  4. package/lib/components/base/TMTreeView.js +3 -2
  5. package/lib/components/editors/TMHtmlEditor.d.ts +5 -0
  6. package/lib/components/editors/TMHtmlEditor.js +72 -12
  7. package/lib/components/editors/TMMetadataValues.js +90 -40
  8. package/lib/components/features/archive/TMArchive.d.ts +2 -0
  9. package/lib/components/features/archive/TMArchive.js +56 -25
  10. package/lib/components/features/blog/TMBlogCommentForm.js +57 -41
  11. package/lib/components/features/documents/TMDcmtForm.d.ts +10 -3
  12. package/lib/components/features/documents/TMDcmtForm.js +134 -36
  13. package/lib/components/features/documents/TMDragDropOverlay.js +2 -1
  14. package/lib/components/features/documents/TMMasterDetailDcmts.js +1 -1
  15. package/lib/components/features/documents/TMRelationViewer.d.ts +53 -3
  16. package/lib/components/features/documents/TMRelationViewer.js +232 -85
  17. package/lib/components/features/search/TMSearch.d.ts +3 -1
  18. package/lib/components/features/search/TMSearch.js +13 -4
  19. package/lib/components/features/search/TMSearchQueryPanel.d.ts +1 -1
  20. package/lib/components/features/search/TMSearchQueryPanel.js +36 -7
  21. package/lib/components/features/search/TMSearchResult.d.ts +3 -1
  22. package/lib/components/features/search/TMSearchResult.js +102 -328
  23. package/lib/components/features/search/TMSearchResultsMenuItems.d.ts +2 -2
  24. package/lib/components/features/search/TMSearchResultsMenuItems.js +32 -15
  25. package/lib/components/features/workflow/TMWorkflowPopup.d.ts +3 -1
  26. package/lib/components/features/workflow/TMWorkflowPopup.js +17 -4
  27. package/lib/components/forms/TMChooserForm.d.ts +1 -1
  28. package/lib/components/forms/TMChooserForm.js +2 -2
  29. package/lib/components/grids/TMBlogsUtils.d.ts +1 -0
  30. package/lib/components/grids/TMBlogsUtils.js +40 -4
  31. package/lib/components/index.d.ts +3 -0
  32. package/lib/components/index.js +3 -0
  33. package/lib/components/viewers/TMTidViewer.js +29 -4
  34. package/lib/css/tm-sdkui.css +1 -1
  35. package/lib/helper/SDKUI_Globals.d.ts +2 -1
  36. package/lib/helper/SDKUI_Globals.js +1 -0
  37. package/lib/helper/SDKUI_Localizator.d.ts +4 -0
  38. package/lib/helper/SDKUI_Localizator.js +46 -0
  39. package/lib/helper/TMIcons.d.ts +2 -0
  40. package/lib/helper/TMIcons.js +6 -0
  41. package/lib/helper/TMToppyMessage.d.ts +7 -0
  42. package/lib/helper/TMToppyMessage.js +42 -0
  43. package/lib/helper/TMUtils.d.ts +7 -0
  44. package/lib/helper/TMUtils.js +16 -0
  45. package/lib/helper/index.d.ts +1 -0
  46. package/lib/helper/index.js +1 -0
  47. package/lib/hooks/useRelatedDocuments.d.ts +72 -0
  48. package/lib/hooks/useRelatedDocuments.js +655 -0
  49. package/lib/index.d.ts +1 -0
  50. package/lib/index.js +1 -0
  51. package/lib/ts/types.d.ts +14 -0
  52. package/lib/ts/types.js +15 -0
  53. package/lib/utils/theme.d.ts +1 -0
  54. package/lib/utils/theme.js +1 -0
  55. package/package.json +2 -2
@@ -1,17 +1,17 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
+ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
3
3
  import TMDcmtPreview from './TMDcmtPreview';
4
- import { AccessLevels, ArchiveConstraints, ArchiveEngineByID, DcmtTypeListCacheService, LayoutModes, MetadataDataTypes, ResultTypes, SDK_Globals, SDK_Localizator, SystemMIDsAsNumber, SystemTIDs, Task_States, TemplateTIDs, UpdateEngineByID, ValidationItem, WorkflowCacheService, WorkItemMetadataNames } from '@topconsultnpm/sdk-ts';
4
+ import { AccessLevels, ArchiveConstraints, ArchiveEngineByID, DcmtTypeListCacheService, LayoutCacheService, LayoutModes, MetadataDataTypes, ObjectClasses, ResultTypes, SDK_Globals, SDK_Localizator, SystemMIDsAsNumber, SystemTIDs, Task_States, TemplateTIDs, TID_DID, UpdateEngineByID, ValidationItem, WorkflowCacheService, WorkItemMetadataNames } from '@topconsultnpm/sdk-ts';
5
5
  import { ContextMenu } from 'devextreme-react';
6
6
  import { WorkFlowApproveRejectPopUp, WorkFlowMoreInfoPopUp, WorkFlowOperationButtons, WorkFlowReAssignPopUp } from '../workflow/TMWorkflowPopup';
7
7
  import { DownloadTypes, FormModes } from '../../../ts';
8
8
  import { DeviceType, useDeviceType } from '../../base/TMDeviceProvider';
9
9
  import { useDcmtOperations } from '../../../hooks/useDcmtOperations';
10
10
  import { getWorkItemSetIDAsync, handleArchiveVisibility, searchResultToMetadataValues } from '../../../helper/queryHelper';
11
- import { genUniqueId, IconShow, SDKUI_Localizator, updateMruTids, IconBoard, IconDcmtTypeSys, IconDetailDcmts, svgToString, IconDownload, calcIsModified, IconMenuVertical, Globalization, getListMaxItems, getSystemMetadata, IconBoxArchiveIn, IconClear, IconUndo, SDKUI_Globals, IconPreview, isTaskMoreInfo, IconWorkflow, IconSearch, deepCompare } from '../../../helper';
11
+ import { genUniqueId, IconShow, SDKUI_Localizator, updateMruTids, IconBoard, IconDcmtTypeSys, IconDetailDcmts, svgToString, IconDownload, calcIsModified, IconMenuVertical, Globalization, getListMaxItems, getSystemMetadata, IconBoxArchiveIn, IconClear, IconUndo, SDKUI_Globals, IconPreview, isTaskMoreInfo, IconWorkflow, IconSearch, deepCompare, IconCheck } from '../../../helper';
12
12
  import { hasDetailRelations, hasMasterRelations, isXMLFileExt } from '../../../helper/dcmtsHelper';
13
13
  import { Gutters, TMColors } from '../../../utils/theme';
14
- import { StyledFormButtonsContainer, StyledLoadingContainer, StyledModalContainer, StyledSpinner, StyledToolbarCardContainer } from '../../base/Styled';
14
+ import { StyledFormButtonsContainer, StyledLoadingContainer, StyledModalContainer, StyledReferenceButton, StyledSpinner, StyledToolbarCardContainer } from '../../base/Styled';
15
15
  import ShowAlert from '../../base/TMAlert';
16
16
  import TMButton from '../../base/TMButton';
17
17
  import { TMExceptionBoxManager, TMMessageBoxManager, ButtonNames } from '../../base/TMPopUp';
@@ -37,7 +37,7 @@ import WFDiagram from '../workflow/diagram/WFDiagram';
37
37
  import TMTooltip from '../../base/TMTooltip';
38
38
  let abortControllerLocal = new AbortController();
39
39
  //#endregion
40
- const TMDcmtForm = ({ showHeader = true, onSaveRecents, layoutMode = LayoutModes.Update, onClose, onSavedAsyncCallback, TID, DID, formMode = FormModes.Update, canNext, canPrev, count, itemIndex, onNext, onPrev, allowNavigation = true, allowRelations = true, isClosable = false, isExpertMode = SDKUI_Globals.userSettings.advancedSettings.expertMode === 1, showDcmtFormSidebar = true, invokedByTodo = false, titleModal, isModal = false, widthModal = "100%", heightModal = "100%", groupId, onWFOperationCompleted, onTaskCompleted, inputFile = null, taskFormDialogComponent, taskMoreInfo, connectorFileSave = undefined, inputMids = [], onOpenS4TViewerRequest, s4TViewerDialogComponent, enableDragDropOverlay = false, passToSearch }) => {
40
+ const TMDcmtForm = ({ showHeader = true, onSaveRecents, layoutMode = LayoutModes.Update, showBackButton = true, onClose, onSavedAsyncCallback, TID, DID, formMode = FormModes.Update, canNext, canPrev, count, itemIndex, onNext, onPrev, allowNavigation = true, allowRelations = true, isClosable = false, isExpertMode = SDKUI_Globals.userSettings.advancedSettings.expertMode === 1, showDcmtFormSidebar = true, invokedByTodo = false, titleModal, isModal = false, widthModal = "100%", heightModal = "100%", groupId, onWFOperationCompleted, onTaskCompleted, inputFile = null, taskFormDialogComponent, taskMoreInfo, connectorFileSave = undefined, inputMids = [], onOpenS4TViewerRequest, s4TViewerDialogComponent, enableDragDropOverlay = false, passToSearch, isSharedDcmt = false, sharedSourceTID, sharedSourceDID, allowButtonsRefs = false, onReferenceClick, }) => {
41
41
  const [id, setID] = useState('');
42
42
  const [showWaitPanelLocal, setShowWaitPanelLocal] = useState(false);
43
43
  const [waitPanelTitleLocal, setWaitPanelTitleLocal] = useState('');
@@ -59,6 +59,7 @@ const TMDcmtForm = ({ showHeader = true, onSaveRecents, layoutMode = LayoutModes
59
59
  const [showReAssignPopup, setShowReAssignPopup] = useState(false);
60
60
  const [showMoreInfoPopup, setShowMoreInfoPopup] = useState(false);
61
61
  const [layout, setLayout] = useState();
62
+ const [customButtonsLayout, setCustomButtonsLayout] = useState();
62
63
  const appliedInputMidsRef = useRef(null);
63
64
  // Refs per evitare stale closure nei callback
64
65
  // I useCallback catturano i valori delle dipendenze al momento della creazione.
@@ -67,6 +68,7 @@ const TMDcmtForm = ({ showHeader = true, onSaveRecents, layoutMode = LayoutModes
67
68
  const formDataOrigRef = useRef([]);
68
69
  const formDataRef = useRef([]);
69
70
  const fromDTDRef = useRef();
71
+ const dcmtFileRef = useRef(null);
70
72
  const [isOpenDetails, setIsOpenDetails] = useState(false);
71
73
  const [isOpenMaster, setIsOpenMaster] = useState(false);
72
74
  const [secondaryMasterDcmts, setSecondaryMasterDcmts] = useState([]);
@@ -85,6 +87,11 @@ const TMDcmtForm = ({ showHeader = true, onSaveRecents, layoutMode = LayoutModes
85
87
  const [showCommentForm, setShowCommentForm] = useState(false);
86
88
  const [isInitialLoading, setIsInitialLoading] = useState(true);
87
89
  const [isNavigating, setIsNavigating] = useState(false);
90
+ const [dcmtReferences, setDcmtReferences] = useState(undefined);
91
+ useEffect(() => {
92
+ if (!allowButtonsRefs)
93
+ setDcmtReferences(undefined);
94
+ }, [allowButtonsRefs]);
88
95
  const { openConfirmAttachmentsDialog, ConfirmAttachmentsDialog } = useInputAttachmentsDialog();
89
96
  const { abortController, showWaitPanel, waitPanelTitle, showPrimary, waitPanelTextPrimary, waitPanelValuePrimary, waitPanelMaxValuePrimary, showSecondary, waitPanelTextSecondary, waitPanelValueSecondary, waitPanelMaxValueSecondary, downloadDcmtsAsync } = useDcmtOperations();
90
97
  // Custom hook to manage workflow approval data
@@ -152,9 +159,37 @@ const TMDcmtForm = ({ showHeader = true, onSaveRecents, layoutMode = LayoutModes
152
159
  return;
153
160
  if (!DID && layoutMode === LayoutModes.Update)
154
161
  return;
155
- let getMetadataResult;
156
- if (layoutMode === LayoutModes.Update)
157
- getMetadataResult = await SDK_Globals.tmSession?.NewSearchEngine().GetMetadataAsync(TID, DID, true);
162
+ // Esegui chiamate in parallelo per ottimizzare le performance
163
+ const parallelCalls = [
164
+ // 1. GetMetadataAsync - solo se layoutMode === Update
165
+ layoutMode === LayoutModes.Update
166
+ ? SDK_Globals.tmSession?.NewSearchEngine().GetMetadataAsync(TID, DID, true) ?? Promise.resolve(undefined)
167
+ : Promise.resolve(undefined),
168
+ // 2. Layout per il layoutMode corrente
169
+ LayoutCacheService.GetAsync(TID, layoutMode),
170
+ // 3. CustomButtonsLayout - solo se layoutMode === Update
171
+ layoutMode === LayoutModes.Update
172
+ ? LayoutCacheService.GetAsync(TID, LayoutModes.None)
173
+ : Promise.resolve(undefined),
174
+ // 4. Opcionalmente FindAllReferencesAsync se abilitato nelle impostazioni e se i bottoni sono richiesti
175
+ (allowButtonsRefs && SDKUI_Globals.userSettings.searchSettings.autoFindReferences.length > 0)
176
+ ? SDK_Globals.tmSession?.NewSearchEngine().FindAllReferencesAsync(TID, DID, SDKUI_Globals.userSettings.searchSettings.autoFindReferences) ?? Promise.resolve(undefined)
177
+ : Promise.resolve(undefined)
178
+ ];
179
+ const results = await Promise.all(parallelCalls);
180
+ // Destructuring: first three are always the original ones, fourth is references (or undefined)
181
+ const getMetadataResult = results[0];
182
+ const resLayout = results[1];
183
+ const customButtonsLayoutResult = results[2];
184
+ const findRefsResult = results[3];
185
+ // Save references result into state (may be undefined)
186
+ setDcmtReferences(findRefsResult);
187
+ // Imposta il layout e customButtonsLayout immediatamente
188
+ setLayout(resLayout);
189
+ if (layoutMode === LayoutModes.Update && customButtonsLayoutResult) {
190
+ setCustomButtonsLayout(customButtonsLayoutResult);
191
+ }
192
+ // Carica DTD e metadata
158
193
  let dtd = await DcmtTypeListCacheService.GetWithNotGrantedAsync(TID, DID, getMetadataResult);
159
194
  setFromDTD(dtd);
160
195
  if (layoutMode === LayoutModes.Update || (layoutMode === LayoutModes.Ark && DID)) {
@@ -167,8 +202,6 @@ const TMDcmtForm = ({ showHeader = true, onSaveRecents, layoutMode = LayoutModes
167
202
  setFormData(structuredClone(metadataList));
168
203
  formDataOrigRef.current = structuredClone(metadataList);
169
204
  }
170
- let resLayout = await SDK_Globals.tmSession?.NewDcmtTypeEngine().LayoutRetrieveAsync(TID, layoutMode);
171
- setLayout(resLayout);
172
205
  (layoutMode === LayoutModes.Ark && !inputFile) && handleReset();
173
206
  }
174
207
  catch (e) {
@@ -179,7 +212,7 @@ const TMDcmtForm = ({ showHeader = true, onSaveRecents, layoutMode = LayoutModes
179
212
  setIsInitialLoading(false);
180
213
  setIsNavigating(false);
181
214
  }
182
- }, [TID, DID, layoutMode, inputFile, setMetadataList, handleReset]);
215
+ }, [TID, DID, layoutMode, inputFile, setMetadataList, handleReset, allowButtonsRefs]);
183
216
  const createChange = useCallback((mid, metadataType, modifiedValue) => {
184
217
  return { mid, metadataType, modifiedValue };
185
218
  }, []);
@@ -211,6 +244,9 @@ const TMDcmtForm = ({ showHeader = true, onSaveRecents, layoutMode = LayoutModes
211
244
  useEffect(() => {
212
245
  fromDTDRef.current = fromDTD;
213
246
  }, [fromDTD]);
247
+ useEffect(() => {
248
+ dcmtFileRef.current = dcmtFile;
249
+ }, [dcmtFile]);
214
250
  useEffect(() => {
215
251
  if (!inputFile || inputFile === null)
216
252
  return;
@@ -271,6 +307,7 @@ const TMDcmtForm = ({ showHeader = true, onSaveRecents, layoutMode = LayoutModes
271
307
  if (layoutMode !== LayoutModes.Ark)
272
308
  return;
273
309
  setFocusedMetadataValue(undefined);
310
+ appliedInputMidsRef.current = null;
274
311
  }, [fromDTD, layoutMode]);
275
312
  useEffect(() => {
276
313
  if (!inputMids || inputMids.length === 0)
@@ -279,9 +316,13 @@ const TMDcmtForm = ({ showHeader = true, onSaveRecents, layoutMode = LayoutModes
279
316
  return;
280
317
  if (!formData || formData.length === 0)
281
318
  return;
319
+ if (!formDataOrig || formDataOrig.length === 0)
320
+ return;
321
+ // Check if formData has actual user metadata (mid > 99), not just system metadata
322
+ if (!formData.some(md => md.mid && md.mid > 99))
323
+ return;
282
324
  if (appliedInputMidsRef.current && deepCompare(appliedInputMidsRef.current, inputMids))
283
325
  return;
284
- appliedInputMidsRef.current = inputMids;
285
326
  let data = structuredClone(formData);
286
327
  for (let md of data) {
287
328
  if (!md.mid)
@@ -312,6 +353,7 @@ const TMDcmtForm = ({ showHeader = true, onSaveRecents, layoutMode = LayoutModes
312
353
  }
313
354
  });
314
355
  setFormData(data);
356
+ appliedInputMidsRef.current = inputMids;
315
357
  }, [inputMids, layoutMode, formData, formDataOrig]);
316
358
  // Memoizza solo il valore WI_SetID per evitare re-render quando altri metadata cambiano
317
359
  const workItemSetIDValue = useMemo(() => formData.find(o => o.md?.name === WorkItemMetadataNames.WI_SetID)?.value, [formData]);
@@ -396,8 +438,8 @@ const TMDcmtForm = ({ showHeader = true, onSaveRecents, layoutMode = LayoutModes
396
438
  const outputMids = formData
397
439
  .filter(md => md.mid && md.mid > 100 && md.value && md.value.length > 0)
398
440
  .map(md => ({ mid: md.mid, value: md.value }));
399
- passToSearch(outputMids);
400
- }, [passToSearch, formData]);
441
+ passToSearch(outputMids, TID);
442
+ }, [passToSearch, formData, TID]);
401
443
  const isPreviewDisabled = useMemo(() => layoutMode === LayoutModes.Ark && fromDTD?.archiveConstraint === ArchiveConstraints.OnlyMetadata, [layoutMode, fromDTD?.archiveConstraint]);
402
444
  const isBoardDisabled = useMemo(() => layoutMode !== LayoutModes.Update || fromDTD?.hasBlog !== 1, [layoutMode, fromDTD?.hasBlog]);
403
445
  const isSysMetadataDisabled = useMemo(() => layoutMode !== LayoutModes.Update, [layoutMode]);
@@ -406,18 +448,41 @@ const TMDcmtForm = ({ showHeader = true, onSaveRecents, layoutMode = LayoutModes
406
448
  const isWFDisabled = useMemo(() => layoutMode !== LayoutModes.Update || fetchError || workItems.length <= 0, [layoutMode, fetchError, workItems.length]);
407
449
  const showToppyForApprove = useMemo(() => layoutMode === LayoutModes.Update && !fetchError && workItems.length > 0 && !isOpenDetails && !isOpenMaster, [layoutMode, fetchError, workItems.length, isOpenDetails, isOpenMaster]);
408
450
  const showToppyForCompleteMoreInfo = useMemo(() => layoutMode === LayoutModes.Update && isTaskMoreInfo(taskMoreInfo?.name) && taskMoreInfo?.state !== Task_States.Completed, [layoutMode, taskMoreInfo?.name, taskMoreInfo?.state]);
451
+ const showToppyForReferences = useMemo(() => allowButtonsRefs && layoutMode === LayoutModes.Update && dcmtReferences && dcmtReferences.length > 0 && !isOpenDetails && !isOpenMaster, [allowButtonsRefs, layoutMode, dcmtReferences, isOpenDetails, isOpenMaster]);
409
452
  const isMobile = useMemo(() => deviceType === DeviceType.MOBILE, [deviceType]);
410
453
  const isApprView = useMemo(() => fromDTD?.templateTID === TemplateTIDs.WF_WIApprView, [fromDTD?.templateTID]);
411
454
  const workitemSetID = useMemo(() => workItems.find(o => o.did === Number(DID))?.setID || formData.find(o => o.md?.name === WorkItemMetadataNames.WI_SetID)?.value, [workItems, DID, formData]);
412
455
  const approvalVID = useMemo(() => workItems.length > 0 ? Number(workItems[0].tid) : -1, [workItems]);
413
- const commandsMenuItems = useMemo(() => [
414
- { icon: svgToString(_jsx(IconDownload, {})), operationType: 'singleRow', disabled: fromDTD?.perm?.canRetrieveFile !== AccessLevels.Yes, text: "Download file", onClick: async () => await downloadDcmtsAsync(getDcmts(), DownloadTypes.Dcmt, "download") },
415
- { icon: svgToString(_jsx(IconDownload, {})), operationType: 'singleRow', disabled: !isXMLFileExt(currentDcmt?.fileExt), text: "Download allegati XML", onClick: async () => await downloadDcmtsAsync(getDcmts(), DownloadTypes.Attachment, "download", undefined, openConfirmAttachmentsDialog) },
416
- ...(allowRelations && currentTIDHasMasterRelations ? [{ icon: svgToString(_jsx(IconDetailDcmts, { transform: 'scale(-1, 1)' })), operationType: 'singleRow', disabled: isMasterDisabled, text: SDKUI_Localizator.DcmtsMaster, onClick: () => { if (!isMasterDisabled)
417
- setIsOpenMaster(!isOpenMaster); } }] : []),
418
- ...(allowRelations && currentTIDHasDetailRelations ? [{ icon: svgToString(_jsx(IconDetailDcmts, {})), operationType: 'singleRow', disabled: isDetailsDisabled, text: SDKUI_Localizator.DcmtsDetail, onClick: () => { if (!isDetailsDisabled)
419
- setIsOpenDetails(!isOpenDetails); } }] : []),
420
- ], [fromDTD?.perm?.canRetrieveFile, currentDcmt?.fileExt, allowRelations, currentTIDHasMasterRelations, isMasterDisabled, currentTIDHasDetailRelations, isDetailsDisabled, getDcmts, downloadDcmtsAsync, openConfirmAttachmentsDialog]);
456
+ const commandsMenuItems = useMemo(() => {
457
+ const items = [
458
+ { icon: svgToString(_jsx(IconDownload, {})), operationType: 'singleRow', disabled: fromDTD?.perm?.canRetrieveFile !== AccessLevels.Yes, text: "Download file", onClick: async () => await downloadDcmtsAsync(getDcmts(), DownloadTypes.Dcmt, "download") },
459
+ { icon: svgToString(_jsx(IconDownload, {})), operationType: 'singleRow', disabled: !isXMLFileExt(currentDcmt?.fileExt), text: "Download allegati XML", onClick: async () => await downloadDcmtsAsync(getDcmts(), DownloadTypes.Attachment, "download", undefined, openConfirmAttachmentsDialog) },
460
+ ...(allowRelations && currentTIDHasMasterRelations ? [{ icon: svgToString(_jsx(IconDetailDcmts, { transform: 'scale(-1, 1)' })), operationType: 'singleRow', disabled: isMasterDisabled, text: SDKUI_Localizator.DcmtsMaster, onClick: () => { if (!isMasterDisabled)
461
+ setIsOpenMaster(!isOpenMaster); } }] : []),
462
+ ...(allowRelations && currentTIDHasDetailRelations ? [{ icon: svgToString(_jsx(IconDetailDcmts, {})), operationType: 'singleRow', disabled: isDetailsDisabled, text: SDKUI_Localizator.DcmtsDetail, onClick: () => { if (!isDetailsDisabled)
463
+ setIsOpenDetails(!isOpenDetails); } }] : []),
464
+ ];
465
+ // Aggiungi submenu "Bottoni personalizzati" se esistono customButtons
466
+ if (customButtonsLayout?.customButtons && Array.isArray(customButtonsLayout.customButtons) && customButtonsLayout.customButtons.length > 0) {
467
+ const customButtonsItems = customButtonsLayout.customButtons.map((customButton) => ({
468
+ text: customButton.title || 'Bottone personalizzato',
469
+ onClick: () => {
470
+ // Per ora, visualizziamo le proprietà del bottone in console e alert
471
+ console.log('Custom Button Properties:', customButton);
472
+ TMMessageBoxManager.show({
473
+ message: `Custom Button:\n${JSON.stringify(customButton, null, 2)}`,
474
+ buttons: [ButtonNames.OK],
475
+ });
476
+ }
477
+ }));
478
+ items.push({
479
+ icon: svgToString(_jsx(IconCheck, {})),
480
+ text: 'Bottoni personalizzati',
481
+ items: customButtonsItems
482
+ });
483
+ }
484
+ return items;
485
+ }, [fromDTD?.perm?.canRetrieveFile, currentDcmt?.fileExt, allowRelations, currentTIDHasMasterRelations, isMasterDisabled, currentTIDHasDetailRelations, isDetailsDisabled, customButtonsLayout, getDcmts, downloadDcmtsAsync, openConfirmAttachmentsDialog]);
421
486
  const isModified = useMemo(() => calcIsModified(formData, formDataOrig), [formData, formDataOrig]);
422
487
  const formToolbar = useMemo(() => _jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: '10px' }, children: [allowNavigation && canPrev != undefined && _jsx("p", { style: { textAlign: 'center', padding: '1px 4px', display: 'flex' }, children: `${itemIndex}/${count}` }), allowNavigation && canPrev != undefined && _jsx(TMSaveFormButtonPrevious, { btnStyle: 'icon', iconColor: 'white', isModified: isModified, formMode: formMode, canPrev: canPrev, onPrev: onPrev }), allowNavigation && canNext != undefined && _jsx(TMSaveFormButtonNext, { btnStyle: 'icon', iconColor: 'white', isModified: isModified, formMode: formMode, canNext: canNext, onNext: onNext }), layoutMode === LayoutModes.Update && _jsx(IconMenuVertical, { id: `commands-detail-${id}`, color: 'white', cursor: 'pointer' }), layoutMode === LayoutModes.Update && _jsx(ContextMenu, { showEvent: 'click', dataSource: commandsMenuItems, target: `#commands-detail-${id}` }), layoutMode === LayoutModes.Ark &&
423
488
  _jsx(TMTooltip, { content: SDKUI_Localizator.PassToSearch, position: 'bottom', children: _jsx(IconSearch, { style: { cursor: 'pointer' }, onClick: handlePassToSearch }) })] }), [allowNavigation, canPrev, canNext, itemIndex, count, isModified, formMode, onPrev, onNext, layoutMode, id, commandsMenuItems, handlePassToSearch]);
@@ -566,7 +631,13 @@ const TMDcmtForm = ({ showHeader = true, onSaveRecents, layoutMode = LayoutModes
566
631
  ae.ArchivingFile = file;
567
632
  }
568
633
  else {
569
- ae.ArchivingFile = dcmtFile;
634
+ ae.ArchivingFile = dcmtFileRef.current;
635
+ }
636
+ if (isSharedDcmt && sharedSourceTID && sharedSourceDID) {
637
+ const sharedDcmt = new TID_DID();
638
+ sharedDcmt.tid = sharedSourceTID;
639
+ sharedDcmt.did = sharedSourceDID;
640
+ ae.SharedDcmt = sharedDcmt;
570
641
  }
571
642
  let newDID = await ae.ArchiveAsync(abortControllerLocal.signal, (pd) => {
572
643
  if (firstBlock) {
@@ -614,7 +685,7 @@ const TMDcmtForm = ({ showHeader = true, onSaveRecents, layoutMode = LayoutModes
614
685
  setShowWaitPanelLocal(false);
615
686
  setUseWaitPanelLocalState(false);
616
687
  }
617
- }, [TID, dcmtFile, connectorFileSave, onSavedAsyncCallback, onSaveRecents, onClose, DID, setMetadataList, handleReset, layoutMode]);
688
+ }, [TID, connectorFileSave, onSavedAsyncCallback, onSaveRecents, onClose, DID, setMetadataList, handleReset, layoutMode, isSharedDcmt]);
618
689
  const handleClearForm = useCallback(() => {
619
690
  let data = structuredClone(formData);
620
691
  if (!data || data.length === 0)
@@ -756,9 +827,9 @@ const TMDcmtForm = ({ showHeader = true, onSaveRecents, layoutMode = LayoutModes
756
827
  ]);
757
828
  const tmBlog = useMemo(() => _jsx(TMDcmtBlog, { tid: TID, did: DID }), [TID, DID]);
758
829
  const tmSysMetadata = useMemo(() => _jsx(TMMetadataValues, { layoutMode: layoutMode, openChooserBySingleClick: !isOpenDistinctValues, TID: TID, isReadOnly: true, deviceType: deviceType, metadataValues: formData.filter(o => (o.mid != undefined && o.mid <= 100)), metadataValuesOrig: formData.filter(o => (o.mid != undefined && o.mid <= 100)), validationItems: [], inputMids: inputMids }), [TID, layoutMode, formData, deviceType, inputMids]);
759
- const tmDcmtPreview = useMemo(() => _jsx(TMDcmtPreviewWrapper, { currentDcmt: currentDcmt, dcmtFile: dcmtFile ?? inputFile, deviceType: deviceType, fromDTD: fromDTD, layoutMode: layoutMode, onFileUpload: (setFile) => {
760
- setDcmtFile(setFile);
761
- }, enableDragDropOverlay: enableDragDropOverlay }), [currentDcmt, dcmtFile, deviceType, fromDTD, layoutMode, inputFile, enableDragDropOverlay]);
830
+ const tmDcmtPreview = useMemo(() => _jsx(TMDcmtPreviewWrapper, { currentDcmt: currentDcmt, dcmtFile: dcmtFile ?? inputFile, deviceType: deviceType, fromDTD: fromDTD, layoutMode: layoutMode, onFileUpload: (file) => {
831
+ setDcmtFile(file);
832
+ }, enableDragDropOverlay: enableDragDropOverlay }), [currentDcmt, dcmtFile, deviceType, fromDTD, layoutMode, inputFile, enableDragDropOverlay, setDcmtFile]);
762
833
  const tmWF = useMemo(() => {
763
834
  if (isWFDataLoading) {
764
835
  return (_jsx("div", { style: {
@@ -824,7 +895,7 @@ const TMDcmtForm = ({ showHeader = true, onSaveRecents, layoutMode = LayoutModes
824
895
  showHeader: showHeader,
825
896
  title: fromDTD?.nameLoc,
826
897
  allowMaximize: !isMobile,
827
- onBack: (isClosable && deviceType !== DeviceType.MOBILE) ? undefined : handleClose,
898
+ onBack: showBackButton ? (isClosable && deviceType !== DeviceType.MOBILE) ? undefined : handleClose : undefined,
828
899
  onClose: isClosable ? () => { } : undefined,
829
900
  toolbar: allowNavigation ? formToolbar : _jsx(_Fragment, {})
830
901
  },
@@ -877,7 +948,7 @@ const TMDcmtForm = ({ showHeader = true, onSaveRecents, layoutMode = LayoutModes
877
948
  isActive: allInitialPanelVisibility['tmWF']
878
949
  }
879
950
  },
880
- ], [fromDTD, tmDcmtForm, tmBlog, tmSysMetadata, tmDcmtPreview, tmWF, isPreviewDisabled, isSysMetadataDisabled, isBoardDisabled, isWFDisabled, inputFile, isClosable]);
951
+ ], [fromDTD, showBackButton, tmDcmtForm, tmBlog, tmSysMetadata, tmDcmtPreview, tmWF, isPreviewDisabled, isSysMetadataDisabled, isBoardDisabled, isWFDisabled, inputFile, isClosable]);
881
952
  // Retrieves the current document form setting based on the normalized TID
882
953
  const getCurrentDcmtFormSetting = () => {
883
954
  const settings = SDKUI_Globals.userSettings.dcmtFormSettings;
@@ -959,6 +1030,23 @@ const TMDcmtForm = ({ showHeader = true, onSaveRecents, layoutMode = LayoutModes
959
1030
  title: SDKUI_Localizator.SignatureAndApprove,
960
1031
  });
961
1032
  }, [onOpenS4TViewerRequest, TID, DID]);
1033
+ const handleNavigateToReference = useCallback((ref) => {
1034
+ if (onReferenceClick) {
1035
+ try {
1036
+ onReferenceClick(ref);
1037
+ }
1038
+ catch (e) {
1039
+ console.error('onReferenceClick handler failed', e);
1040
+ }
1041
+ return;
1042
+ }
1043
+ }, [onReferenceClick]);
1044
+ // Mapping for objClass specific label/action. Easy to extend for future objClass types.
1045
+ const referenceActionMap = useMemo(() => ({
1046
+ [ObjectClasses.Dossier]: { label: 'Vai a pratica' },
1047
+ [ObjectClasses.WorkingGroup]: { label: 'Vai a gruppo di lavoro' },
1048
+ // add other ObjectClasses here as needed
1049
+ }), []);
962
1050
  const renderDcmtForm = () => {
963
1051
  // Show flat spinner during initial load (before component is mounted)
964
1052
  if (isInitialLoading) {
@@ -989,12 +1077,22 @@ const TMDcmtForm = ({ showHeader = true, onSaveRecents, layoutMode = LayoutModes
989
1077
  isEditable: true,
990
1078
  value: FormulaHelper.addFormulaTag(newFormula.expression)
991
1079
  }));
992
- } }), showApprovePopup && _jsx(WorkFlowApproveRejectPopUp, { deviceType: deviceType, onCompleted: handleWFOperationCompleted, TID: approvalVID, DID: DID, isReject: 0, onClose: () => setShowApprovePopup(false) }), showRejectPopup && _jsx(WorkFlowApproveRejectPopUp, { deviceType: deviceType, onCompleted: handleWFOperationCompleted, TID: approvalVID, DID: DID, isReject: 1, onClose: () => setShowRejectPopup(false) }), showReAssignPopup && _jsx(WorkFlowReAssignPopUp, { deviceType: deviceType, onCompleted: handleWFOperationCompleted, TID: approvalVID, DID: DID, onClose: () => setShowReAssignPopup(false) }), showMoreInfoPopup && _jsx(WorkFlowMoreInfoPopUp, { deviceType: deviceType, onCompleted: handleWFOperationCompleted, TID: approvalVID, DID: DID, onClose: () => setShowMoreInfoPopup(false) }), (isModal && onClose) && _jsx("div", { id: "TMDcmtFormShowConfirmForClose-" + id })] }), showToppyForApprove && (_jsx(ToppyHelpCenter, { deviceType: deviceType, usePortal: false, content: workItems.length === 1 ?
993
- _jsx("div", { style: { display: 'flex', flexDirection: 'column', gap: '10px' }, children: _jsx(WorkFlowOperationButtons, { deviceType: deviceType, onApprove: () => setShowApprovePopup(true), onSignApprove: handleSignApprove, onReject: () => setShowRejectPopup(true), onReAssign: () => setShowReAssignPopup(true), onMoreInfo: () => setShowMoreInfoPopup(true) }) })
994
- :
995
- _jsxs("div", { style: { padding: 10, color: 'white', maxWidth: '180px', borderRadius: 10, background: '#1B1464 0% 0% no-repeat padding-box', border: '1px solid #FFFFFF' }, children: [`Devi approvare ${workItems.length} workitem(s) per questo documento.`, `Vai alla sezione di approvazione.`] }) })), showToppyForCompleteMoreInfo && (_jsx(ToppyHelpCenter, { deviceType: deviceType, usePortal: false, content: _jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 10 }, children: [_jsx("div", { style: { padding: 10, color: 'white', maxWidth: '180px', borderRadius: 10, background: '#1B1464 0% 0% no-repeat padding-box', border: '1px solid #FFFFFF' }, children: `${SDKUI_Localizator.MoreInfoCompleteRequestSentBy} ${taskMoreInfo?.fromName}!` }), _jsx(TMButton, { caption: SDKUI_Localizator.CommentAndComplete, color: 'success', showTooltip: false, onClick: () => {
996
- setShowCommentForm(true);
997
- } })] }) })), (showCommentForm && TID && DID) &&
1080
+ } }), showApprovePopup && _jsx(WorkFlowApproveRejectPopUp, { deviceType: deviceType, onCompleted: handleWFOperationCompleted, TID: approvalVID, DID: DID, isReject: 0, onClose: () => setShowApprovePopup(false) }), showRejectPopup && _jsx(WorkFlowApproveRejectPopUp, { deviceType: deviceType, onCompleted: handleWFOperationCompleted, TID: approvalVID, DID: DID, isReject: 1, onClose: () => setShowRejectPopup(false) }), showReAssignPopup && _jsx(WorkFlowReAssignPopUp, { deviceType: deviceType, onCompleted: handleWFOperationCompleted, TID: approvalVID, DID: DID, onClose: () => setShowReAssignPopup(false) }), showMoreInfoPopup && _jsx(WorkFlowMoreInfoPopUp, { deviceType: deviceType, onCompleted: handleWFOperationCompleted, TID: approvalVID, DID: DID, onClose: () => setShowMoreInfoPopup(false) }), (isModal && onClose) && _jsx("div", { id: "TMDcmtFormShowConfirmForClose-" + id })] }), (showToppyForApprove || showToppyForCompleteMoreInfo || showToppyForReferences) && (_jsx(ToppyHelpCenter, { deviceType: deviceType, usePortal: false, content: _jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: '10px' }, children: [showToppyForApprove && (workItems.length === 1 ?
1081
+ _jsx(WorkFlowOperationButtons, { deviceType: deviceType, onApprove: () => setShowApprovePopup(true), onSignApprove: handleSignApprove, onReject: () => setShowRejectPopup(true), onReAssign: () => setShowReAssignPopup(true), onMoreInfo: () => setShowMoreInfoPopup(true), dtd: fromDTD })
1082
+ :
1083
+ _jsxs("div", { style: { padding: 10, color: 'white', maxWidth: '180px', borderRadius: 10, background: '#1B1464 0% 0% no-repeat padding-box', border: '1px solid #FFFFFF' }, children: [`Devi approvare ${workItems.length} workitem(s) per questo documento.`, `Vai alla sezione di approvazione.`] })), showToppyForCompleteMoreInfo && (_jsxs(_Fragment, { children: [_jsx("div", { style: { padding: 10, color: 'white', maxWidth: '180px', borderRadius: 10, background: '#1B1464 0% 0% no-repeat padding-box', border: '1px solid #FFFFFF' }, children: `${SDKUI_Localizator.MoreInfoCompleteRequestSentBy} ${taskMoreInfo?.fromName}!` }), _jsx(TMButton, { caption: SDKUI_Localizator.CommentAndComplete, color: 'success', showTooltip: false, onClick: () => {
1084
+ setShowCommentForm(true);
1085
+ } })] })), showToppyForReferences && dcmtReferences && dcmtReferences
1086
+ .filter(ref => ref.objClass === ObjectClasses.Dossier || ref.objClass === ObjectClasses.WorkingGroup) // keep only known objClass types
1087
+ .map((ref, index, arr) => {
1088
+ const mapEntry = referenceActionMap[String(ref.objClass)];
1089
+ const label = mapEntry?.label ?? 'Vai a riferimento';
1090
+ return (_jsxs(React.Fragment, { children: [index === 0 && (showToppyForApprove || showToppyForCompleteMoreInfo) && (_jsx("div", { style: {
1091
+ height: 1,
1092
+ backgroundColor: 'rgba(255,255,255,0.2)',
1093
+ margin: '6px 0'
1094
+ } })), _jsxs(StyledReferenceButton, { onClick: () => handleNavigateToReference(ref), children: [_jsx("span", { children: label }), _jsx("span", { children: ref.objName })] }, `ref-${index}-${ref.objID}`)] }, `ref-frag-${index}-${ref.objID}`));
1095
+ })] }) })), (showCommentForm && TID && DID) &&
998
1096
  _jsx(TMBlogCommentForm, { context: { engine: 'SearchEngine', object: { tid: TID, did: DID } }, onClose: () => setShowCommentForm(false), refreshCallback: handleCompleteMoreInfo, participants: [], showAttachmentsSection: false, allArchivedDocumentsFileItems: [] }), isOpenDetails &&
999
1097
  _jsx(StyledModalContainer, { style: { backgroundColor: 'white' }, children: _jsx(TMMasterDetailDcmts, { deviceType: deviceType, isForMaster: false, inputDcmts: getSelectionDcmtInfo(), allowNavigation: allowNavigation, canNext: canNext, canPrev: canPrev, onNext: onNext, onPrev: onPrev, onBack: () => setIsOpenDetails(false) }) }), isOpenMaster &&
1000
1098
  _jsxs(StyledModalContainer, { style: { backgroundColor: 'white' }, children: [_jsx(TMMasterDetailDcmts, { deviceType: deviceType, inputDcmts: getSelectionDcmtInfo(), isForMaster: true, allowNavigation: allowNavigation, canNext: canNext, canPrev: canPrev, onNext: onNext, onPrev: onPrev, onBack: () => setIsOpenMaster(false), appendMasterDcmts: handleAddItem }), secondaryMasterDcmts.length > 0 && secondaryMasterDcmts.map((dcmt, index) => {
@@ -1034,7 +1132,7 @@ const applyMetadataFilter = (data, showAll, listMaxItems, TID) => {
1034
1132
  return showAll ? filteredData : filteredData.slice(0, listMaxItems);
1035
1133
  };
1036
1134
  //#region Validaion
1037
- const validateMetadataList = (mvdList = []) => {
1135
+ export const validateMetadataList = (mvdList = []) => {
1038
1136
  if (!Array.isArray(mvdList)) {
1039
1137
  throw new TypeError("metadataList must be an array of ITMMetadataProps.");
1040
1138
  }
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useState } from "react";
3
3
  import Toppy from '../../../assets/Toppy-generico.png';
4
+ import { SDKUI_Localizator } from "../../../helper";
4
5
  const TMDragDropOverlay = (props) => {
5
6
  const { handleFile, refocusAfterFileInput } = props;
6
7
  const [dragOver, setDragOver] = useState(false);
@@ -91,6 +92,6 @@ const TMDragDropOverlay = (props) => {
91
92
  alignItems: 'center',
92
93
  gap: 20,
93
94
  textAlign: 'center',
94
- }, children: [_jsx("img", { src: Toppy, alt: "Toppy", style: { width: 80, height: 'auto' } }), _jsx("div", { style: { fontSize: '1.6rem' }, children: "Rilascia il tuo file qui" })] })] }));
95
+ }, children: [_jsx("img", { src: Toppy, alt: "Toppy", style: { width: 80, height: 'auto' } }), _jsx("div", { style: { fontSize: '1.6rem' }, children: SDKUI_Localizator.DropFileHere })] })] }));
95
96
  };
96
97
  export default TMDragDropOverlay;
@@ -279,7 +279,7 @@ const TMRelationViewerWrapper = ({ inputDcmts, isForMaster, showCurrentDcmtIndic
279
279
  const TMFormOrResultWrapper = ({ deviceType, focusedItem, onTaskCreateRequest }) => {
280
280
  const { setPanelVisibilityById } = useTMPanelManagerContext();
281
281
  return (_jsx(_Fragment, { children: focusedItem?.isDcmt ?
282
- _jsx(TMDcmtForm, { groupId: 'tmFormOrResult', TID: focusedItem?.tid, DID: focusedItem.did, isClosable: deviceType !== DeviceType.MOBILE, allowNavigation: false, allowRelations: deviceType !== DeviceType.MOBILE, showDcmtFormSidebar: false, onClose: () => {
282
+ _jsx(TMDcmtForm, { groupId: 'tmFormOrResult', TID: focusedItem?.tid, DID: focusedItem.did, allowButtonsRefs: true, isClosable: deviceType !== DeviceType.MOBILE, allowNavigation: false, allowRelations: deviceType !== DeviceType.MOBILE, showDcmtFormSidebar: false, onClose: () => {
283
283
  setPanelVisibilityById('tmTreeView', true);
284
284
  } }) :
285
285
  _jsx(TMSearchResult, { groupId: 'tmFormOrResult', isClosable: deviceType !== DeviceType.MOBILE, context: SearchResultContext.METADATA_SEARCH, allowFloatingBar: false, allowRelations: false, openDcmtFormAsModal: true, searchResults: focusedItem?.searchResult ?? [], showSearchResultSidebar: false, onTaskCreateRequest: onTaskCreateRequest, onClose: () => {
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { DcmtTypeDescriptor, SearchResultDescriptor } from "@topconsultnpm/sdk-ts";
2
+ import { DcmtTypeDescriptor, SearchResultDescriptor, DataColumnDescriptor } from "@topconsultnpm/sdk-ts";
3
3
  import { DcmtInfo } from '../../../ts';
4
4
  import { ITMTreeItem } from '../../base/TMTreeView';
5
5
  /**
@@ -17,6 +17,7 @@ export interface RelationTreeItem extends ITMTreeItem {
17
17
  isMaster?: boolean;
18
18
  isCorrelated?: boolean;
19
19
  isLoaded?: boolean;
20
+ isSeparator?: boolean;
20
21
  values?: any;
21
22
  searchResult?: SearchResultDescriptor[];
22
23
  itemsCount?: number;
@@ -49,8 +50,24 @@ export interface TMRelationViewerProps {
49
50
  onSelectedItemsChanged?: (items: RelationTreeItem[]) => void;
50
51
  /** Callback when a document is double-clicked */
51
52
  onDocumentDoubleClick?: (tid: number, did: number, name?: string) => void;
52
- /** Custom item renderer */
53
- customItemRender?: (item: RelationTreeItem | null) => JSX.Element;
53
+ /** Custom item renderer (full control). Return undefined to use default renderer. */
54
+ customItemRender?: (item: RelationTreeItem | null) => JSX.Element | undefined;
55
+ /** Custom document style function */
56
+ customDocumentStyle?: (item: RelationTreeItem) => React.CSSProperties;
57
+ /**
58
+ * Custom content renderer for the main container (when showMainDocument is false).
59
+ * Allows full control over the main container rendering including style and content.
60
+ * This replaces only the main container content, not the detail containers.
61
+ */
62
+ customMainContainerContent?: (item: RelationTreeItem, defaultContent: JSX.Element) => JSX.Element;
63
+ /** Custom document content renderer (partial control - replaces only metadata display) */
64
+ customDocumentContent?: (item: RelationTreeItem, defaultMetadataContent: JSX.Element) => JSX.Element;
65
+ /**
66
+ * Show metadata names before values (default: false).
67
+ * When true, displays "MetadataName: Value", otherwise just "Value".
68
+ * Value rendering respects DataDomain (uses TMDataListItemViewer for lists, TMUserIdViewer for users, etc.)
69
+ */
70
+ showMetadataNames?: boolean;
54
71
  /** Maximum depth level for recursive loading (default: 2) */
55
72
  maxDepthLevel?: number;
56
73
  /**
@@ -58,6 +75,23 @@ export interface TMRelationViewerProps {
58
75
  * If false, when isForMaster=true shows: master docs → detail docs as children (original navigation)
59
76
  */
60
77
  invertMasterNavigation?: boolean;
78
+ /**
79
+ * Additional static items to append at the end of the tree.
80
+ * These items are not loaded dynamically - they must already contain all data.
81
+ * Useful for showing pre-calculated documents (e.g., additional documents in dossiers).
82
+ * Can include a separator item by marking it with `isSeparator: true`.
83
+ */
84
+ additionalStaticItems?: RelationTreeItem[];
85
+ /**
86
+ * If false, hides the main document node and shows detail containers directly under the main container.
87
+ * Default: true (show main document as intermediate node)
88
+ */
89
+ showMainDocument?: boolean;
90
+ /**
91
+ * Custom label for the main container when showMainDocument is false.
92
+ * If not provided, uses the document type name.
93
+ */
94
+ labelMainContainer?: string;
61
95
  }
62
96
  /**
63
97
  * Check if document type has detail relations
@@ -67,5 +101,21 @@ export declare const hasDetailRelations: (mTID: number | undefined) => Promise<b
67
101
  * Check if document type has master relations
68
102
  */
69
103
  export declare const hasMasterRelations: (dTID: number | undefined) => Promise<boolean>;
104
+ /**
105
+ * Get metadata keys excluding system metadata
106
+ */
107
+ export declare const getMetadataKeys: (obj: any) => string[];
108
+ /**
109
+ * Get display value keys for a document (max 5, prioritize SYS_Abstract)
110
+ */
111
+ export declare const getDcmtDisplayValue: (obj: any) => string[];
112
+ /**
113
+ * Get display value formatted by column type
114
+ */
115
+ export declare const getDisplayValueByColumn: (col: DataColumnDescriptor | undefined, value: any) => any;
116
+ /**
117
+ * Convert SearchResultDescriptor to structured data source with metadata
118
+ */
119
+ export declare const searchResultToDataSource: (searchResult: SearchResultDescriptor | undefined, hideSysMetadata?: boolean) => Promise<any[]>;
70
120
  declare const TMRelationViewer: React.FC<TMRelationViewerProps>;
71
121
  export default TMRelationViewer;