@topconsultnpm/sdkui-react 6.19.0-dev1.9 → 6.19.0-dev2.2

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 (116) 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/TMCustomButton.d.ts +11 -0
  4. package/lib/components/base/TMCustomButton.js +63 -0
  5. package/lib/components/base/TMFileManagerDataGridView.js +4 -1
  6. package/lib/components/base/TMLayout.d.ts +2 -1
  7. package/lib/components/base/TMLayout.js +2 -2
  8. package/lib/components/base/TMPopUp.js +5 -18
  9. package/lib/components/base/TMTreeView.js +3 -2
  10. package/lib/components/editors/TMHtmlEditor.d.ts +5 -0
  11. package/lib/components/editors/TMHtmlEditor.js +72 -12
  12. package/lib/components/editors/TMMetadataValues.js +90 -40
  13. package/lib/components/features/archive/TMArchive.d.ts +10 -0
  14. package/lib/components/features/archive/TMArchive.js +56 -25
  15. package/lib/components/features/blog/TMBlogCommentForm.d.ts +4 -4
  16. package/lib/components/features/blog/TMBlogCommentForm.js +76 -51
  17. package/lib/components/features/documents/TMDcmtBlog.d.ts +15 -0
  18. package/lib/components/features/documents/TMDcmtBlog.js +21 -33
  19. package/lib/components/features/documents/TMDcmtForm.d.ts +17 -3
  20. package/lib/components/features/documents/TMDcmtForm.js +205 -46
  21. package/lib/components/features/documents/TMDcmtTasks.d.ts +13 -0
  22. package/lib/components/features/documents/TMDcmtTasks.js +24 -0
  23. package/lib/components/features/documents/TMDragDropOverlay.js +2 -1
  24. package/lib/components/features/documents/TMMasterDetailDcmts.d.ts +8 -1
  25. package/lib/components/features/documents/TMMasterDetailDcmts.js +6 -6
  26. package/lib/components/features/documents/TMRelationViewer.d.ts +53 -3
  27. package/lib/components/features/documents/TMRelationViewer.js +232 -85
  28. package/lib/components/features/search/TMSearch.d.ts +10 -1
  29. package/lib/components/features/search/TMSearch.js +14 -5
  30. package/lib/components/features/search/TMSearchQueryPanel.d.ts +1 -1
  31. package/lib/components/features/search/TMSearchQueryPanel.js +36 -7
  32. package/lib/components/features/search/TMSearchResult.d.ts +10 -1
  33. package/lib/components/features/search/TMSearchResult.js +140 -422
  34. package/lib/components/features/search/TMSearchResultsMenuItems.d.ts +2 -2
  35. package/lib/components/features/search/TMSearchResultsMenuItems.js +33 -8
  36. package/lib/components/features/tasks/TMTaskForm.d.ts +38 -0
  37. package/lib/components/features/tasks/TMTaskForm.js +386 -0
  38. package/lib/components/features/tasks/TMTasksAgenda.d.ts +17 -0
  39. package/lib/components/features/tasks/TMTasksAgenda.js +107 -0
  40. package/lib/components/features/tasks/TMTasksCalendar.d.ts +21 -0
  41. package/lib/components/features/tasks/TMTasksCalendar.js +240 -0
  42. package/lib/components/features/tasks/TMTasksHeader.d.ts +14 -0
  43. package/lib/components/features/tasks/TMTasksHeader.js +37 -0
  44. package/lib/components/features/tasks/TMTasksPanelContent.d.ts +20 -0
  45. package/lib/components/features/tasks/TMTasksPanelContent.js +65 -0
  46. package/lib/components/features/tasks/TMTasksUtils.d.ts +132 -0
  47. package/lib/components/features/tasks/TMTasksUtils.js +634 -0
  48. package/lib/components/features/tasks/TMTasksUtilsView.d.ts +39 -0
  49. package/lib/components/features/tasks/TMTasksUtilsView.js +118 -0
  50. package/lib/components/features/tasks/TMTasksView.d.ts +40 -0
  51. package/lib/components/features/tasks/TMTasksView.js +560 -0
  52. package/lib/components/features/workflow/TMWorkflowPopup.d.ts +3 -1
  53. package/lib/components/features/workflow/TMWorkflowPopup.js +19 -6
  54. package/lib/components/features/workflow/diagram/RecipientList.js +4 -3
  55. package/lib/components/forms/Login/Chooser.js +1 -1
  56. package/lib/components/forms/TMChooserForm.d.ts +1 -1
  57. package/lib/components/forms/TMChooserForm.js +2 -2
  58. package/lib/components/grids/TMBlogAttachments.d.ts +42 -0
  59. package/lib/components/grids/TMBlogAttachments.js +43 -0
  60. package/lib/components/grids/TMBlogHeader.d.ts +31 -0
  61. package/lib/components/grids/TMBlogHeader.js +41 -0
  62. package/lib/components/grids/{TMBlogs.d.ts → TMBlogsPost.d.ts} +42 -58
  63. package/lib/components/grids/TMBlogsPost.js +628 -0
  64. package/lib/components/grids/{TMBlogsUtils.d.ts → TMBlogsPostUtils.d.ts} +61 -47
  65. package/lib/components/grids/{TMBlogsUtils.js → TMBlogsPostUtils.js} +146 -124
  66. package/lib/components/index.d.ts +14 -1
  67. package/lib/components/index.js +15 -1
  68. package/lib/components/layout/panelManager/TMPanelManagerContext.js +7 -0
  69. package/lib/components/settings/SettingsAppearance.js +8 -0
  70. package/lib/components/viewers/TMTidViewer.js +20 -2
  71. package/lib/css/tm-sdkui.css +1 -1
  72. package/lib/helper/SDKUI_Globals.d.ts +4 -1
  73. package/lib/helper/SDKUI_Globals.js +10 -1
  74. package/lib/helper/SDKUI_Localizator.d.ts +62 -4
  75. package/lib/helper/SDKUI_Localizator.js +618 -25
  76. package/lib/helper/TMCustomSearchBar.d.ts +8 -0
  77. package/lib/helper/TMCustomSearchBar.js +54 -0
  78. package/lib/helper/TMIcons.d.ts +2 -0
  79. package/lib/helper/TMIcons.js +6 -0
  80. package/lib/helper/TMImageLibrary.d.ts +3 -2
  81. package/lib/helper/TMImageLibrary.js +230 -230
  82. package/lib/helper/TMToppyMessage.d.ts +7 -0
  83. package/lib/helper/TMToppyMessage.js +42 -0
  84. package/lib/helper/TMUtils.d.ts +10 -1
  85. package/lib/helper/TMUtils.js +42 -1
  86. package/lib/helper/dcmtsHelper.d.ts +2 -0
  87. package/lib/helper/dcmtsHelper.js +18 -0
  88. package/lib/helper/helpers.js +1 -0
  89. package/lib/helper/index.d.ts +1 -0
  90. package/lib/helper/index.js +1 -0
  91. package/lib/hooks/useRelatedDocuments.d.ts +72 -0
  92. package/lib/hooks/useRelatedDocuments.js +655 -0
  93. package/lib/index.d.ts +1 -0
  94. package/lib/index.js +1 -0
  95. package/lib/ts/types.d.ts +14 -0
  96. package/lib/ts/types.js +15 -0
  97. package/lib/utils/theme.d.ts +1 -0
  98. package/lib/utils/theme.js +1 -0
  99. package/package.json +7 -7
  100. package/lib/components/grids/TMBlogs.js +0 -721
  101. package/lib/stories/TMButton.stories.d.ts +0 -4
  102. package/lib/stories/TMButton.stories.js +0 -29
  103. package/lib/stories/TMDataGrid.stories.d.ts +0 -9
  104. package/lib/stories/TMDataGrid.stories.js +0 -310
  105. package/lib/stories/TMHtmlContentDisplay.stories.d.ts +0 -6
  106. package/lib/stories/TMHtmlContentDisplay.stories.js +0 -45
  107. package/lib/stories/TMHtmlEditor.stories.d.ts +0 -6
  108. package/lib/stories/TMHtmlEditor.stories.js +0 -49
  109. package/lib/stories/TMIcons.stories.d.ts +0 -4
  110. package/lib/stories/TMIcons.stories.js +0 -13
  111. package/lib/stories/TMSDKUI_Localizator.stories.d.ts +0 -4
  112. package/lib/stories/TMSDKUI_Localizator.stories.js +0 -123
  113. package/lib/stories/TMStoriesUtils.d.ts +0 -1
  114. package/lib/stories/TMStoriesUtils.js +0 -10
  115. package/lib/stories/TMUserAvatar.stories.d.ts +0 -6
  116. package/lib/stories/TMUserAvatar.stories.js +0 -20
@@ -7,6 +7,7 @@ export declare const StyledMultiViewPanel: import("styled-components/dist/types"
7
7
  }>> & string;
8
8
  export declare const StyledParagraph: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>, never>> & string;
9
9
  export declare const StyledToolbarForm: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>> & string;
10
+ export declare const StyledReferenceButton: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, never>> & string;
10
11
  export declare const StyledPanelPage: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components/dist/types").Substitute<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, {
11
12
  $isOpen?: boolean;
12
13
  $padding?: string;
@@ -36,6 +36,46 @@ export const StyledToolbarForm = styled.div `
36
36
  gap: 2px;
37
37
  background-color: ${TMColors.toolbar_background};
38
38
  `;
39
+ export const StyledReferenceButton = styled.button `
40
+ display: flex;
41
+ flex-direction: column;
42
+ align-items: center;
43
+ justify-content: center;
44
+ padding: 10px 16px;
45
+ border-radius: 20px;
46
+ border: none;
47
+ background-color: ${TMColors.button_floating_background};
48
+ color: white;
49
+ font-size: 0.9rem;
50
+ font-weight: 500;
51
+ cursor: pointer;
52
+ transition: all 0.2s ease;
53
+ text-align: center;
54
+ max-width: 200px;
55
+ gap: 2px;
56
+
57
+ &:hover {
58
+ opacity: 0.9;
59
+ transform: translateY(-1px);
60
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
61
+ }
62
+
63
+ &:active {
64
+ transform: translateY(0);
65
+ }
66
+
67
+ &:disabled {
68
+ opacity: 0.5;
69
+ cursor: not-allowed;
70
+ }
71
+
72
+ span {
73
+ width: 100%;
74
+ white-space: nowrap;
75
+ overflow: hidden;
76
+ text-overflow: ellipsis;
77
+ }
78
+ `;
39
79
  export const StyledPanelPage = styled.div `
40
80
  position: absolute;
41
81
  width: calc(100% - 50px);
@@ -0,0 +1,11 @@
1
+ import { LayoutCustomButtonDescriptor } from '@topconsultnpm/sdk-ts';
2
+ import { MetadataValueDescriptorEx } from '../..';
3
+ type TMCustomButtonProps = {
4
+ button: LayoutCustomButtonDescriptor;
5
+ isModal?: boolean;
6
+ formData?: MetadataValueDescriptorEx[];
7
+ selectedItems?: Array<any>;
8
+ onClose?: () => void;
9
+ };
10
+ declare const TMCustomButton: (props: TMCustomButtonProps) => import("react/jsx-runtime").JSX.Element | null;
11
+ export default TMCustomButton;
@@ -0,0 +1,63 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { SDK_Globals } from '@topconsultnpm/sdk-ts';
3
+ import { useEffect, useRef, useState } from 'react';
4
+ import TMModal from './TMModal';
5
+ import styled from 'styled-components';
6
+ import { processButtonAttributes } from '../../helper/dcmtsHelper';
7
+ const IframeContainer = styled.div `
8
+ display: flex;
9
+ height: 100%;
10
+ flex-direction: column;
11
+ `;
12
+ const StyledIframe = styled.iframe `
13
+ border: none;
14
+ flex: 1;
15
+ `;
16
+ const TMCustomButton = (props) => {
17
+ const { button, isModal = true, formData, selectedItems, onClose } = props;
18
+ const { appName: scriptUrl, arguments: args } = button;
19
+ const iframeRef = useRef(null);
20
+ const attributes = processButtonAttributes(args, formData);
21
+ const [loading, setLoading] = useState(true);
22
+ const [error, setError] = useState(false);
23
+ const getTargetOrigin = (url) => {
24
+ if (!url)
25
+ return '*';
26
+ try {
27
+ const urlObj = new URL(url);
28
+ return urlObj.origin;
29
+ }
30
+ catch {
31
+ return '*';
32
+ }
33
+ };
34
+ useEffect(() => {
35
+ if (iframeRef.current?.contentWindow) {
36
+ const mergedAttributes = { ...attributes, selectedItems: selectedItems };
37
+ iframeRef.current.contentWindow.postMessage({
38
+ "options": mergedAttributes,
39
+ "selectedItems": selectedItems,
40
+ "session": SDK_Globals.tmSession
41
+ }, getTargetOrigin(scriptUrl));
42
+ }
43
+ clearTimeout(timeoutIframe);
44
+ }, [loading, error, scriptUrl, attributes]);
45
+ const handleLoad = () => setLoading(false);
46
+ const handleError = () => {
47
+ setLoading(false);
48
+ setError(true);
49
+ };
50
+ // Timeout di sicurezza nel caso l'evento 'error' non venga chiamato
51
+ const timeoutIframe = setTimeout(() => {
52
+ if (loading)
53
+ handleError();
54
+ }, 5000); // 5 secondi
55
+ useEffect(() => {
56
+ if (!isModal && scriptUrl) {
57
+ window.open(scriptUrl, '_blank');
58
+ onClose?.();
59
+ }
60
+ }, [isModal, scriptUrl, onClose]);
61
+ return isModal ? (_jsx(TMModal, { title: button.title, width: '60%', height: '60%', resizable: true, onClose: onClose, children: _jsxs(IframeContainer, { children: [error && _jsx("div", { children: "Si \u00E8 verificato un errore nel caricamento del contenuto." }), !error && _jsx(StyledIframe, { ref: iframeRef, loading: 'lazy', onLoad: handleLoad, onError: handleError, src: scriptUrl })] }) })) : null;
62
+ };
63
+ export default TMCustomButton;
@@ -50,7 +50,8 @@ const TMFileManagerDataGridView = (props) => {
50
50
  }
51
51
  };
52
52
  const cellDefaultRender = useCallback((cellData) => {
53
- return renderHighlightedText(cellData.value.toString(), searchText, false);
53
+ const value = cellData.value ?? '';
54
+ return renderHighlightedText(value.toString(), searchText, false);
54
55
  }, [searchText]);
55
56
  const cellNameRender = useCallback((cellData) => {
56
57
  const { checkoutDate, checkOutUserID, checkOutUserName, version, ext, creationTime, lastUpdateTime } = cellData.data;
@@ -66,6 +67,8 @@ const TMFileManagerDataGridView = (props) => {
66
67
  return _jsx("div", { style: { display: 'flex', justifyContent: 'center', alignItems: 'center' }, children: _jsx(TMDcmtIcon, { tid: item.tid, did: item.did, fileExtension: item.ext, downloadMode: 'openInNewWindow', tooltipContent: tooltipContent }) });
67
68
  }, []);
68
69
  const cellDatetimeRender = useCallback((cellData) => {
70
+ if (!cellData.value)
71
+ return '-';
69
72
  return renderHighlightedText(Globalization.getDateTimeDisplayValue(cellData.value).toString(), searchText, false);
70
73
  }, [searchText]);
71
74
  const cellSizeRender = useCallback((cellData) => {
@@ -48,7 +48,8 @@ export interface ITMLayoutContainerProps {
48
48
  alignItems?: string;
49
49
  direction?: 'vertical' | 'horizontal';
50
50
  onClick?: () => void;
51
+ onContextMenu?: (e: React.MouseEvent) => void;
51
52
  }
52
- declare const TMLayoutContainer: ({ gap, onClick, justifyContent, alignItems, children, direction }: ITMLayoutContainerProps) => import("react/jsx-runtime").JSX.Element;
53
+ declare const TMLayoutContainer: ({ gap, onClick, justifyContent, alignItems, children, direction, onContextMenu }: ITMLayoutContainerProps) => import("react/jsx-runtime").JSX.Element;
53
54
  export { TMCard, TMLayoutItem, TMSplitterLayout };
54
55
  export default TMLayoutContainer;
@@ -193,9 +193,9 @@ const TMSplitterLayout = ({ animation = false, showSeparator = true, separatorCo
193
193
  const TMLayoutItem = ({ onClick, children, width = '100%', minWidth, maxWidth, maxHeight, height = '100%', minHeight }) => {
194
194
  return (_jsx(StyledLayoutItem, { onClick: onClick, "$height": height, "$maxHeight": maxHeight, "$minHeight": minHeight, "$width": width, "$minWidth": minWidth, "$maxWidth": maxWidth, children: children }));
195
195
  };
196
- const TMLayoutContainer = ({ gap = 3, onClick, justifyContent = 'flex-start', alignItems = 'flex-start', children, direction = 'vertical' }) => {
196
+ const TMLayoutContainer = ({ gap = 3, onClick, justifyContent = 'flex-start', alignItems = 'flex-start', children, direction = 'vertical', onContextMenu }) => {
197
197
  const renderedEls = () => { return (React.Children.map(children, child => (child))); };
198
- return (_jsxs(StyledLayoutContainer, { "$alignItems": alignItems, "$justifyContent": justifyContent, onClick: onClick, "$direction": direction, style: { gap: gap }, children: [" ", renderedEls(), " "] }));
198
+ return (_jsxs(StyledLayoutContainer, { "$alignItems": alignItems, "$justifyContent": justifyContent, onClick: onClick, onContextMenu: onContextMenu, "$direction": direction, style: { gap: gap }, children: [" ", renderedEls(), " "] }));
199
199
  };
200
200
  export { TMCard, TMLayoutItem, TMSplitterLayout };
201
201
  export default TMLayoutContainer;
@@ -152,24 +152,11 @@ const ResponsiveMessageText = styled.div `
152
152
  line-height: 1.1;
153
153
  }
154
154
  `;
155
- const ResponsiveButton = styled(TMButton) `
156
- font-size: ${props => props.fontSize || 'clamp(12px, 2vw, 1.1rem)'} !important;
157
- padding: clamp(2px, 1vw, 8px) clamp(4px, 2vw, 12px) !important;
158
- min-width: clamp(30px, 10vw, 60px) !important;
159
- white-space: nowrap !important;
160
-
161
- @media (max-width: 200px) {
162
- padding: 2px 4px !important;
163
- min-width: 25px !important;
164
- font-size: 8px !important;
165
- }
166
-
167
- @media (max-width: 150px) {
168
- padding: 1px 3px !important;
169
- min-width: 20px !important;
170
- font-size: 7px !important;
171
- }
172
- `;
155
+ // ResponsiveButton wrapper component to avoid circular dependency
156
+ const ResponsiveButton = (props) => {
157
+ const { fontSize: customFontSize, ...otherProps } = props;
158
+ return _jsx(TMButton, { fontSize: customFontSize || 'clamp(12px, 2vw, 1.1rem)', padding: "clamp(2px, 1vw, 8px) clamp(4px, 2vw, 12px)", width: "clamp(30px, 10vw, 60px)", ...otherProps });
159
+ };
173
160
  const ResponsiveMessageBody = ({ message, isMobile, MessageToolbar, showToppy }) => {
174
161
  return (_jsxs(ResponsiveMessageContainer, { children: [_jsxs(ResponsiveMessageContent, { "$isMobile": isMobile, children: [showToppy && _jsx(ResponsiveToppyImage, { "$isMobile": isMobile, src: toppy, alt: "Toppy" }), _jsx(ResponsiveMessageText, { "$isMobile": isMobile, children: typeof message === 'string' ? _jsx(Message, { msg: message }) : message })] }), _jsx(MessageToolbar, {})] }));
175
162
  };
@@ -332,15 +332,16 @@ const TMTreeView = ({ dataSource = [], focusedItem, selectedItems = [], allowMul
332
332
  if (input) {
333
333
  input.indeterminate = isIndeterminate(node);
334
334
  }
335
- } })), _jsx("div", { style: { display: 'flex', alignItems: 'center', flex: 1, minHeight: 0 }, onClick: (e) => { handleNodeClick(node, e); }, children: itemRender(node) })] }), node.expanded && node.items && (_jsxs("div", { style: { paddingLeft: 20, width: '100%' }, children: [renderTree(getVisibleItems(node)), needsLoadMoreButton(node) && (_jsx("div", { style: { display: 'flex', justifyContent: 'center', margin: '5px 0' }, children: _jsx(TMButton, { onClick: () => handleLoadMoreItems(node.key), showTooltip: false, caption: `Carica altri ${itemsPerPage}... (${node.visibleItemsCount ?? itemsPerPage} / ${node.items?.length ?? 0})` }) }))] }))] }, node.key)));
335
+ } })), _jsx("div", { style: { display: 'flex', alignItems: 'center', flex: 1, minWidth: 0 }, onClick: (e) => { handleNodeClick(node, e); }, children: itemRender(node) })] }), node.expanded && node.items && (_jsxs("div", { style: { paddingLeft: 20, width: '100%' }, children: [renderTree(getVisibleItems(node)), needsLoadMoreButton(node) && (_jsx("div", { style: { display: 'flex', justifyContent: 'center', margin: '5px 0' }, children: _jsx(TMButton, { onClick: () => handleLoadMoreItems(node.key), showTooltip: false, caption: `Carica altri ${itemsPerPage}... (${node.visibleItemsCount ?? itemsPerPage} / ${node.items?.length ?? 0})` }) }))] }))] }, node.key)));
336
336
  }, [handleNodeClick, handleNodeToggle, handleCheckboxChange, focusedItem, selectedItems, allowMultipleSelection, getVisibleItems, needsLoadMoreButton, handleLoadMoreItems, itemsPerPage]);
337
- return (_jsx("div", { style: { height: '100%', width: '100%', overflowY: 'auto', padding: '0px 5px 2px 2px' }, children: renderTree(dataSource) }));
337
+ return (_jsx("div", { style: { height: '100%', width: '100%', overflowY: 'auto', overflowX: 'hidden', padding: '0px 5px 2px 2px' }, children: renderTree(dataSource) }));
338
338
  };
339
339
  export default TMTreeView;
340
340
  export const StyledTreeNode = styled.div `
341
341
  display: flex;
342
342
  flex-direction: row;
343
343
  width: 100%;
344
+ min-width: 0;
344
345
  min-height: 22px;
345
346
  max-height: 30px;
346
347
  gap: 5px;
@@ -1,4 +1,5 @@
1
1
  import { ValidationItem } from '@topconsultnpm/sdk-ts';
2
+ export declare const sanitizeAndFormatComment: (raw?: string) => string;
2
3
  export interface ITMHtmlEditor {
3
4
  /** Width of the editor (e.g., '100%', '500px') */
4
5
  width?: string;
@@ -28,6 +29,10 @@ export interface ITMHtmlEditor {
28
29
  toolbarMode?: 'compact' | 'expanded';
29
30
  /** If true, the editor will be focused on mount */
30
31
  autoFocus?: boolean;
32
+ /** Maximum number of characters allowed in the editor */
33
+ maxLength?: number;
34
+ /** If true, displays a character count below the editor */
35
+ showCount?: boolean;
31
36
  }
32
37
  declare const TMHtmlEditor: (props: ITMHtmlEditor) => import("react/jsx-runtime").JSX.Element;
33
38
  export default TMHtmlEditor;
@@ -4,8 +4,35 @@ import HtmlEditor, { Toolbar, Item } from 'devextreme-react/html-editor';
4
4
  import ReactDOM from 'react-dom';
5
5
  import { SDKUI_Localizator } from '../../helper';
6
6
  import TMVilViewer from '../base/TMVilViewer';
7
+ import { TMMessageBoxManager, ButtonNames } from '../base/TMPopUp';
8
+ import TMTooltip from '../base/TMTooltip';
9
+ import { useDeviceType, DeviceType } from '../base/TMDeviceProvider';
10
+ export const sanitizeAndFormatComment = (raw = "") => {
11
+ if (!raw)
12
+ return "";
13
+ let cleanComment = raw
14
+ // Simplify dx-mention markup - replace with just @username
15
+ .replace(/<span class="dx-mention"[^>]*>\uFEFF?<span[^>]*><span>@<\/span>([^<]+)<\/span>\uFEFF?<\/span>/gi, '@$1')
16
+ // Replace </p> with '' only if followed by <ol> or <ul>
17
+ .replace(/<\/p>(?=\s*<(ol|ul)>)/gi, '')
18
+ // Replace all other </p> with '\r\n'
19
+ .replace(/<\/p>/gi, '\r\n')
20
+ // Remove all <p> tags
21
+ .replace(/<p>/gi, '')
22
+ // Remove all <br> tags
23
+ .replace(/<br>/gi, '')
24
+ // Trim whitespace
25
+ .trim();
26
+ // Remove dangerous HTML elements
27
+ cleanComment = cleanComment.replace(/<(script|iframe|embed|object|link|style|img|video|audio|svg|form|input|button|textarea|select|pre|function)[^>]*>/gi, '');
28
+ return cleanComment;
29
+ };
7
30
  const TMHtmlEditor = (props) => {
8
- const { width = "100%", height = "100%", initialMarkup = "", mentionsConfig, onValueChanged, validationItems = [], isEditorEnabled: isEditorEnabledProp = false, toolbarMode = 'compact', autoFocus = false } = props;
31
+ const { width = "100%", height = "100%", initialMarkup = "", mentionsConfig, onValueChanged, validationItems = [], isEditorEnabled: isEditorEnabledProp = false, toolbarMode = 'compact', autoFocus = false, maxLength = 1000, showCount = true, } = props;
32
+ // Get the current device type (e.g., mobile, tablet, desktop) using a custom hook.
33
+ const deviceType = useDeviceType();
34
+ // This avoids unnecessary re-renders by only recalculating when deviceType changes.
35
+ let isMobile = useMemo(() => { return deviceType === DeviceType.MOBILE; }, [deviceType]);
9
36
  // Ref for HtmlEditor instance
10
37
  const editorRef = useRef(null);
11
38
  // State for editor enable/disable
@@ -14,13 +41,15 @@ const TMHtmlEditor = (props) => {
14
41
  const [markup, setMarkup] = useState(initialMarkup);
15
42
  // State to track the position (x, y coordinates) where the custom context menu should be displayed.
16
43
  const [contextMenuPos, setContextMenuPos] = useState(null);
17
- // State to indicate whether a paste operation is currently in progress
18
- const [isPasting, setIsPasting] = useState(false);
44
+ // State to track remaining characters
45
+ const [charactersRemaining, setCharactersRemaining] = useState(maxLength - initialMarkup.length);
46
+ useEffect(() => {
47
+ const cleanedMarkup = sanitizeAndFormatComment(markup);
48
+ setCharactersRemaining(maxLength - cleanedMarkup.length);
49
+ }, [markup, maxLength]);
19
50
  // Function to read text from the clipboard, format it, and paste into the Quill editor
20
51
  const pasteFromClipboard = async () => {
21
52
  try {
22
- // Set flag to indicate paste operation is in progress
23
- setIsPasting(true);
24
53
  // Read plain text from the clipboard asynchronously
25
54
  let text = await navigator.clipboard.readText();
26
55
  text = '<p>' + text.replace(/\r\n/g, '</p><p>') + '</p>';
@@ -52,10 +81,6 @@ const TMHtmlEditor = (props) => {
52
81
  // Log any errors that might occur during clipboard access or pasting
53
82
  console.warn('Clipboard paste failed:', err);
54
83
  }
55
- finally {
56
- // Reset paste state after a short delay to restore UI state
57
- setTimeout(() => setIsPasting(false), 500);
58
- }
59
84
  };
60
85
  useEffect(() => {
61
86
  const handleKeyDown = (e) => {
@@ -116,7 +141,7 @@ const TMHtmlEditor = (props) => {
116
141
  }, [autoFocus]);
117
142
  // Memoized check for validation errors
118
143
  const hasValidationErrors = useMemo(() => {
119
- return !isPasting && validationItems && validationItems.length > 0;
144
+ return validationItems && validationItems.length > 0;
120
145
  }, [validationItems]);
121
146
  // Handler function triggered by the context menu's "Paste" action
122
147
  const handlePaste = async () => {
@@ -125,9 +150,44 @@ const TMHtmlEditor = (props) => {
125
150
  // Close the custom context menu after pasting
126
151
  setContextMenuPos(null);
127
152
  };
128
- return (_jsxs("div", { style: { height: hasValidationErrors ? `calc(${height} - 30px)` : "100%", width: width }, onContextMenu: handleContextMenu, children: [_jsx("div", { className: "custom-mentions-wrapper", style: { borderWidth: '1px', borderStyle: 'solid', borderColor: hasValidationErrors ? "red" : "#e0e0e0 #e0e0e0 #616161", width: "100%", height: "100%" }, children: _jsx(HtmlEditor, { ref: editorRef, placeholder: SDKUI_Localizator.TypeAMessage + "...", width: "100%", height: "100%", value: markup, onValueChange: onValueChangeCallback, mentions: mentionsConfig, style: { overflow: 'hidden', outline: "none", fontSize: '1rem' }, children: isEditorEnabled && (toolbarMode === 'compact' ?
153
+ return (_jsxs("div", { style: {
154
+ height: height,
155
+ width: width
156
+ }, children: [_jsx("div", { className: "custom-mentions-wrapper", onContextMenu: handleContextMenu, style: {
157
+ borderWidth: '1px',
158
+ borderStyle: 'solid',
159
+ borderColor: hasValidationErrors ? "red" : "#e0e0e0 #e0e0e0 #616161",
160
+ width: "100%",
161
+ height: `calc(100% - ${showCount ? 15 : 0}px - ${validationItems.length > 0 ? 15 : 0}px)`,
162
+ }, children: _jsx(HtmlEditor, { ref: editorRef, placeholder: SDKUI_Localizator.TypeAMessage + "...", width: "100%", height: "100%", value: markup, onValueChange: onValueChangeCallback, mentions: mentionsConfig, style: { overflow: 'hidden', outline: "none", fontSize: '1rem' }, children: isEditorEnabled && (toolbarMode === 'compact' ?
129
163
  _jsxs(Toolbar, { multiline: false, children: [_jsx(Item, { name: "undo" }), _jsx(Item, { name: "redo" }), _jsx(Item, { name: "separator" }), _jsx(Item, { name: "bold" }), _jsx(Item, { name: "italic" }), _jsx(Item, { name: "strike" }), _jsx(Item, { name: "underline" }), _jsx(Item, { name: "separator" }), _jsx(Item, { name: "orderedList" }), _jsx(Item, { name: "bulletList" })] }) :
130
- _jsxs(Toolbar, { children: [_jsx(Item, { name: "undo" }), _jsx(Item, { name: "redo" }), _jsx(Item, { name: "separator" }), _jsx(Item, { name: "separator" }), _jsx(Item, { name: "bold" }), _jsx(Item, { name: "italic" }), _jsx(Item, { name: "strike" }), _jsx(Item, { name: "underline" }), _jsx(Item, { name: "separator" }), _jsx(Item, { name: "alignLeft" }), _jsx(Item, { name: "alignCenter" }), _jsx(Item, { name: "alignRight" }), _jsx(Item, { name: "alignJustify" }), _jsx(Item, { name: "separator" }), _jsx(Item, { name: "color" }), _jsx(Item, { name: "background" }), _jsx(Item, { name: "separator" }), _jsx(Item, { name: "link" }), _jsx(Item, { name: "image" }), _jsx(Item, { name: "separator" })] })) }) }), !isPasting && validationItems.length > 0 && (_jsx("div", { style: { width: "100%", height: "30px" }, children: _jsx(TMVilViewer, { vil: validationItems }) })), contextMenuPos &&
164
+ _jsxs(Toolbar, { children: [_jsx(Item, { name: "undo" }), _jsx(Item, { name: "redo" }), _jsx(Item, { name: "separator" }), _jsx(Item, { name: "separator" }), _jsx(Item, { name: "bold" }), _jsx(Item, { name: "italic" }), _jsx(Item, { name: "strike" }), _jsx(Item, { name: "underline" }), _jsx(Item, { name: "separator" }), _jsx(Item, { name: "alignLeft" }), _jsx(Item, { name: "alignCenter" }), _jsx(Item, { name: "alignRight" }), _jsx(Item, { name: "alignJustify" }), _jsx(Item, { name: "separator" }), _jsx(Item, { name: "color" }), _jsx(Item, { name: "background" }), _jsx(Item, { name: "separator" }), _jsx(Item, { name: "link" }), _jsx(Item, { name: "image" }), _jsx(Item, { name: "separator" })] })) }) }), showCount ? ((() => {
165
+ const cleanedMarkup = sanitizeAndFormatComment(markup);
166
+ const showInfoIcon = charactersRemaining !== maxLength;
167
+ return (_jsxs("div", { style: {
168
+ display: 'flex',
169
+ alignItems: 'center',
170
+ justifyContent: 'flex-end',
171
+ fontSize: 12,
172
+ color: '#6c757d',
173
+ marginTop: 4,
174
+ gap: 4,
175
+ }, children: [`${Math.max(charactersRemaining, 0)} ${SDKUI_Localizator.CharactersRemaining}`, showInfoIcon && (_jsx(TMTooltip, { content: 'Markup HTML', children: _jsx("span", { className: "dx-icon-codeblock", style: { fontSize: 22, cursor: 'pointer' }, onClick: () => {
176
+ TMMessageBoxManager.show({
177
+ title: 'Markup HTML',
178
+ initialWidth: !isMobile ? '700px' : undefined,
179
+ message: (_jsx("pre", { style: {
180
+ whiteSpace: 'pre-wrap',
181
+ background: '#f5f5f5',
182
+ padding: '12px',
183
+ borderRadius: '6px',
184
+ }, children: cleanedMarkup })),
185
+ resizable: true,
186
+ showToppy: false,
187
+ buttons: [ButtonNames.OK],
188
+ });
189
+ } }) }))] }));
190
+ })()) : null, validationItems.length > 0 && _jsx(TMVilViewer, { vil: validationItems }), contextMenuPos &&
131
191
  ReactDOM.createPortal(_jsxs("div", { style: {
132
192
  position: 'fixed',
133
193
  top: contextMenuPos.y,
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import React, { useEffect, useState } from "react";
2
+ import React, { useCallback, useEffect, useMemo, useState } from "react";
3
3
  import styled from "styled-components";
4
4
  import { AccessLevels, DcmtTypeListCacheService, LayoutItemTypes, LayoutModes, MetadataDataDomains, MetadataDataTypes, SDK_Globals, SystemMIDsAsNumber, SystemTIDs, TemplateTIDs, WorkItemMetadataNames } from '@topconsultnpm/sdk-ts';
5
5
  import { IconUndo, IconPencil, IconFunction, IconMenuVertical, IconDataList, SDKUI_Localizator, IconNull, stringIsNullOrEmpty, deepCompare, SDKUI_Globals, IconDcmtTypeSys } from "../../helper";
@@ -10,7 +10,7 @@ import TMTooltip from "../base/TMTooltip";
10
10
  import TMCheckBox from "./TMCheckBox";
11
11
  import TMMetadataEditor, { useMetadataEditableList } from "./TMMetadataEditor";
12
12
  import { FormulaHelper } from "./TMFormulaEditor";
13
- import { DraftsMIDs } from "../../ts";
13
+ import { DraftsMIDs, DSAttachsMIDs } from "../../ts";
14
14
  import { TMNothingToShow } from "../features/documents/TMDcmtPreview";
15
15
  import TMAccordion from "../base/TMAccordion";
16
16
  export var ShowCheckBoxesMode;
@@ -33,7 +33,7 @@ const TMMetadataValues = ({ showCheckBoxes = ShowCheckBoxesMode.Never, checkPerm
33
33
  const [selectedItem, setSelectedItem] = useState(undefined);
34
34
  const [prevMetadataValues, setPrevMetadataValues] = useState([]);
35
35
  const [inputMidsApplied, setInputMidsApplied] = useState(false);
36
- const onChangeHandler = (newValue, mid) => {
36
+ const onChangeHandler = useCallback((newValue, mid) => {
37
37
  let newValues = structuredClone(metadataValues);
38
38
  const item = newValues.find(value => value.mid === mid);
39
39
  if (item) {
@@ -51,25 +51,25 @@ const TMMetadataValues = ({ showCheckBoxes = ShowCheckBoxesMode.Never, checkPerm
51
51
  }
52
52
  }
53
53
  onValueChanged?.(newValues);
54
- };
55
- const editorValidationHandler = (mid) => {
54
+ }, [metadataValues, showCheckBoxes, dynDataListsToBeRefreshed, onValueChanged]);
55
+ const editorValidationHandler = useCallback((mid) => {
56
56
  const md = metadataValues?.find(m => m.mid === mid);
57
57
  const validationItem = validationItems.find(vil => vil.PropertyName === md?.md?.nameLoc);
58
58
  return validationItem ? [validationItem] : [];
59
- };
60
- const isEditable = (mid) => {
59
+ }, [metadataValues, validationItems]);
60
+ const isEditable = useCallback((mid) => {
61
61
  let md = currentDTD?.metadata?.find(o => o.id == mid);
62
62
  if (!md)
63
63
  return false;
64
64
  let isList = md.dataDomain == MetadataDataDomains.DataList || md.dataDomain == MetadataDataDomains.DynamicDataList || md.dataDomain == MetadataDataDomains.UserID;
65
65
  return isList && isEditableList(mid);
66
- };
67
- const handleMetadataValueSelection = (item) => {
66
+ }, [currentDTD, isEditableList]);
67
+ const handleMetadataValueSelection = useCallback((item) => {
68
68
  if (selectedItem?.mid !== item.mid) {
69
69
  setSelectedItem(item);
70
70
  onFocusedItemChanged?.(item);
71
71
  }
72
- };
72
+ }, [selectedItem, onFocusedItemChanged]);
73
73
  useEffect(() => {
74
74
  if (!TID) {
75
75
  setCurrentDTD(undefined);
@@ -184,7 +184,7 @@ const TMMetadataValues = ({ showCheckBoxes = ShowCheckBoxesMode.Never, checkPerm
184
184
  }
185
185
  return toBeRefreshed;
186
186
  };
187
- const getAdvancedMenuItems = (mvd) => {
187
+ const getAdvancedMenuItems = useCallback((mvd) => {
188
188
  let md = mvd.md;
189
189
  if (!md)
190
190
  return [];
@@ -241,9 +241,9 @@ const TMMetadataValues = ({ showCheckBoxes = ShowCheckBoxesMode.Never, checkPerm
241
241
  ...customMenuItems
242
242
  ];
243
243
  return menu;
244
- };
244
+ }, [checkPerms, isExpertMode, customMenuItems, metadataValues, onAdvancedMenuClick, onValueChanged]);
245
245
  // Helper function to render a single metadata item
246
- const renderMetadataItem = (item, isReadOnlyOverride = false) => (_jsxs(StyledRow, { style: { marginTop: item.md?.dataType === MetadataDataTypes.DateTime ? '6px' : '0', gap: '8px' }, onClick: () => { handleMetadataValueSelection(item); }, onFocus: () => { handleMetadataValueSelection(item); }, children: [showCheckBoxes !== ShowCheckBoxesMode.Never &&
246
+ const renderMetadataItem = useCallback((item, isReadOnlyOverride = false) => (_jsxs(StyledRow, { style: { marginTop: item.md?.dataType === MetadataDataTypes.DateTime ? '6px' : '0', gap: '8px' }, onClick: () => { handleMetadataValueSelection(item); }, onFocus: () => { handleMetadataValueSelection(item); }, children: [showCheckBoxes !== ShowCheckBoxesMode.Never &&
247
247
  _jsx(TMCheckBox, { elementStyle: { marginTop: item.md?.dataType === MetadataDataTypes.DateTime ? '14px' : '20px' }, value: item.isSelected, disabled: showCheckBoxes === ShowCheckBoxesMode.AlwaysReadOnly, onValueChanged: (newValue) => {
248
248
  let newValues = structuredClone(metadataValues);
249
249
  const mvd = newValues.find(value => value.mid === item.mid);
@@ -296,8 +296,8 @@ const TMMetadataValues = ({ showCheckBoxes = ShowCheckBoxesMode.Never, checkPerm
296
296
  mvd.isSelected = !stringIsNullOrEmpty(mvd.value);
297
297
  }
298
298
  onValueChanged?.(newValues);
299
- } }) }), !isReadOnly && _jsx("div", { style: { marginTop: item.md?.dataType === MetadataDataTypes.DateTime ? '12px' : '18px' }, onClick: () => { handleMetadataValueSelection(item); }, children: _jsx(TMDropDownMenu, { backgroundColor: 'white', color: TMColors.button_icon, borderRadius: '3px', content: _jsx(TMButton, { btnStyle: 'icon', icon: _jsx(IconMenuVertical, {}), showTooltip: false }), disabled: item.isLexProt === 1, items: getAdvancedMenuItems(item) }) })] }, item.mid));
300
- const layoutWorkItem = () => {
299
+ } }) }), !isReadOnly && _jsx("div", { style: { marginTop: item.md?.dataType === MetadataDataTypes.DateTime ? '12px' : '18px' }, onClick: () => { handleMetadataValueSelection(item); }, children: _jsx(TMDropDownMenu, { backgroundColor: 'white', color: TMColors.button_icon, borderRadius: '3px', content: _jsx(TMButton, { btnStyle: 'icon', icon: _jsx(IconMenuVertical, {}), showTooltip: false }), disabled: item.isLexProt === 1, items: getAdvancedMenuItems(item) }) })] }, item.mid)), [TID, showCheckBoxes, showNullValueCheckBoxes, isReadOnly, layoutMode, selectedMID, isOpenDistinctValues, openChooserBySingleClick, dynDataListsToBeRefreshed, metadataValues, metadataValuesOrig, validationItems, onValueChanged, handleMetadataValueSelection, getAdvancedMenuItems, onChangeHandler, editorValidationHandler, isEditable]);
300
+ const layoutWorkItem = useMemo(() => {
301
301
  const workItemData = [];
302
302
  const technicalWorkItemData = [];
303
303
  const documentData = [];
@@ -328,8 +328,8 @@ const TMMetadataValues = ({ showCheckBoxes = ShowCheckBoxesMode.Never, checkPerm
328
328
  }
329
329
  });
330
330
  return (_jsxs("div", { style: { width: '100%' }, children: [documentData.length > 0 && _jsx(TMAccordion, { title: SDKUI_Localizator.DocumentData, children: documentData.map(item => renderMetadataItem(item)) }), workItemData.length > 0 && _jsx(TMAccordion, { title: SDKUI_Localizator.WorkItemData, children: workItemData.map(item => renderMetadataItem(item, true)) }), technicalWorkItemData.length > 0 && _jsx(TMAccordion, { title: SDKUI_Localizator.WorkItemTechnicalData, defaultCollapsed: true, children: technicalWorkItemData.map(item => renderMetadataItem(item, true)) })] }));
331
- };
332
- const layoutDraft = () => {
331
+ }, [metadataValues, showCheckBoxes, showNullValueCheckBoxes, isReadOnly, dynDataListsToBeRefreshed, validationItems, selectedMID, isOpenDistinctValues, openChooserBySingleClick, metadataValuesOrig]);
332
+ const layoutDraft = useMemo(() => {
333
333
  // Definiamo l'ordine desiderato per gli elementi
334
334
  const desiredDraftOrder = [
335
335
  DraftsMIDs.Name,
@@ -384,36 +384,73 @@ const TMMetadataValues = ({ showCheckBoxes = ShowCheckBoxesMode.Never, checkPerm
384
384
  checkOutData.push(tempCICODataMap[id]);
385
385
  }
386
386
  });
387
- return (_jsxs("div", { style: { width: '100%' }, children: [draftData.length > 0 && _jsx(TMAccordion, { title: SDKUI_Localizator.Draft, children: draftData.map(item => renderMetadataItem(item)) }), checkOutData.length > 0 && _jsx(TMAccordion, { title: `${SDKUI_Localizator.CheckIn}/${SDKUI_Localizator.CheckOut}`, children: checkOutData.map(item => renderMetadataItem(item, true)) })] }));
388
- };
389
- const layoutCustom = () => {
387
+ return (_jsxs("div", { style: { width: '100%' }, children: [draftData.length > 0 && _jsx(TMAccordion, { title: SDKUI_Localizator.Draft, children: draftData.map(item => renderMetadataItem(item, isReadOnly)) }), checkOutData.length > 0 && _jsx(TMAccordion, { title: `${SDKUI_Localizator.CheckIn}/${SDKUI_Localizator.CheckOut}`, children: checkOutData.map(item => renderMetadataItem(item, true)) })] }));
388
+ }, [metadataValues, showCheckBoxes, showNullValueCheckBoxes, isReadOnly, dynDataListsToBeRefreshed, validationItems, selectedMID, isOpenDistinctValues, openChooserBySingleClick, metadataValuesOrig]);
389
+ const layoutDsAttachs = useMemo(() => {
390
+ const dsAttachsData = [];
391
+ metadataValues.forEach(item => {
392
+ switch (item.md?.id) {
393
+ // case DSAttachsMIDs.DSID:
394
+ case DSAttachsMIDs.Name:
395
+ case DSAttachsMIDs.Description:
396
+ dsAttachsData.push(item);
397
+ break;
398
+ default:
399
+ break;
400
+ }
401
+ });
402
+ return (_jsx("div", { style: { width: '100%' }, children: dsAttachsData.length > 0 && _jsx(TMAccordion, { title: SDKUI_Localizator.Attachment, children: dsAttachsData.map(item => renderMetadataItem(item, isReadOnly)) }) }));
403
+ }, [metadataValues, showCheckBoxes, showNullValueCheckBoxes, isReadOnly, dynDataListsToBeRefreshed, validationItems, selectedMID, isOpenDistinctValues, openChooserBySingleClick, metadataValuesOrig]);
404
+ const layoutCustom = useMemo(() => {
390
405
  if (!layout || !layout.items || layout.items.length === 0) {
391
406
  return metadataValues.map((item) => renderMetadataItem(item));
392
407
  }
393
- // Build a map of items by ID for quick lookup
408
+ // Build a map of items by ID for quick lookup (include negative/zero IDs)
394
409
  const itemsById = new Map();
395
410
  layout.items.forEach(item => {
396
- if (item.layoutItemID) {
411
+ if (item.layoutItemID !== undefined && item.layoutItemID !== null) {
397
412
  itemsById.set(item.layoutItemID, item);
398
413
  }
399
414
  });
400
- // Find root items (items without parent or parent === 0)
401
- const rootItems = layout.items.filter(item => !item.parentID || item.parentID === 0);
402
- // Recursive function to get children of an item
415
+ // Determine root items. Prefer explicit LayoutRoot items if present;
416
+ // otherwise fall back to items with no parent (parentID undefined or -1).
417
+ const hasExplicitRoot = layout.items.some(item => item.type === LayoutItemTypes.LayoutRoot);
418
+ const rootItems = hasExplicitRoot
419
+ ? layout.items.filter(item => item.type === LayoutItemTypes.LayoutRoot)
420
+ : layout.items.filter(item => item.parentID === undefined || item.parentID === -1);
421
+ // Recursive function to get children of an item. Handle parentID === -1 and undefined
422
+ // because some layouts use -1 for root while children use undefined.
403
423
  const getChildren = (parentID) => {
424
+ if (parentID === -1) {
425
+ return layout.items?.filter(item => item.parentID === -1 || item.parentID === undefined) || [];
426
+ }
427
+ if (parentID === undefined) {
428
+ return layout.items?.filter(item => item.parentID === undefined) || [];
429
+ }
404
430
  return layout.items?.filter(item => item.parentID === parentID) || [];
405
431
  };
406
432
  // Recursive function to render layout items with depth tracking for indentation
407
- const renderLayoutItem = (layoutItem, depth = 0) => {
433
+ // Prevent infinite recursion by tracking visited layoutItemIDs (handles malformed layouts where an item
434
+ // may reference itself as a child or cycles exist).
435
+ const renderLayoutItem = (layoutItem, depth = 0, visited = new Set()) => {
436
+ const id = layoutItem.layoutItemID ?? 0;
437
+ if (visited.has(id))
438
+ return null;
439
+ visited.add(id);
440
+ // Check if this is a LayoutRoot - just render its children
441
+ if (layoutItem.type === LayoutItemTypes.LayoutRoot) {
442
+ const children = getChildren(layoutItem.layoutItemID);
443
+ return (_jsx(React.Fragment, { children: children.map(child => renderLayoutItem(child, depth, visited)) }, `root-${layoutItem.layoutItemID}`));
444
+ }
408
445
  // Check if this is a LayoutGroup
409
- if (layoutItem.type === LayoutItemTypes.LayoutGroup && layoutItem.lgd) {
410
- const children = getChildren(layoutItem.layoutItemID ?? 0);
446
+ else if (layoutItem.type === LayoutItemTypes.LayoutGroup && layoutItem.lgd) {
447
+ const children = getChildren(layoutItem.layoutItemID);
411
448
  const groupDescriptor = layoutItem.lgd;
412
449
  const groupTitle = groupDescriptor.caption || `Group ${layoutItem.layoutItemID}`;
413
450
  const isCollapsed = false; // LayoutGroupDescriptor doesn't have collapsed property
414
451
  // Apply indentation only to subgroups (depth > 0), not to root groups
415
452
  const indentationPx = depth > 0 ? depth * 10 : 0;
416
- return (_jsx("div", { style: { paddingLeft: `${indentationPx}px` }, children: _jsx(TMAccordion, { title: groupTitle, defaultCollapsed: isCollapsed, children: children.map(child => renderLayoutItem(child, depth + 1)) }) }, `group-wrapper-${layoutItem.layoutItemID}`));
453
+ return (_jsx("div", { style: { paddingLeft: `${indentationPx}px` }, children: _jsx(TMAccordion, { title: groupTitle, defaultCollapsed: isCollapsed, children: children.map(child => renderLayoutItem(child, depth + 1, visited)) }) }, `group-wrapper-${layoutItem.layoutItemID}`));
417
454
  }
418
455
  // Check if this is a LayoutControlItem (metadata field)
419
456
  else if (layoutItem.type === LayoutItemTypes.LayoutControlItem && layoutItem.lcid) {
@@ -425,8 +462,9 @@ const TMMetadataValues = ({ showCheckBoxes = ShowCheckBoxesMode.Never, checkPerm
425
462
  const metadataItem = metadataValues.find(m => m.mid === mid);
426
463
  if (!metadataItem)
427
464
  return null;
428
- // No indentation for control items - they maintain their original styling
429
- return (_jsx(React.Fragment, { children: renderMetadataItem(metadataItem) }, `control-${layoutItem.layoutItemID}`));
465
+ // Indent control items based on depth so they align under groups
466
+ const controlIndentPx = depth > 0 ? depth * 10 : 15;
467
+ return (_jsx("div", { style: { paddingLeft: `${controlIndentPx}px` }, children: renderMetadataItem(metadataItem) }, `control-${layoutItem.layoutItemID}`));
430
468
  }
431
469
  // Check if this is a SeparatorItem (horizontal line)
432
470
  else if (layoutItem.type === LayoutItemTypes.SeparatorItem) {
@@ -434,27 +472,39 @@ const TMMetadataValues = ({ showCheckBoxes = ShowCheckBoxesMode.Never, checkPerm
434
472
  }
435
473
  return null;
436
474
  };
437
- return (_jsx("div", { style: { width: '100%' }, children: rootItems.map(item => renderLayoutItem(item, 0)) }));
438
- };
439
- const renderForm = () => {
475
+ return (_jsx("div", { style: { width: '100%' }, children: (() => {
476
+ const visited = new Set();
477
+ return rootItems.map(item => renderLayoutItem(item, 0, visited));
478
+ })() }));
479
+ }, [layout, metadataValues, showCheckBoxes, showNullValueCheckBoxes, isReadOnly, dynDataListsToBeRefreshed, validationItems, selectedMID, isOpenDistinctValues, openChooserBySingleClick, metadataValuesOrig]);
480
+ const renderForm = useMemo(() => {
481
+ // Se currentDTD non è ancora stato caricato, non renderizzare nulla
482
+ if (!currentDTD) {
483
+ return null;
484
+ }
485
+ // Se tutti i metadata sono di sistema, renderizziamo tutti in sola lettura senza layout
486
+ const allSystem = metadataValues.length > 0 && metadataValues.every(item => item.md?.isSystem === 1);
487
+ if (allSystem) {
488
+ return metadataValues.map((item) => renderMetadataItem(item, true));
489
+ }
440
490
  if (currentDTD?.templateTID === TemplateTIDs.WF_WIApprView && !isReadOnly) {
441
- return layoutWorkItem();
491
+ return layoutWorkItem;
442
492
  }
443
493
  switch (currentDTD?.id) {
444
- case SystemTIDs.Drafts: return !isReadOnly ? layoutDraft() : metadataValues.map((item) => renderMetadataItem(item));
494
+ case SystemTIDs.Drafts: return layoutDraft;
445
495
  // case SystemTIDs.Chronology: break;
446
- // case SystemTIDs.DSAttachs: break;
496
+ case SystemTIDs.DSAttachs: return layoutDsAttachs;
447
497
  default:
448
498
  // Se è presente un layout personalizzato, usalo, altrimenti usa il rendering standard
449
499
  if (layout && layout.items && layout.items.length > 0) {
450
- return layoutCustom();
500
+ return layoutCustom;
451
501
  }
452
502
  return metadataValues.map((item) => renderMetadataItem(item));
453
503
  }
454
- };
504
+ }, [currentDTD, metadataValues, layout, isReadOnly, showCheckBoxes, showNullValueCheckBoxes, dynDataListsToBeRefreshed, validationItems, selectedMID, isOpenDistinctValues, openChooserBySingleClick, metadataValuesOrig]);
455
505
  return (_jsx(StyledMetadataValuesContainer, { children: !TID ?
456
506
  _jsx(TMNothingToShow, { text: `${SDKUI_Localizator.NoDcmtSelected}.`, secondText: `${SDKUI_Localizator.MetadataSystem} - ${SDKUI_Localizator.NotAvailable}`, icon: _jsx(IconDcmtTypeSys, { fontSize: 96 }) }) :
457
- _jsx(_Fragment, { children: renderForm() }) }));
507
+ _jsx(_Fragment, { children: renderForm }) }));
458
508
  };
459
509
  export default TMMetadataValues;
460
510
  //#region Styled Components