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

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 +138 -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,41 @@ 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
+ (() => {
176
+ const refs = SDKUI_Globals?.userSettings?.searchSettings?.autoFindReferences ?? [];
177
+ const hasRefs = allowButtonsRefs && Array.isArray(refs) && refs.length > 0;
178
+ return hasRefs
179
+ ? SDK_Globals.tmSession?.NewSearchEngine().FindAllReferencesAsync(TID, DID, refs) ?? Promise.resolve(undefined)
180
+ : Promise.resolve(undefined);
181
+ })()
182
+ ];
183
+ const results = await Promise.all(parallelCalls);
184
+ // Destructuring: first three are always the original ones, fourth is references (or undefined)
185
+ const getMetadataResult = results[0];
186
+ const resLayout = results[1];
187
+ const customButtonsLayoutResult = results[2];
188
+ const findRefsResult = results[3];
189
+ // Save references result into state (may be undefined)
190
+ setDcmtReferences(findRefsResult);
191
+ // Imposta il layout e customButtonsLayout immediatamente
192
+ setLayout(resLayout);
193
+ if (layoutMode === LayoutModes.Update && customButtonsLayoutResult) {
194
+ setCustomButtonsLayout(customButtonsLayoutResult);
195
+ }
196
+ // Carica DTD e metadata
158
197
  let dtd = await DcmtTypeListCacheService.GetWithNotGrantedAsync(TID, DID, getMetadataResult);
159
198
  setFromDTD(dtd);
160
199
  if (layoutMode === LayoutModes.Update || (layoutMode === LayoutModes.Ark && DID)) {
@@ -167,8 +206,6 @@ const TMDcmtForm = ({ showHeader = true, onSaveRecents, layoutMode = LayoutModes
167
206
  setFormData(structuredClone(metadataList));
168
207
  formDataOrigRef.current = structuredClone(metadataList);
169
208
  }
170
- let resLayout = await SDK_Globals.tmSession?.NewDcmtTypeEngine().LayoutRetrieveAsync(TID, layoutMode);
171
- setLayout(resLayout);
172
209
  (layoutMode === LayoutModes.Ark && !inputFile) && handleReset();
173
210
  }
174
211
  catch (e) {
@@ -179,7 +216,7 @@ const TMDcmtForm = ({ showHeader = true, onSaveRecents, layoutMode = LayoutModes
179
216
  setIsInitialLoading(false);
180
217
  setIsNavigating(false);
181
218
  }
182
- }, [TID, DID, layoutMode, inputFile, setMetadataList, handleReset]);
219
+ }, [TID, DID, layoutMode, inputFile, setMetadataList, handleReset, allowButtonsRefs]);
183
220
  const createChange = useCallback((mid, metadataType, modifiedValue) => {
184
221
  return { mid, metadataType, modifiedValue };
185
222
  }, []);
@@ -211,6 +248,9 @@ const TMDcmtForm = ({ showHeader = true, onSaveRecents, layoutMode = LayoutModes
211
248
  useEffect(() => {
212
249
  fromDTDRef.current = fromDTD;
213
250
  }, [fromDTD]);
251
+ useEffect(() => {
252
+ dcmtFileRef.current = dcmtFile;
253
+ }, [dcmtFile]);
214
254
  useEffect(() => {
215
255
  if (!inputFile || inputFile === null)
216
256
  return;
@@ -271,6 +311,7 @@ const TMDcmtForm = ({ showHeader = true, onSaveRecents, layoutMode = LayoutModes
271
311
  if (layoutMode !== LayoutModes.Ark)
272
312
  return;
273
313
  setFocusedMetadataValue(undefined);
314
+ appliedInputMidsRef.current = null;
274
315
  }, [fromDTD, layoutMode]);
275
316
  useEffect(() => {
276
317
  if (!inputMids || inputMids.length === 0)
@@ -279,9 +320,13 @@ const TMDcmtForm = ({ showHeader = true, onSaveRecents, layoutMode = LayoutModes
279
320
  return;
280
321
  if (!formData || formData.length === 0)
281
322
  return;
323
+ if (!formDataOrig || formDataOrig.length === 0)
324
+ return;
325
+ // Check if formData has actual user metadata (mid > 99), not just system metadata
326
+ if (!formData.some(md => md.mid && md.mid > 99))
327
+ return;
282
328
  if (appliedInputMidsRef.current && deepCompare(appliedInputMidsRef.current, inputMids))
283
329
  return;
284
- appliedInputMidsRef.current = inputMids;
285
330
  let data = structuredClone(formData);
286
331
  for (let md of data) {
287
332
  if (!md.mid)
@@ -312,6 +357,7 @@ const TMDcmtForm = ({ showHeader = true, onSaveRecents, layoutMode = LayoutModes
312
357
  }
313
358
  });
314
359
  setFormData(data);
360
+ appliedInputMidsRef.current = inputMids;
315
361
  }, [inputMids, layoutMode, formData, formDataOrig]);
316
362
  // Memoizza solo il valore WI_SetID per evitare re-render quando altri metadata cambiano
317
363
  const workItemSetIDValue = useMemo(() => formData.find(o => o.md?.name === WorkItemMetadataNames.WI_SetID)?.value, [formData]);
@@ -396,8 +442,8 @@ const TMDcmtForm = ({ showHeader = true, onSaveRecents, layoutMode = LayoutModes
396
442
  const outputMids = formData
397
443
  .filter(md => md.mid && md.mid > 100 && md.value && md.value.length > 0)
398
444
  .map(md => ({ mid: md.mid, value: md.value }));
399
- passToSearch(outputMids);
400
- }, [passToSearch, formData]);
445
+ passToSearch(outputMids, TID);
446
+ }, [passToSearch, formData, TID]);
401
447
  const isPreviewDisabled = useMemo(() => layoutMode === LayoutModes.Ark && fromDTD?.archiveConstraint === ArchiveConstraints.OnlyMetadata, [layoutMode, fromDTD?.archiveConstraint]);
402
448
  const isBoardDisabled = useMemo(() => layoutMode !== LayoutModes.Update || fromDTD?.hasBlog !== 1, [layoutMode, fromDTD?.hasBlog]);
403
449
  const isSysMetadataDisabled = useMemo(() => layoutMode !== LayoutModes.Update, [layoutMode]);
@@ -406,18 +452,41 @@ const TMDcmtForm = ({ showHeader = true, onSaveRecents, layoutMode = LayoutModes
406
452
  const isWFDisabled = useMemo(() => layoutMode !== LayoutModes.Update || fetchError || workItems.length <= 0, [layoutMode, fetchError, workItems.length]);
407
453
  const showToppyForApprove = useMemo(() => layoutMode === LayoutModes.Update && !fetchError && workItems.length > 0 && !isOpenDetails && !isOpenMaster, [layoutMode, fetchError, workItems.length, isOpenDetails, isOpenMaster]);
408
454
  const showToppyForCompleteMoreInfo = useMemo(() => layoutMode === LayoutModes.Update && isTaskMoreInfo(taskMoreInfo?.name) && taskMoreInfo?.state !== Task_States.Completed, [layoutMode, taskMoreInfo?.name, taskMoreInfo?.state]);
455
+ const showToppyForReferences = useMemo(() => allowButtonsRefs && layoutMode === LayoutModes.Update && dcmtReferences && dcmtReferences.length > 0 && !isOpenDetails && !isOpenMaster, [allowButtonsRefs, layoutMode, dcmtReferences, isOpenDetails, isOpenMaster]);
409
456
  const isMobile = useMemo(() => deviceType === DeviceType.MOBILE, [deviceType]);
410
457
  const isApprView = useMemo(() => fromDTD?.templateTID === TemplateTIDs.WF_WIApprView, [fromDTD?.templateTID]);
411
458
  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
459
  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]);
460
+ const commandsMenuItems = useMemo(() => {
461
+ const items = [
462
+ { icon: svgToString(_jsx(IconDownload, {})), operationType: 'singleRow', disabled: fromDTD?.perm?.canRetrieveFile !== AccessLevels.Yes, text: "Download file", onClick: async () => await downloadDcmtsAsync(getDcmts(), DownloadTypes.Dcmt, "download") },
463
+ { icon: svgToString(_jsx(IconDownload, {})), operationType: 'singleRow', disabled: !isXMLFileExt(currentDcmt?.fileExt), text: "Download allegati XML", onClick: async () => await downloadDcmtsAsync(getDcmts(), DownloadTypes.Attachment, "download", undefined, openConfirmAttachmentsDialog) },
464
+ ...(allowRelations && currentTIDHasMasterRelations ? [{ icon: svgToString(_jsx(IconDetailDcmts, { transform: 'scale(-1, 1)' })), operationType: 'singleRow', disabled: isMasterDisabled, text: SDKUI_Localizator.DcmtsMaster, onClick: () => { if (!isMasterDisabled)
465
+ setIsOpenMaster(!isOpenMaster); } }] : []),
466
+ ...(allowRelations && currentTIDHasDetailRelations ? [{ icon: svgToString(_jsx(IconDetailDcmts, {})), operationType: 'singleRow', disabled: isDetailsDisabled, text: SDKUI_Localizator.DcmtsDetail, onClick: () => { if (!isDetailsDisabled)
467
+ setIsOpenDetails(!isOpenDetails); } }] : []),
468
+ ];
469
+ // Aggiungi submenu "Bottoni personalizzati" se esistono customButtons
470
+ if (customButtonsLayout?.customButtons && Array.isArray(customButtonsLayout.customButtons) && customButtonsLayout.customButtons.length > 0) {
471
+ const customButtonsItems = customButtonsLayout.customButtons.map((customButton) => ({
472
+ text: customButton.title || 'Bottone personalizzato',
473
+ onClick: () => {
474
+ // Per ora, visualizziamo le proprietà del bottone in console e alert
475
+ console.log('Custom Button Properties:', customButton);
476
+ TMMessageBoxManager.show({
477
+ message: `Custom Button:\n${JSON.stringify(customButton, null, 2)}`,
478
+ buttons: [ButtonNames.OK],
479
+ });
480
+ }
481
+ }));
482
+ items.push({
483
+ icon: svgToString(_jsx(IconCheck, {})),
484
+ text: 'Bottoni personalizzati',
485
+ items: customButtonsItems
486
+ });
487
+ }
488
+ return items;
489
+ }, [fromDTD?.perm?.canRetrieveFile, currentDcmt?.fileExt, allowRelations, currentTIDHasMasterRelations, isMasterDisabled, currentTIDHasDetailRelations, isDetailsDisabled, customButtonsLayout, getDcmts, downloadDcmtsAsync, openConfirmAttachmentsDialog]);
421
490
  const isModified = useMemo(() => calcIsModified(formData, formDataOrig), [formData, formDataOrig]);
422
491
  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
492
  _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 +635,13 @@ const TMDcmtForm = ({ showHeader = true, onSaveRecents, layoutMode = LayoutModes
566
635
  ae.ArchivingFile = file;
567
636
  }
568
637
  else {
569
- ae.ArchivingFile = dcmtFile;
638
+ ae.ArchivingFile = dcmtFileRef.current;
639
+ }
640
+ if (isSharedDcmt && sharedSourceTID && sharedSourceDID) {
641
+ const sharedDcmt = new TID_DID();
642
+ sharedDcmt.tid = sharedSourceTID;
643
+ sharedDcmt.did = sharedSourceDID;
644
+ ae.SharedDcmt = sharedDcmt;
570
645
  }
571
646
  let newDID = await ae.ArchiveAsync(abortControllerLocal.signal, (pd) => {
572
647
  if (firstBlock) {
@@ -614,7 +689,7 @@ const TMDcmtForm = ({ showHeader = true, onSaveRecents, layoutMode = LayoutModes
614
689
  setShowWaitPanelLocal(false);
615
690
  setUseWaitPanelLocalState(false);
616
691
  }
617
- }, [TID, dcmtFile, connectorFileSave, onSavedAsyncCallback, onSaveRecents, onClose, DID, setMetadataList, handleReset, layoutMode]);
692
+ }, [TID, connectorFileSave, onSavedAsyncCallback, onSaveRecents, onClose, DID, setMetadataList, handleReset, layoutMode, isSharedDcmt]);
618
693
  const handleClearForm = useCallback(() => {
619
694
  let data = structuredClone(formData);
620
695
  if (!data || data.length === 0)
@@ -756,9 +831,9 @@ const TMDcmtForm = ({ showHeader = true, onSaveRecents, layoutMode = LayoutModes
756
831
  ]);
757
832
  const tmBlog = useMemo(() => _jsx(TMDcmtBlog, { tid: TID, did: DID }), [TID, DID]);
758
833
  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]);
834
+ const tmDcmtPreview = useMemo(() => _jsx(TMDcmtPreviewWrapper, { currentDcmt: currentDcmt, dcmtFile: dcmtFile ?? inputFile, deviceType: deviceType, fromDTD: fromDTD, layoutMode: layoutMode, onFileUpload: (file) => {
835
+ setDcmtFile(file);
836
+ }, enableDragDropOverlay: enableDragDropOverlay }), [currentDcmt, dcmtFile, deviceType, fromDTD, layoutMode, inputFile, enableDragDropOverlay, setDcmtFile]);
762
837
  const tmWF = useMemo(() => {
763
838
  if (isWFDataLoading) {
764
839
  return (_jsx("div", { style: {
@@ -824,7 +899,7 @@ const TMDcmtForm = ({ showHeader = true, onSaveRecents, layoutMode = LayoutModes
824
899
  showHeader: showHeader,
825
900
  title: fromDTD?.nameLoc,
826
901
  allowMaximize: !isMobile,
827
- onBack: (isClosable && deviceType !== DeviceType.MOBILE) ? undefined : handleClose,
902
+ onBack: showBackButton ? (isClosable && deviceType !== DeviceType.MOBILE) ? undefined : handleClose : undefined,
828
903
  onClose: isClosable ? () => { } : undefined,
829
904
  toolbar: allowNavigation ? formToolbar : _jsx(_Fragment, {})
830
905
  },
@@ -877,7 +952,7 @@ const TMDcmtForm = ({ showHeader = true, onSaveRecents, layoutMode = LayoutModes
877
952
  isActive: allInitialPanelVisibility['tmWF']
878
953
  }
879
954
  },
880
- ], [fromDTD, tmDcmtForm, tmBlog, tmSysMetadata, tmDcmtPreview, tmWF, isPreviewDisabled, isSysMetadataDisabled, isBoardDisabled, isWFDisabled, inputFile, isClosable]);
955
+ ], [fromDTD, showBackButton, tmDcmtForm, tmBlog, tmSysMetadata, tmDcmtPreview, tmWF, isPreviewDisabled, isSysMetadataDisabled, isBoardDisabled, isWFDisabled, inputFile, isClosable]);
881
956
  // Retrieves the current document form setting based on the normalized TID
882
957
  const getCurrentDcmtFormSetting = () => {
883
958
  const settings = SDKUI_Globals.userSettings.dcmtFormSettings;
@@ -959,6 +1034,23 @@ const TMDcmtForm = ({ showHeader = true, onSaveRecents, layoutMode = LayoutModes
959
1034
  title: SDKUI_Localizator.SignatureAndApprove,
960
1035
  });
961
1036
  }, [onOpenS4TViewerRequest, TID, DID]);
1037
+ const handleNavigateToReference = useCallback((ref) => {
1038
+ if (onReferenceClick) {
1039
+ try {
1040
+ onReferenceClick(ref);
1041
+ }
1042
+ catch (e) {
1043
+ console.error('onReferenceClick handler failed', e);
1044
+ }
1045
+ return;
1046
+ }
1047
+ }, [onReferenceClick]);
1048
+ // Mapping for objClass specific label/action. Easy to extend for future objClass types.
1049
+ const referenceActionMap = useMemo(() => ({
1050
+ [ObjectClasses.Dossier]: { label: 'Vai a pratica' },
1051
+ [ObjectClasses.WorkingGroup]: { label: 'Vai a gruppo di lavoro' },
1052
+ // add other ObjectClasses here as needed
1053
+ }), []);
962
1054
  const renderDcmtForm = () => {
963
1055
  // Show flat spinner during initial load (before component is mounted)
964
1056
  if (isInitialLoading) {
@@ -989,12 +1081,22 @@ const TMDcmtForm = ({ showHeader = true, onSaveRecents, layoutMode = LayoutModes
989
1081
  isEditable: true,
990
1082
  value: FormulaHelper.addFormulaTag(newFormula.expression)
991
1083
  }));
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) &&
1084
+ } }), 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 ?
1085
+ _jsx(WorkFlowOperationButtons, { deviceType: deviceType, onApprove: () => setShowApprovePopup(true), onSignApprove: handleSignApprove, onReject: () => setShowRejectPopup(true), onReAssign: () => setShowReAssignPopup(true), onMoreInfo: () => setShowMoreInfoPopup(true), dtd: fromDTD })
1086
+ :
1087
+ _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: () => {
1088
+ setShowCommentForm(true);
1089
+ } })] })), showToppyForReferences && dcmtReferences && dcmtReferences
1090
+ .filter(ref => ref.objClass === ObjectClasses.Dossier || ref.objClass === ObjectClasses.WorkingGroup) // keep only known objClass types
1091
+ .map((ref, index, arr) => {
1092
+ const mapEntry = referenceActionMap[String(ref.objClass)];
1093
+ const label = mapEntry?.label ?? 'Vai a riferimento';
1094
+ return (_jsxs(React.Fragment, { children: [index === 0 && (showToppyForApprove || showToppyForCompleteMoreInfo) && (_jsx("div", { style: {
1095
+ height: 1,
1096
+ backgroundColor: 'rgba(255,255,255,0.2)',
1097
+ margin: '6px 0'
1098
+ } })), _jsxs(StyledReferenceButton, { onClick: () => handleNavigateToReference(ref), children: [_jsx("span", { children: label }), _jsx("span", { children: `"${ref.objName}"` })] }, `ref-${index}-${ref.objID}`)] }, `ref-frag-${index}-${ref.objID}`));
1099
+ })] }) })), (showCommentForm && TID && DID) &&
998
1100
  _jsx(TMBlogCommentForm, { context: { engine: 'SearchEngine', object: { tid: TID, did: DID } }, onClose: () => setShowCommentForm(false), refreshCallback: handleCompleteMoreInfo, participants: [], showAttachmentsSection: false, allArchivedDocumentsFileItems: [] }), isOpenDetails &&
999
1101
  _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
1102
  _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 +1136,7 @@ const applyMetadataFilter = (data, showAll, listMaxItems, TID) => {
1034
1136
  return showAll ? filteredData : filteredData.slice(0, listMaxItems);
1035
1137
  };
1036
1138
  //#region Validaion
1037
- const validateMetadataList = (mvdList = []) => {
1139
+ export const validateMetadataList = (mvdList = []) => {
1038
1140
  if (!Array.isArray(mvdList)) {
1039
1141
  throw new TypeError("metadataList must be an array of ITMMetadataProps.");
1040
1142
  }
@@ -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;