@topconsultnpm/sdkui-react 6.19.0-dev2.42 → 6.19.0-dev2.44
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/components/base/TMCustomButton.d.ts +1 -1
- package/lib/components/base/TMCustomButton.js +28 -26
- package/lib/components/base/TMFileManagerDataGridView.js +1 -1
- package/lib/components/base/TMModal.d.ts +2 -0
- package/lib/components/base/TMModal.js +13 -2
- package/lib/components/features/assistant/ToppyDraggableHelpCenter.d.ts +28 -0
- package/lib/components/features/assistant/ToppyDraggableHelpCenter.js +283 -0
- package/lib/components/features/assistant/ToppyHelpCenter.js +1 -1
- package/lib/components/features/assistant/ToppySpeechBubble.d.ts +8 -0
- package/lib/components/features/assistant/ToppySpeechBubble.js +53 -0
- package/lib/components/features/blog/TMBlogCommentForm.d.ts +2 -0
- package/lib/components/features/blog/TMBlogCommentForm.js +18 -6
- package/lib/components/features/documents/TMDcmtForm.js +2 -2
- package/lib/components/features/search/TMSearchResult.js +2 -2
- package/lib/components/forms/TMSaveForm.js +2 -2
- package/lib/components/index.d.ts +2 -1
- package/lib/components/index.js +2 -1
- package/lib/helper/SDKUI_Localizator.d.ts +3 -0
- package/lib/helper/SDKUI_Localizator.js +30 -0
- package/lib/helper/dcmtsHelper.d.ts +2 -2
- package/lib/helper/dcmtsHelper.js +53 -21
- package/lib/ts/types.d.ts +1 -0
- package/package.json +1 -1
|
@@ -7,5 +7,5 @@ type TMCustomButtonProps = {
|
|
|
7
7
|
selectedItems?: Array<any>;
|
|
8
8
|
onClose?: () => void;
|
|
9
9
|
};
|
|
10
|
-
declare const TMCustomButton: (props: TMCustomButtonProps) => import("react/jsx-runtime").JSX.Element
|
|
10
|
+
declare const TMCustomButton: (props: TMCustomButtonProps) => false | import("react/jsx-runtime").JSX.Element;
|
|
11
11
|
export default TMCustomButton;
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { SDK_Globals } from '@topconsultnpm/sdk-ts';
|
|
3
2
|
import { useEffect, useRef, useState, useMemo } from 'react';
|
|
4
3
|
import TMModal from './TMModal';
|
|
5
4
|
import styled from 'styled-components';
|
|
6
|
-
import { TMLayoutWaitingContainer } from '../..';
|
|
7
|
-
import {
|
|
5
|
+
import { SDKUI_Localizator, TMLayoutWaitingContainer } from '../..';
|
|
6
|
+
import { getButtonAttributes, getSelectedItem } from '../../helper/dcmtsHelper';
|
|
8
7
|
const IframeContainer = styled.div `
|
|
9
8
|
display: flex;
|
|
10
9
|
height: 100%;
|
|
@@ -19,15 +18,14 @@ const TMCustomButton = (props) => {
|
|
|
19
18
|
const { button, isModal = true, formData, selectedItems, onClose } = props;
|
|
20
19
|
const { appName: scriptUrl, arguments: args } = button;
|
|
21
20
|
const iframeRef = useRef(null);
|
|
22
|
-
const attributes = useMemo(() =>
|
|
23
|
-
const selectedItemsProcessed = useMemo(() => processSelectedItems(args, formData, selectedItems), [args, formData, selectedItems]);
|
|
21
|
+
const attributes = useMemo(() => getButtonAttributes(args, formData, selectedItems), [args, formData, selectedItems]);
|
|
24
22
|
const RunOnce = button.mode === "RunOnce";
|
|
25
23
|
const [loading, setLoading] = useState(true);
|
|
26
24
|
const [error, setError] = useState(false);
|
|
27
|
-
const selectedItemsCount = selectedItems?.length ||
|
|
25
|
+
const selectedItemsCount = selectedItems?.length || 0;
|
|
28
26
|
// Stati per il wait panel
|
|
29
|
-
const [showWaitPanel, setShowWaitPanel] = useState(
|
|
30
|
-
const [waitPanelText, setWaitPanelText] = useState(
|
|
27
|
+
const [showWaitPanel, setShowWaitPanel] = useState(selectedItemsCount > 0 && !RunOnce);
|
|
28
|
+
const [waitPanelText, setWaitPanelText] = useState(SDKUI_Localizator.CustomButtonActions.replaceParams(1, selectedItemsCount));
|
|
31
29
|
const [waitPanelValue, setWaitPanelValue] = useState(0);
|
|
32
30
|
const [waitPanelMaxValue, setWaitPanelMaxValue] = useState(selectedItemsCount);
|
|
33
31
|
const [abortController, setAbortController] = useState(undefined);
|
|
@@ -48,23 +46,21 @@ const TMCustomButton = (props) => {
|
|
|
48
46
|
setError(true);
|
|
49
47
|
};
|
|
50
48
|
const executeSequentially = async (controller) => {
|
|
51
|
-
if (!
|
|
49
|
+
if (!selectedItems)
|
|
52
50
|
return;
|
|
53
|
-
const
|
|
54
|
-
for (const [index, item] of processedItems.entries()) {
|
|
51
|
+
for (const [index, item] of selectedItems.entries()) {
|
|
55
52
|
if (controller.signal.aborted)
|
|
56
53
|
break;
|
|
57
|
-
setWaitPanelText(
|
|
54
|
+
setWaitPanelText(SDKUI_Localizator.CustomButtonActions.replaceParams(index + 1, selectedItemsCount));
|
|
58
55
|
setWaitPanelValue(index);
|
|
59
56
|
// Attendi che l'iframe sia pronto e invia il messaggio
|
|
60
57
|
await new Promise((resolve) => {
|
|
61
58
|
const checkIframe = setInterval(() => {
|
|
62
59
|
if (iframeRef.current?.contentWindow) {
|
|
63
60
|
clearInterval(checkIframe);
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}, targetOrigin);
|
|
61
|
+
//devo convertire item in formData
|
|
62
|
+
const processedItem = getSelectedItem(args, formData, item);
|
|
63
|
+
postMessageIframe(processedItem);
|
|
68
64
|
// Attendi prima di passare al prossimo
|
|
69
65
|
setTimeout(() => {
|
|
70
66
|
setWaitPanelValue(index + 1);
|
|
@@ -77,12 +73,17 @@ const TMCustomButton = (props) => {
|
|
|
77
73
|
setShowWaitPanel(false);
|
|
78
74
|
onClose?.();
|
|
79
75
|
};
|
|
76
|
+
const postMessageIframe = (data) => {
|
|
77
|
+
console.log("TMCustomButton - postMessageIframe - data:", data);
|
|
78
|
+
if (iframeRef.current?.contentWindow) {
|
|
79
|
+
iframeRef.current.contentWindow.postMessage({ "options": data }, targetOrigin);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
80
82
|
useEffect(() => {
|
|
81
83
|
if (loading || error)
|
|
82
84
|
return;
|
|
83
85
|
//if(error) clearTimeout(timeoutIframe);
|
|
84
|
-
|
|
85
|
-
if (!RunOnce) {
|
|
86
|
+
if (!RunOnce && selectedItemsCount > 0) {
|
|
86
87
|
// esegui per ogni item selezionato
|
|
87
88
|
const controller = new AbortController();
|
|
88
89
|
controller.signal.addEventListener('abort', () => {
|
|
@@ -93,13 +94,15 @@ const TMCustomButton = (props) => {
|
|
|
93
94
|
setWaitPanelMaxValue(selectedItemsCount);
|
|
94
95
|
executeSequentially(controller);
|
|
95
96
|
}
|
|
96
|
-
else
|
|
97
|
+
else {
|
|
97
98
|
// Modalità RunOnce: invia dati all'iframe quando è caricato
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
99
|
+
postMessageIframe(attributes);
|
|
100
|
+
if (!RunOnce) {
|
|
101
|
+
//chiudo l'iframe dopo 2 secondi
|
|
102
|
+
setTimeout(() => {
|
|
103
|
+
onClose?.();
|
|
104
|
+
}, 2000);
|
|
105
|
+
}
|
|
103
106
|
//clearTimeout(timeoutIframe);
|
|
104
107
|
}
|
|
105
108
|
}, [loading, error, RunOnce]);
|
|
@@ -110,7 +113,6 @@ const TMCustomButton = (props) => {
|
|
|
110
113
|
}
|
|
111
114
|
}, []);
|
|
112
115
|
const iframeContent = (_jsxs(IframeContainer, { style: !RunOnce ? { visibility: 'hidden' } : {}, 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 })] }));
|
|
113
|
-
return isModal && RunOnce ? (_jsx(TMModal, { title: button.title, width: '60%', height: '70%', resizable: true, onClose: onClose, children: iframeContent })) : !RunOnce &&
|
|
114
|
-
: null;
|
|
116
|
+
return isModal && RunOnce ? (_jsx(TMModal, { title: button.title, width: '60%', height: '70%', resizable: true, expandable: true, onClose: onClose, children: iframeContent })) : !RunOnce && (_jsxs(_Fragment, { children: [_jsx(TMLayoutWaitingContainer, { showWaitPanel: showWaitPanel, waitPanelTitle: SDKUI_Localizator.CustomButtonAction, showWaitPanelPrimary: true, waitPanelTextPrimary: waitPanelText, waitPanelValuePrimary: waitPanelValue, waitPanelMaxValuePrimary: waitPanelMaxValue, showWaitPanelSecondary: false, isCancelable: true, abortController: abortController, children: undefined }), iframeContent] }));
|
|
115
117
|
};
|
|
116
118
|
export default TMCustomButton;
|
|
@@ -96,6 +96,6 @@ const TMFileManagerDataGridView = (props) => {
|
|
|
96
96
|
{ dataField: "creationTime", caption: SDKUI_Localizator.CreationTime, dataType: 'datetime', format: 'dd/MM/yyyy HH:mm', cellRender: cellDatetimeRender },
|
|
97
97
|
];
|
|
98
98
|
}, [searchText]);
|
|
99
|
-
return _jsx(TMDataGrid, { dataSource: items ?? [], dataColumns: dataColumns, focusedRowKey: focusedRowKey, selectedRowKeys: selectedRowKeys, onFocusedRowChanged: onFocusedRowChanged, onSelectionChanged: onSelectionChanged, onCellDblClick: onCellDblClick, onContextMenuPreparing: onContextMenuPreparing, showSearchPanel: false, noDataText: SDKUI_Localizator.FolderIsEmpty });
|
|
99
|
+
return _jsx(TMDataGrid, { dataSource: items ?? [], dataColumns: dataColumns, focusedRowKey: focusedRowKey, selectedRowKeys: selectedRowKeys, onFocusedRowChanged: onFocusedRowChanged, onSelectionChanged: onSelectionChanged, onCellDblClick: onCellDblClick, onContextMenuPreparing: onContextMenuPreparing, showSearchPanel: false, noDataText: SDKUI_Localizator.FolderIsEmpty }, items.length);
|
|
100
100
|
};
|
|
101
101
|
export default TMFileManagerDataGridView;
|
|
@@ -8,9 +8,11 @@ interface ITMModal {
|
|
|
8
8
|
fontSize?: string;
|
|
9
9
|
isModal?: boolean;
|
|
10
10
|
resizable?: boolean;
|
|
11
|
+
expandable?: boolean;
|
|
11
12
|
onClose?: () => void;
|
|
12
13
|
hidePopup?: boolean;
|
|
13
14
|
askClosingConfirm?: boolean;
|
|
15
|
+
showCloseButton?: boolean;
|
|
14
16
|
}
|
|
15
17
|
declare const TMModal: React.FC<ITMModal>;
|
|
16
18
|
export default TMModal;
|
|
@@ -4,6 +4,7 @@ import { Popup } from 'devextreme-react';
|
|
|
4
4
|
import styled from 'styled-components';
|
|
5
5
|
import TMLayoutContainer, { TMCard, TMLayoutItem } from './TMLayout';
|
|
6
6
|
import { FontSize, TMColors } from '../../utils/theme';
|
|
7
|
+
import { IconWindowMaximize, IconWindowMinimize, svgToString } from '../../helper';
|
|
7
8
|
const StyledModal = styled.div `
|
|
8
9
|
width: ${props => props.$width};
|
|
9
10
|
height: ${props => props.$height};
|
|
@@ -38,11 +39,12 @@ const StyledModalContext = styled.div `
|
|
|
38
39
|
overflow: auto;
|
|
39
40
|
height: 100%;
|
|
40
41
|
`;
|
|
41
|
-
const TMModal = ({ resizable = true, isModal = true, title = '', toolbar, onClose, children, width = '100%', height = '100%', fontSize = FontSize.defaultFontSize, hidePopup = true, askClosingConfirm = false }) => {
|
|
42
|
+
const TMModal = ({ resizable = true, expandable = false, isModal = true, title = '', toolbar, onClose, children, width = '100%', height = '100%', fontSize = FontSize.defaultFontSize, hidePopup = true, askClosingConfirm = false, showCloseButton = true }) => {
|
|
42
43
|
const [initialWidth, setInitialWidth] = useState(width);
|
|
43
44
|
const [initialHeight, setInitialHeight] = useState(height);
|
|
44
45
|
const [showPopup, setShowPopup] = useState(false);
|
|
45
46
|
const [isResizing, setIsResizing] = useState(false);
|
|
47
|
+
const [isFullScreen, setIsFullScreen] = useState(false);
|
|
46
48
|
useEffect(() => {
|
|
47
49
|
setShowPopup(isModal);
|
|
48
50
|
}, [isModal]);
|
|
@@ -65,6 +67,15 @@ const TMModal = ({ resizable = true, isModal = true, title = '', toolbar, onClos
|
|
|
65
67
|
setShowPopup(false);
|
|
66
68
|
onClose && onClose();
|
|
67
69
|
};
|
|
68
|
-
return (_jsx(_Fragment, { children: isModal ? (_jsx(Popup, { showCloseButton:
|
|
70
|
+
return (_jsx(_Fragment, { children: isModal ? (_jsx(Popup, { showCloseButton: showCloseButton, animation: undefined, maxHeight: '95%', maxWidth: '95%', dragEnabled: !isResizing, resizeEnabled: resizable, width: expandable && isFullScreen ? '95%' : initialWidth, height: expandable && isFullScreen ? '95%' : initialHeight, title: title, visible: showPopup, onResizeStart: handleResizeStart, onResizeEnd: handleResizeEnd, onHiding: onHiding, toolbarItems: expandable ? [
|
|
71
|
+
{
|
|
72
|
+
widget: 'dxButton',
|
|
73
|
+
location: 'after',
|
|
74
|
+
options: {
|
|
75
|
+
icon: isFullScreen ? svgToString(_jsx(IconWindowMinimize, {})) : svgToString(_jsx(IconWindowMaximize, {})),
|
|
76
|
+
onClick: () => setIsFullScreen(!isFullScreen)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
] : undefined, children: _jsxs(TMLayoutContainer, { children: [toolbar && (_jsx(TMLayoutItem, { height: "40px", children: _jsx(StyledModalToolbar, { children: toolbar }) })), _jsx(TMLayoutItem, { height: toolbar ? 'calc(100% - 50px)' : 'calc(100% - 10px)', children: _jsx(TMCard, { showBorder: false, padding: false, scrollY: true, children: children }) })] }) })) : (_jsxs(StyledModal, { "$isModal": isModal, className: "temp-modal", "$fontSize": fontSize, "$width": initialWidth, "$height": initialHeight, children: [toolbar ? _jsx(StyledModalToolbar, { children: toolbar }) : _jsx(_Fragment, {}), _jsx(StyledModalContext, { children: children })] })) }));
|
|
69
80
|
};
|
|
70
81
|
export default TMModal;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { DeviceType } from '../../base/TMDeviceProvider';
|
|
3
|
+
/**
|
|
4
|
+
* Proprietà del componente ToppyDraggableHelpCenter
|
|
5
|
+
*/
|
|
6
|
+
interface ToppyDraggableHelpCenterProps {
|
|
7
|
+
/** Contenuto da visualizzare nella speech bubble */
|
|
8
|
+
content?: React.ReactNode;
|
|
9
|
+
/** Tipo di dispositivo per adattare il comportamento */
|
|
10
|
+
deviceType?: DeviceType;
|
|
11
|
+
/** Allineamento iniziale del componente (destra o sinistra) */
|
|
12
|
+
align?: 'right' | 'left';
|
|
13
|
+
/** Stato iniziale collassato/espanso */
|
|
14
|
+
initialIsCollapsed?: boolean;
|
|
15
|
+
/** Callback chiamato quando si clicca sull'immagine di Toppy */
|
|
16
|
+
onToppyImageClick?: () => void;
|
|
17
|
+
/** Visibilità del componente */
|
|
18
|
+
isVisible?: boolean;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Componente ToppyDraggableHelpCenter
|
|
22
|
+
*
|
|
23
|
+
* Renderizza un assistente virtuale (Toppy) draggable che può mostrare contenuti
|
|
24
|
+
* in una speech bubble. Il componente può essere trascinato all'interno del suo
|
|
25
|
+
* contenitore e può essere collassato/espanso con un doppio click.
|
|
26
|
+
*/
|
|
27
|
+
declare const ToppyDraggableHelpCenter: React.FC<ToppyDraggableHelpCenterProps>;
|
|
28
|
+
export default ToppyDraggableHelpCenter;
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useRef, useState, useEffect } from 'react';
|
|
3
|
+
import styled from 'styled-components';
|
|
4
|
+
import Toppy from '../../../assets/Toppy-generico.png';
|
|
5
|
+
import { DeviceType } from '../../base/TMDeviceProvider';
|
|
6
|
+
import ToppySpeechBubble from './ToppySpeechBubble';
|
|
7
|
+
/**
|
|
8
|
+
* Styled component per il pulsante di Toppy
|
|
9
|
+
* Gestisce il posizionamento, le dimensioni e le animazioni
|
|
10
|
+
*/
|
|
11
|
+
const ToppyButton = styled.button.attrs((props) => ({
|
|
12
|
+
style: {
|
|
13
|
+
// Applica left/top come stili inline per evitare la generazione di troppe classi CSS
|
|
14
|
+
...(props.$x !== undefined && props.$y !== undefined
|
|
15
|
+
? {
|
|
16
|
+
left: `${props.$x}px`,
|
|
17
|
+
top: `${props.$y}px`,
|
|
18
|
+
bottom: 'auto',
|
|
19
|
+
right: 'auto',
|
|
20
|
+
}
|
|
21
|
+
: {}),
|
|
22
|
+
// Cursore applicato come stile inline
|
|
23
|
+
cursor: props.$isDragging ? 'grabbing' : 'grab',
|
|
24
|
+
},
|
|
25
|
+
})) `
|
|
26
|
+
/* Visibilità controllata dalla prop */
|
|
27
|
+
display: ${(props) => (props.$isVisible ? 'flex' : 'none')};
|
|
28
|
+
position: absolute;
|
|
29
|
+
|
|
30
|
+
/* Posizionamento di default quando non è draggato (x e y non sono definiti) */
|
|
31
|
+
${(props) => props.$x === undefined || props.$y === undefined
|
|
32
|
+
? `
|
|
33
|
+
bottom: -20px;
|
|
34
|
+
${props.$align === 'left' ? 'left: 10px;' : 'right: 10px;'};
|
|
35
|
+
`
|
|
36
|
+
: ''}
|
|
37
|
+
|
|
38
|
+
/* Z-index alto per assicurare che sia sempre in primo piano */
|
|
39
|
+
z-index: 2147483647;
|
|
40
|
+
background-color: transparent;
|
|
41
|
+
border: none;
|
|
42
|
+
|
|
43
|
+
/* Dimensioni dinamiche in base allo stato collassato
|
|
44
|
+
Usa min() per adattarsi su schermi piccoli */
|
|
45
|
+
width: ${(props) => (props.$isCollapsed ? 'min(60px, 100%)' : '120px')};
|
|
46
|
+
height: ${(props) => (props.$isCollapsed ? 'min(70px, 100%)' : '140px')};
|
|
47
|
+
max-width: 100%;
|
|
48
|
+
max-height: 100%;
|
|
49
|
+
|
|
50
|
+
user-select: none;
|
|
51
|
+
|
|
52
|
+
img {
|
|
53
|
+
/* Dimensioni dell'immagine in base allo stato collassato */
|
|
54
|
+
width: ${(props) => (props.$isCollapsed ? '60px' : '120px')};
|
|
55
|
+
height: ${(props) => (props.$isCollapsed ? '60px' : '140px')};
|
|
56
|
+
pointer-events: none;
|
|
57
|
+
border-radius: 50%; /* Rende l'immagine circolare */
|
|
58
|
+
/* Rotazione leggera in base all'allineamento */
|
|
59
|
+
transform: ${(props) => props.$align === 'left' ? 'rotate(20deg)' : 'rotate(-20deg)'};
|
|
60
|
+
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
|
61
|
+
|
|
62
|
+
/* Animazione di pulsazione quando è collassato per attirare l'attenzione */
|
|
63
|
+
${(props) => props.$isCollapsed &&
|
|
64
|
+
`
|
|
65
|
+
animation: toppyPulse 1.5s infinite;
|
|
66
|
+
`}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* Keyframes per l'animazione di pulsazione */
|
|
70
|
+
@keyframes toppyPulse {
|
|
71
|
+
0% {
|
|
72
|
+
/* Dimensione normale all'inizio */
|
|
73
|
+
transform: scale(1) rotate(${(props) => props.$align === 'left' ? '20deg' : '-20deg'});
|
|
74
|
+
box-shadow: 0 0 0 rgba(0, 113, 188, 0.5);
|
|
75
|
+
}
|
|
76
|
+
50% {
|
|
77
|
+
/* Ingrandimento e glow al 50% dell'animazione */
|
|
78
|
+
transform: scale(1.1) rotate(${(props) => props.$align === 'left' ? '20deg' : '-20deg'});
|
|
79
|
+
box-shadow: 0 0 15px 5px rgba(0, 113, 188, 0.6);
|
|
80
|
+
}
|
|
81
|
+
100% {
|
|
82
|
+
/* Ritorno alla dimensione normale */
|
|
83
|
+
transform: scale(1) rotate(${(props) => props.$align === 'left' ? '20deg' : '-20deg'});
|
|
84
|
+
box-shadow: 0 0 0 rgba(0, 113, 188, 0.5);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
`;
|
|
88
|
+
/**
|
|
89
|
+
* Componente ToppyDraggableHelpCenter
|
|
90
|
+
*
|
|
91
|
+
* Renderizza un assistente virtuale (Toppy) draggable che può mostrare contenuti
|
|
92
|
+
* in una speech bubble. Il componente può essere trascinato all'interno del suo
|
|
93
|
+
* contenitore e può essere collassato/espanso con un doppio click.
|
|
94
|
+
*/
|
|
95
|
+
const ToppyDraggableHelpCenter = ({ content, deviceType, align = 'right', onToppyImageClick, initialIsCollapsed, isVisible = true, }) => {
|
|
96
|
+
// Ref per il pulsante principale
|
|
97
|
+
const buttonRef = useRef(null);
|
|
98
|
+
// Stato per controllare se il componente è collassato o espanso
|
|
99
|
+
const [isCollapsed, setIsCollapsed] = useState(initialIsCollapsed ?? false);
|
|
100
|
+
// Stato per tracciare se il componente è in fase di trascinamento
|
|
101
|
+
const [isDragging, setIsDragging] = useState(false);
|
|
102
|
+
// Posizione corrente del componente (null = posizione di default)
|
|
103
|
+
const [position, setPosition] = useState(null);
|
|
104
|
+
// Offset del mouse rispetto all'angolo superiore sinistro del componente durante il drag
|
|
105
|
+
const dragOffset = useRef({ x: 0, y: 0 });
|
|
106
|
+
// Ref e stato per tracciare le dimensioni della speech bubble
|
|
107
|
+
const bubbleRef = useRef(null);
|
|
108
|
+
const [bubbleSize, setBubbleSize] = useState({ width: 0, height: 0 });
|
|
109
|
+
/**
|
|
110
|
+
* Effect per aggiornare le dimensioni della bubble quando cambia il contenuto
|
|
111
|
+
* o lo stato di collassamento. Necessario per calcolare correttamente i limiti
|
|
112
|
+
* di trascinamento.
|
|
113
|
+
*/
|
|
114
|
+
useEffect(() => {
|
|
115
|
+
if (bubbleRef.current) {
|
|
116
|
+
const rect = bubbleRef.current.getBoundingClientRect();
|
|
117
|
+
setBubbleSize({ width: rect.width, height: rect.height });
|
|
118
|
+
}
|
|
119
|
+
}, [content, isCollapsed]);
|
|
120
|
+
/**
|
|
121
|
+
* Effect per resettare la posizione quando il parent o la finestra vengono ridimensionati.
|
|
122
|
+
* Questo previene che il componente finisca fuori dai bordi dopo un resize.
|
|
123
|
+
*/
|
|
124
|
+
useEffect(() => {
|
|
125
|
+
if (!buttonRef.current)
|
|
126
|
+
return;
|
|
127
|
+
const parent = buttonRef.current.offsetParent;
|
|
128
|
+
if (!parent)
|
|
129
|
+
return;
|
|
130
|
+
// Funzione per verificare e aggiustare la posizione dopo un resize
|
|
131
|
+
const handleResize = () => {
|
|
132
|
+
if (!buttonRef.current || !position)
|
|
133
|
+
return;
|
|
134
|
+
const parentRect = parent.getBoundingClientRect();
|
|
135
|
+
const rect = buttonRef.current.getBoundingClientRect();
|
|
136
|
+
// Spazio extra occupato dalla bubble quando non è collassato
|
|
137
|
+
const extraHeight = !isCollapsed ? bubbleSize.height : 0;
|
|
138
|
+
const extraWidth = !isCollapsed ? bubbleSize.width : 0;
|
|
139
|
+
// Calcola i nuovi limiti
|
|
140
|
+
let minX = 0;
|
|
141
|
+
let maxX = parentRect.width - rect.width;
|
|
142
|
+
if (!isCollapsed) {
|
|
143
|
+
if (align === 'right') {
|
|
144
|
+
minX = Math.max(0, extraWidth - rect.width);
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
maxX = Math.min(maxX, parentRect.width - extraWidth);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
const maxY = parentRect.height - rect.height;
|
|
151
|
+
// Verifica se la posizione corrente è fuori dai limiti
|
|
152
|
+
const isOutOfBounds = position.x < minX ||
|
|
153
|
+
position.x > maxX ||
|
|
154
|
+
position.y < extraHeight ||
|
|
155
|
+
position.y > maxY;
|
|
156
|
+
// Se è fuori dai limiti, resetta alla posizione default
|
|
157
|
+
if (isOutOfBounds) {
|
|
158
|
+
setPosition(null);
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
// Observer per monitorare i cambiamenti di dimensione del parent
|
|
162
|
+
const resizeObserver = new ResizeObserver(handleResize);
|
|
163
|
+
resizeObserver.observe(parent);
|
|
164
|
+
// Listener per il resize della finestra
|
|
165
|
+
window.addEventListener('resize', handleResize);
|
|
166
|
+
return () => {
|
|
167
|
+
resizeObserver.disconnect();
|
|
168
|
+
window.removeEventListener('resize', handleResize);
|
|
169
|
+
};
|
|
170
|
+
}, [position, isCollapsed, bubbleSize, align]);
|
|
171
|
+
/**
|
|
172
|
+
* Effect per impostare automaticamente lo stato collassato su dispositivi mobile
|
|
173
|
+
* se non è stato specificato un valore iniziale.
|
|
174
|
+
*/
|
|
175
|
+
useEffect(() => {
|
|
176
|
+
if (initialIsCollapsed === undefined && deviceType === DeviceType.MOBILE) {
|
|
177
|
+
setIsCollapsed(true);
|
|
178
|
+
}
|
|
179
|
+
}, [deviceType, initialIsCollapsed]);
|
|
180
|
+
/**
|
|
181
|
+
* Gestisce il toggle dello stato collassato/espanso
|
|
182
|
+
* Chiamato dal doppio click sul componente
|
|
183
|
+
*/
|
|
184
|
+
const toggleCollapse = (e) => {
|
|
185
|
+
e.stopPropagation();
|
|
186
|
+
setIsCollapsed(!isCollapsed);
|
|
187
|
+
onToppyImageClick?.();
|
|
188
|
+
};
|
|
189
|
+
/**
|
|
190
|
+
* Gestisce l'inizio del trascinamento
|
|
191
|
+
* Salva la posizione relativa del mouse rispetto al componente (offset)
|
|
192
|
+
* per mantenere il punto di presa durante il trascinamento
|
|
193
|
+
*/
|
|
194
|
+
const handleMouseDown = (e) => {
|
|
195
|
+
if (!buttonRef.current)
|
|
196
|
+
return;
|
|
197
|
+
const rect = buttonRef.current.getBoundingClientRect();
|
|
198
|
+
const parentRect = buttonRef.current.offsetParent?.getBoundingClientRect();
|
|
199
|
+
if (!parentRect)
|
|
200
|
+
return;
|
|
201
|
+
// Calcola l'offset tra il punto di click e l'angolo superiore sinistro del componente
|
|
202
|
+
dragOffset.current = {
|
|
203
|
+
x: e.clientX - rect.left,
|
|
204
|
+
y: e.clientY - rect.top,
|
|
205
|
+
};
|
|
206
|
+
setIsDragging(true);
|
|
207
|
+
e.preventDefault();
|
|
208
|
+
};
|
|
209
|
+
/**
|
|
210
|
+
* Gestisce il movimento durante il trascinamento
|
|
211
|
+
* Calcola la nuova posizione rispettando i limiti del parent container
|
|
212
|
+
* e tenendo conto delle dimensioni della speech bubble quando espansa
|
|
213
|
+
*/
|
|
214
|
+
const handleMouseMove = (e) => {
|
|
215
|
+
if (!isDragging || !buttonRef.current)
|
|
216
|
+
return;
|
|
217
|
+
const parentRect = buttonRef.current.offsetParent?.getBoundingClientRect();
|
|
218
|
+
if (!parentRect)
|
|
219
|
+
return;
|
|
220
|
+
const rect = buttonRef.current.getBoundingClientRect();
|
|
221
|
+
// Spazio extra occupato dalla bubble quando non è collassato
|
|
222
|
+
const extraHeight = !isCollapsed ? bubbleSize.height : 0;
|
|
223
|
+
const extraWidth = !isCollapsed ? bubbleSize.width : 0;
|
|
224
|
+
// Calcola i limiti orizzontali considerando la bubble
|
|
225
|
+
let minX = 0;
|
|
226
|
+
let maxX = parentRect.width - rect.width;
|
|
227
|
+
if (!isCollapsed) {
|
|
228
|
+
if (align === 'right') {
|
|
229
|
+
// La bubble si estende verso sinistra: aumenta il limite minimo
|
|
230
|
+
// per evitare che la bubble esca dal bordo sinistro
|
|
231
|
+
minX = Math.max(0, extraWidth - rect.width);
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
// La bubble si estende verso destra: riduce il limite massimo
|
|
235
|
+
// per evitare che la bubble esca dal bordo destro
|
|
236
|
+
maxX = Math.min(maxX, parentRect.width - extraWidth);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
// Calcola la nuova posizione X rispettando i limiti
|
|
240
|
+
const newX = Math.max(minX, Math.min(e.clientX - parentRect.left - dragOffset.current.x, maxX));
|
|
241
|
+
// Calcola la nuova posizione Y rispettando i limiti
|
|
242
|
+
// Il limite superiore tiene conto dell'altezza della bubble
|
|
243
|
+
const newY = Math.max(extraHeight, Math.min(e.clientY - parentRect.top - dragOffset.current.y, parentRect.height - rect.height));
|
|
244
|
+
setPosition({ x: newX, y: newY });
|
|
245
|
+
};
|
|
246
|
+
/**
|
|
247
|
+
* Gestisce il rilascio del mouse alla fine del trascinamento
|
|
248
|
+
* Se il movimento è stato minimo (< 5px), viene interpretato come un click
|
|
249
|
+
* e viene chiamato il callback onToppyImageClick
|
|
250
|
+
*/
|
|
251
|
+
const handleMouseUp = (e) => {
|
|
252
|
+
if (isDragging) {
|
|
253
|
+
setIsDragging(false);
|
|
254
|
+
const rect = buttonRef.current?.getBoundingClientRect();
|
|
255
|
+
if (rect) {
|
|
256
|
+
// Calcola la distanza totale del movimento usando il teorema di Pitagora
|
|
257
|
+
const moveDistance = Math.hypot(e.clientX - (rect.left + dragOffset.current.x), e.clientY - (rect.top + dragOffset.current.y));
|
|
258
|
+
// Se il movimento è stato minimo, trattalo come un click
|
|
259
|
+
if (moveDistance < 5 && onToppyImageClick) {
|
|
260
|
+
onToppyImageClick();
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
/**
|
|
266
|
+
* Effect per gestire gli event listener durante il trascinamento
|
|
267
|
+
* Gli eventi sono registrati sul document per catturare il movimento
|
|
268
|
+
* anche quando il mouse esce dal componente
|
|
269
|
+
*/
|
|
270
|
+
useEffect(() => {
|
|
271
|
+
if (isDragging) {
|
|
272
|
+
document.addEventListener('mousemove', handleMouseMove);
|
|
273
|
+
document.addEventListener('mouseup', handleMouseUp);
|
|
274
|
+
return () => {
|
|
275
|
+
document.removeEventListener('mousemove', handleMouseMove);
|
|
276
|
+
document.removeEventListener('mouseup', handleMouseUp);
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
return undefined;
|
|
280
|
+
}, [isDragging]);
|
|
281
|
+
return (_jsxs(ToppyButton, { ref: buttonRef, "$align": align, "$isDragging": isDragging, "$x": position?.x, "$y": position?.y, "$isVisible": isVisible, "$isCollapsed": isCollapsed, onMouseDown: handleMouseDown, onContextMenu: (e) => e.preventDefault(), onDoubleClick: toggleCollapse, children: [(content && !isCollapsed) && (_jsx(ToppySpeechBubble, { ref: bubbleRef, align: align, children: content })), _jsx("img", { src: Toppy, alt: "Toppy Help", draggable: false })] }));
|
|
282
|
+
};
|
|
283
|
+
export default ToppyDraggableHelpCenter;
|
|
@@ -162,7 +162,7 @@ const ToppyHelpCenter = ({ content, deviceType, usePortal = true, align = 'right
|
|
|
162
162
|
onToppyImageClick?.();
|
|
163
163
|
};
|
|
164
164
|
const isMobile = deviceType === DeviceType.MOBILE;
|
|
165
|
-
const toppyComponent = (_jsxs(ToppyContainer, { "$isMobile": isMobile, "$isCollapsed": isCollapsed, "$fixed": usePortal, "$align": align, children: [_jsx(ToppyImage, { "$isMobile": isMobile, "$isCollapsed": isCollapsed, "$align": align, onClick: toggleCollapse, src: Toppy, alt: "Toppy" }), _jsx(ToppyContent, { "$isCollapsed": isCollapsed, "$isMobile": isMobile, "$align": align, children: content })] }));
|
|
165
|
+
const toppyComponent = (_jsxs(ToppyContainer, { "$isMobile": isMobile, "$isCollapsed": isCollapsed, "$fixed": usePortal, "$align": align, onContextMenu: (e) => e.preventDefault(), children: [_jsx(ToppyImage, { "$isMobile": isMobile, "$isCollapsed": isCollapsed, "$align": align, onClick: toggleCollapse, src: Toppy, alt: "Toppy" }), _jsx(ToppyContent, { "$isCollapsed": isCollapsed, "$isMobile": isMobile, "$align": align, children: content })] }));
|
|
166
166
|
if (usePortal) {
|
|
167
167
|
if (!portalContainer)
|
|
168
168
|
return null;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
interface ToppySpeechBubbleProps {
|
|
3
|
+
align?: 'left' | 'right';
|
|
4
|
+
children: React.ReactNode;
|
|
5
|
+
className?: string;
|
|
6
|
+
}
|
|
7
|
+
declare const ToppySpeechBubble: React.ForwardRefExoticComponent<ToppySpeechBubbleProps & React.RefAttributes<HTMLDivElement>>;
|
|
8
|
+
export default ToppySpeechBubble;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef } from 'react';
|
|
3
|
+
import styled from 'styled-components';
|
|
4
|
+
// Styled component
|
|
5
|
+
const Bubble = styled.div `
|
|
6
|
+
position: absolute;
|
|
7
|
+
bottom: 145px;
|
|
8
|
+
${({ $align }) => ($align === 'left' ? 'left: 0px;' : 'right: 0px;')}
|
|
9
|
+
width: max-content;
|
|
10
|
+
padding: 10px;
|
|
11
|
+
background: linear-gradient(180deg, rgba(0, 113, 188, 0.45) 0%,rgba(27, 20, 100, 0.65) 100%);
|
|
12
|
+
border-radius: 18px;
|
|
13
|
+
box-shadow: 0 10px 28px rgba(0, 0, 0, 0.18);
|
|
14
|
+
font-size: 14px;
|
|
15
|
+
line-height: 1.4;
|
|
16
|
+
color: #333;
|
|
17
|
+
z-index: 10000;
|
|
18
|
+
|
|
19
|
+
&::after {
|
|
20
|
+
transform: ${({ $align }) => ($align === 'left' ? 'skewX(15deg)' : 'skewX(-15deg)')};
|
|
21
|
+
content: "";
|
|
22
|
+
position: absolute;
|
|
23
|
+
top: 100%;
|
|
24
|
+
${({ $align }) => ($align === 'left' ? 'left: 20px;' : 'right: 15px;')}
|
|
25
|
+
border-width: 32px 32px 0 0;
|
|
26
|
+
border-style: solid;
|
|
27
|
+
border-color: #FFFFFF transparent;
|
|
28
|
+
display: block;
|
|
29
|
+
width: 0;
|
|
30
|
+
height: 0;
|
|
31
|
+
z-index: 1;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
&::before {
|
|
35
|
+
transform: ${({ $align }) => ($align === 'left' ? 'skewX(15deg)' : 'skewX(-15deg)')};
|
|
36
|
+
content: "";
|
|
37
|
+
position: absolute;
|
|
38
|
+
top: 100%;
|
|
39
|
+
${({ $align }) => ($align === 'left' ? 'left: 20px;' : 'right: 15px;')}
|
|
40
|
+
border-width: 32px 32px 0 0;
|
|
41
|
+
border-style: solid;
|
|
42
|
+
border-color: rgba(27, 20, 100, 0.65) transparent;
|
|
43
|
+
display: block;
|
|
44
|
+
width: 0;
|
|
45
|
+
height: 0;
|
|
46
|
+
z-index: 2;
|
|
47
|
+
}
|
|
48
|
+
`;
|
|
49
|
+
// Componente con forwardRef
|
|
50
|
+
const ToppySpeechBubble = forwardRef(({ align = 'right', children, className }, ref) => {
|
|
51
|
+
return (_jsx(Bubble, { ref: ref, "$align": align, className: className, children: children }));
|
|
52
|
+
});
|
|
53
|
+
export default ToppySpeechBubble;
|
|
@@ -6,12 +6,14 @@ interface TMBlogCommentFormProps {
|
|
|
6
6
|
participants: Array<UserDescriptor>;
|
|
7
7
|
onClose: () => void;
|
|
8
8
|
showAttachmentsSection?: boolean;
|
|
9
|
+
removeAndEditAttachment?: boolean;
|
|
9
10
|
selectedAttachments?: Array<FileItem>;
|
|
10
11
|
selectedAttachmentDid?: Array<number>;
|
|
11
12
|
allFileItems?: FileItem;
|
|
12
13
|
allArchivedDocumentsFileItems?: Array<FileItem>;
|
|
13
14
|
onFilterCreated?: (predicate: (post: BlogPost) => boolean) => void;
|
|
14
15
|
refreshCallback?: () => Promise<void>;
|
|
16
|
+
isCommentRequired?: boolean;
|
|
15
17
|
}
|
|
16
18
|
declare const TMBlogCommentForm: (props: TMBlogCommentFormProps) => import("react/jsx-runtime").JSX.Element;
|
|
17
19
|
export default TMBlogCommentForm;
|
|
@@ -29,7 +29,7 @@ const getNonDirectoryFiles = (items, exclude) => {
|
|
|
29
29
|
};
|
|
30
30
|
const TMBlogCommentForm = (props) => {
|
|
31
31
|
const maxLength = 1000;
|
|
32
|
-
const { participants, selectedAttachments, selectedAttachmentDid, allFileItems, allArchivedDocumentsFileItems = [], onClose, context, showAttachmentsSection = true, onFilterCreated, refreshCallback } = props;
|
|
32
|
+
const { participants, selectedAttachments, selectedAttachmentDid, allFileItems, allArchivedDocumentsFileItems = [], onClose, context, showAttachmentsSection = true, removeAndEditAttachment = true, onFilterCreated, refreshCallback, isCommentRequired = false } = props;
|
|
33
33
|
// Initialize state with combined array
|
|
34
34
|
const [dataSource, setDataSource] = useState(() => [...getNonDirectoryFiles(allFileItems?.items || [], []), ...allArchivedDocumentsFileItems]);
|
|
35
35
|
const [isEditorEnabled, setIsEditorEnabled] = useState(true);
|
|
@@ -216,8 +216,20 @@ const TMBlogCommentForm = (props) => {
|
|
|
216
216
|
// Update the state with selected draft items
|
|
217
217
|
setCurrentDraftAttachments(selectedDraftItems);
|
|
218
218
|
};
|
|
219
|
-
return _jsx(TMSaveForm, { id: 1, title: SDKUI_Localizator.AddNewComment, showTitleFormMode: false, showErrorCount: false, customSaveButton: _jsx("i", { className: 'dx-icon-send' }), customTooltipSaveButton: SDKUI_Localizator.Send, showUndoButton: false, hasNavigation: false, skipIsModifiedCheck: true, isModal: true, width: calcResponsiveSizes(deviceType, '800px', '800px', '95%'), height: '550px', formMode: FormModes.Create, validationItems: validationItems, exception: exception, isModified: calcIsModified(formData, formDataOrig), onSaveAsync: onSaveAsync, onClose: onCloseCallback, customToolbarElements: _jsx("div", { style: { display: 'flex', gap: '2px' }, children: _jsx(TMButton, { btnStyle: "toolbar", icon: isEditorEnabled ? _jsx("i", { className: 'dx-icon-font' }) : _jsx("i", { className: 'dx-icon-background' }), caption: isEditorEnabled ? SDKUI_Localizator.HideFormattingOptions : SDKUI_Localizator.ShowFormattingOptions, onClick: toggleEditorMode }) }), children: _jsxs("div", { style: { width: "100%", height: "100%" }, children: [_jsxs("div", { style: { width: "100%", height: "100%" }, children: [
|
|
220
|
-
|
|
219
|
+
return _jsx(TMSaveForm, { id: 1, title: SDKUI_Localizator.AddNewComment, showTitleFormMode: false, showErrorCount: false, customSaveButton: _jsx("i", { className: 'dx-icon-send' }), customTooltipSaveButton: SDKUI_Localizator.Send, showUndoButton: false, hasNavigation: false, skipIsModifiedCheck: true, isModal: true, width: calcResponsiveSizes(deviceType, '800px', '800px', '95%'), height: '550px', formMode: FormModes.Create, validationItems: validationItems, exception: exception, isModified: calcIsModified(formData, formDataOrig), onSaveAsync: onSaveAsync, onClose: onCloseCallback, showCloseButton: isCommentRequired ? false : true, customToolbarElements: _jsx("div", { style: { display: 'flex', gap: '2px' }, children: _jsx(TMButton, { btnStyle: "toolbar", icon: isEditorEnabled ? _jsx("i", { className: 'dx-icon-font' }) : _jsx("i", { className: 'dx-icon-background' }), caption: isEditorEnabled ? SDKUI_Localizator.HideFormattingOptions : SDKUI_Localizator.ShowFormattingOptions, onClick: toggleEditorMode }) }), children: _jsxs("div", { style: { width: "100%", height: "100%" }, children: [_jsxs("div", { style: { width: "100%", height: "100%" }, children: [_jsxs("div", { style: { width: "100%", height: showAttachmentsSection ? `calc(100% - 60px)` : '100%' }, children: [isCommentRequired && _jsx("div", { style: {
|
|
220
|
+
padding: '10px',
|
|
221
|
+
width: '100%',
|
|
222
|
+
height: '30px',
|
|
223
|
+
backgroundColor: '#e8f5e9',
|
|
224
|
+
border: '1px solid #81c784',
|
|
225
|
+
borderRadius: '4px',
|
|
226
|
+
fontSize: '13px',
|
|
227
|
+
marginBottom: '10px',
|
|
228
|
+
color: '#2e7d32',
|
|
229
|
+
display: 'flex',
|
|
230
|
+
alignItems: 'center',
|
|
231
|
+
}, children: SDKUI_Localizator.InsertCommentToCompleteOperation }), _jsx("div", { style: { width: "100%", height: isCommentRequired ? `calc(100% - 40px)` : '100%' }, children: _jsx(TMHtmlEditor, { width: '100%', height: '100%', isEditorEnabled: isEditorEnabled, validationItems: validationItems, onValueChanged: onValueChanged, mentionsConfig: mentionsConfig, autoFocus: true, maxLength: maxLength }) })] }), showAttachmentsSection && _jsxs("div", { style: { display: 'flex', alignItems: 'center', height: '60px', marginTop: '10px' }, children: [_jsx("div", { style: {
|
|
232
|
+
width: `calc(100% - ${removeAndEditAttachment ? 60 : 0}px)`,
|
|
221
233
|
overflowX: 'auto',
|
|
222
234
|
whiteSpace: 'nowrap',
|
|
223
235
|
height: '50px',
|
|
@@ -231,7 +243,7 @@ const TMBlogCommentForm = (props) => {
|
|
|
231
243
|
const tooltipContent = (_jsxs("div", { style: { textAlign: 'left' }, children: [_jsxs("div", { children: [_jsxs("span", { style: { fontWeight: 'bold' }, children: [SDKUI_Localizator.Name, ":"] }), " ", draft.name ?? '-'] }), _jsxs("div", { children: [_jsxs("span", { style: { fontWeight: 'bold' }, children: [SDKUI_Localizator.Author, ":"] }), " ", draft.updaterName ?? '-'] }), _jsx("hr", {}), _jsxs("div", { children: [_jsxs("span", { style: { fontWeight: 'bold' }, children: [SDKUI_Localizator.Version, ":"] }), " ", draft.version] }), _jsxs("div", { children: [_jsxs("span", { style: { fontWeight: 'bold' }, children: [SDKUI_Localizator.Size, ":"] }), " ", formatBytes(draft.size ?? 0)] }), _jsx("hr", {}), _jsxs("div", { children: [_jsxs("span", { style: { fontWeight: 'bold' }, children: [SDKUI_Localizator.CreationTime, ":"] }), " ", Globalization.getDateTimeDisplayValue(draft.creationTime)] }), _jsxs("div", { children: [_jsxs("span", { style: { fontWeight: 'bold' }, children: [SDKUI_Localizator.LastUpdateTime, ":"] }), " ", Globalization.getDateTimeDisplayValue(draft.lastUpdateTime)] })] }));
|
|
232
244
|
return _jsxs("div", { style: {
|
|
233
245
|
display: 'inline-flex',
|
|
234
|
-
alignItems: 'center',
|
|
246
|
+
alignItems: 'center',
|
|
235
247
|
padding: '4px 8px',
|
|
236
248
|
margin: '4px',
|
|
237
249
|
border: '1px solid #ddd',
|
|
@@ -258,7 +270,7 @@ const TMBlogCommentForm = (props) => {
|
|
|
258
270
|
fontWeight: 'bold',
|
|
259
271
|
marginRight: '8px',
|
|
260
272
|
boxShadow: '1px 1px 2px #00000020',
|
|
261
|
-
}, children: _jsx(TMTooltip, { content: SDKUI_Localizator.Version, children: draft.version }) })), _jsx(TMTooltip, { content: SDKUI_Localizator.RemoveAttachment, children: _jsx("span", { onClick: () => removeAttachment(draft), style: {
|
|
273
|
+
}, children: _jsx(TMTooltip, { content: SDKUI_Localizator.Version, children: draft.version }) })), removeAndEditAttachment && _jsx(TMTooltip, { content: SDKUI_Localizator.RemoveAttachment, children: _jsx("span", { onClick: () => removeAttachment(draft), style: {
|
|
262
274
|
display: 'inline-flex',
|
|
263
275
|
width: '20px',
|
|
264
276
|
height: '20px',
|
|
@@ -270,7 +282,7 @@ const TMBlogCommentForm = (props) => {
|
|
|
270
282
|
cursor: 'pointer',
|
|
271
283
|
boxShadow: '1px 1px 2px #00000020',
|
|
272
284
|
}, children: _jsx("span", { style: { fontSize: '15px' }, children: "\u00D7" }) }) })] }, draft.did);
|
|
273
|
-
})) : (_jsx("div", { style: { color: '#999', width: '100%', textAlign: 'center' }, children: SDKUI_Localizator.NoAttachments })) }), _jsx(TMTooltip, { content: SDKUI_Localizator.Attachments + ": " + currentDraftAttachments.length, children: _jsxs("div", { style: { position: 'relative', display: 'inline-block' }, children: [_jsx("i", { className: "dx-icon-attach", style: {
|
|
285
|
+
})) : (_jsx("div", { style: { color: '#999', width: '100%', textAlign: 'center' }, children: SDKUI_Localizator.NoAttachments })) }), removeAndEditAttachment && _jsx(TMTooltip, { content: SDKUI_Localizator.Attachments + ": " + currentDraftAttachments.length, children: _jsxs("div", { style: { position: 'relative', display: 'inline-block' }, children: [_jsx("i", { className: "dx-icon-attach", style: {
|
|
274
286
|
width: '50px',
|
|
275
287
|
height: '50px',
|
|
276
288
|
marginLeft: '10px',
|
|
@@ -34,7 +34,6 @@ import { useTMPanelManagerContext } from '../../layout/panelManager/TMPanelManag
|
|
|
34
34
|
import TMPanelManagerContainer from '../../layout/panelManager/TMPanelManagerContainer';
|
|
35
35
|
import { TMPanelManagerWithPersistenceProvider } from '../../layout/panelManager/TMPanelManagerWithPersistenceProvider';
|
|
36
36
|
import { useWorkflowApprove } from '../../../hooks/useWorkflowApprove';
|
|
37
|
-
import ToppyHelpCenter from '../assistant/ToppyHelpCenter';
|
|
38
37
|
import TMBlogCommentForm from '../blog/TMBlogCommentForm';
|
|
39
38
|
import WFDiagram from '../workflow/diagram/WFDiagram';
|
|
40
39
|
import TMTooltip from '../../base/TMTooltip';
|
|
@@ -42,6 +41,7 @@ import TMDcmtTasks from './TMDcmtTasks';
|
|
|
42
41
|
import TMToppyMessage from '../../../helper/TMToppyMessage';
|
|
43
42
|
import { getTaskAssignedToMe } from '../tasks/TMTasksUtils';
|
|
44
43
|
import TMCustomButton from '../../base/TMCustomButton';
|
|
44
|
+
import ToppyDraggableHelpCenter from '../assistant/ToppyDraggableHelpCenter';
|
|
45
45
|
let abortControllerLocal = new AbortController();
|
|
46
46
|
//#endregion
|
|
47
47
|
const TMDcmtForm = ({ allTasks = [], getAllTasks, deleteTaskByIdsCallback, addTaskCallback, editTaskCallback, handleNavigateToWGs, handleNavigateToDossiers, 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, onTaskCreateRequest, inputFile = null, taskFormDialogComponent, taskMoreInfo, connectorFileSave = undefined, inputMids = [], onOpenS4TViewerRequest, s4TViewerDialogComponent, enableDragDropOverlay = false, passToSearch, isSharedDcmt = false, sharedSourceTID, sharedSourceDID, allowButtonsRefs = false, onReferenceClick, }) => {
|
|
@@ -1353,7 +1353,7 @@ const TMDcmtForm = ({ allTasks = [], getAllTasks, deleteTaskByIdsCallback, addTa
|
|
|
1353
1353
|
isEditable: true,
|
|
1354
1354
|
value: FormulaHelper.addFormulaTag(newFormula.expression)
|
|
1355
1355
|
}));
|
|
1356
|
-
} }), 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(
|
|
1356
|
+
} }), 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(ToppyDraggableHelpCenter, { deviceType: deviceType, content: _jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: '10px' }, children: [showToppyForApprove && (workItems.length === 1 ?
|
|
1357
1357
|
_jsx(WorkFlowOperationButtons, { deviceType: deviceType, onApprove: () => setShowApprovePopup(true), onSignApprove: handleSignApprove, onReject: () => setShowRejectPopup(true), onReAssign: () => setShowReAssignPopup(true), onMoreInfo: () => setShowMoreInfoPopup(true), dtd: fromDTD })
|
|
1358
1358
|
:
|
|
1359
1359
|
_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: () => {
|
|
@@ -31,7 +31,6 @@ import TMDcmtBlog from '../documents/TMDcmtBlog';
|
|
|
31
31
|
import TMDcmtIcon from '../documents/TMDcmtIcon';
|
|
32
32
|
import { TMPanelManagerProvider, useTMPanelManagerContext } from '../../layout/panelManager/TMPanelManagerContext';
|
|
33
33
|
import TMPanelManagerContainer from '../../layout/panelManager/TMPanelManagerContainer';
|
|
34
|
-
import ToppyHelpCenter from '../assistant/ToppyHelpCenter';
|
|
35
34
|
import TMAccordion from '../../base/TMAccordion';
|
|
36
35
|
import TMDataGridExportForm from '../../base/TMDataGridExportForm';
|
|
37
36
|
import TMSearchResultFloatingActionButton from './TMSearchResultFloatingActionButton';
|
|
@@ -43,6 +42,7 @@ import TMSearch from './TMSearch';
|
|
|
43
42
|
import TMArchive from '../archive/TMArchive';
|
|
44
43
|
import { TMResultManager } from '../../forms/TMResultDialog';
|
|
45
44
|
import TMCustomButton from '../../base/TMCustomButton';
|
|
45
|
+
import ToppyDraggableHelpCenter from '../assistant/ToppyDraggableHelpCenter';
|
|
46
46
|
//#region Helper Methods
|
|
47
47
|
export const getSearchResultCountersSingleCategory = (searchResults) => {
|
|
48
48
|
// let totDcmtTypes = searchResults.length;
|
|
@@ -585,7 +585,7 @@ const TMSearchResult = ({ allTasks = [], getAllTasks, deleteTaskByIdsCallback, a
|
|
|
585
585
|
setIsModifiedBatchUpdate(false);
|
|
586
586
|
await refreshSelectionDataRowsAsync();
|
|
587
587
|
}, onStatusChanged: (isModified) => { setIsModifiedBatchUpdate(isModified); } }), (showToppyForApprove && !showApprovePopup && !showRejectPopup && !showReAssignPopup && !showMoreInfoPopup && !openS4TViewer && !showTodoDcmtForm) &&
|
|
588
|
-
_jsx(
|
|
588
|
+
_jsx(ToppyDraggableHelpCenter, { deviceType: deviceType, content: _jsx("div", { style: { display: 'flex', flexDirection: 'column', gap: '10px' }, children: _jsx(WorkFlowOperationButtons, { deviceType: deviceType, onApprove: () => {
|
|
589
589
|
setShowApprovePopup(true);
|
|
590
590
|
}, onSignApprove: () => {
|
|
591
591
|
handleSignApprove();
|
|
@@ -11,7 +11,7 @@ import { TMColors } from '../../utils/theme';
|
|
|
11
11
|
import TMValidationItemsList from '../grids/TMValidationItemsList';
|
|
12
12
|
import TMModal from '../base/TMModal';
|
|
13
13
|
import { DeviceType, useDeviceType } from '../base/TMDeviceProvider';
|
|
14
|
-
const TMSaveForm = ({ id, formMode = FormModes.Update, showToolbar = true, skipIsModifiedCheck = false, title, children, isModal, exception, customToolbarElements, hasNavigation, showSaveButton = true, customSaveButton, customTooltipSaveButton, showBackButton, showWarningsCount = true, showErrorCount = true, showUndoButton = true, onClose, onSaveAsync, onNext, onPrev, canNext, canPrev, isModified, onShowList, validationItems = [], onUndo, onCancel, width, height, askClosingConfirm = false, showTitleFormMode = true }) => {
|
|
14
|
+
const TMSaveForm = ({ id, formMode = FormModes.Update, showToolbar = true, skipIsModifiedCheck = false, title, children, isModal, exception, customToolbarElements, hasNavigation, showSaveButton = true, customSaveButton, customTooltipSaveButton, showBackButton, showWarningsCount = true, showErrorCount = true, showUndoButton = true, onClose, onSaveAsync, onNext, onPrev, canNext, canPrev, isModified, onShowList, validationItems = [], onUndo, onCancel, width, height, askClosingConfirm = false, showTitleFormMode = true, showCloseButton = true }) => {
|
|
15
15
|
const [showList, setShowList] = useState(true);
|
|
16
16
|
const [showErrorGrid, setShowErrorGrid] = useState(false);
|
|
17
17
|
const deviceType = useDeviceType();
|
|
@@ -110,7 +110,7 @@ const TMSaveForm = ({ id, formMode = FormModes.Update, showToolbar = true, skipI
|
|
|
110
110
|
_jsx("div", { style: { width: '100%', height: '100%', marginTop: '50px', display: 'flex', alignItems: 'center', justifyContent: 'center', flexDirection: 'column', color: getColor('error') }, children: 'Si è verificato un errore' })
|
|
111
111
|
: _jsx(_Fragment, { children: children }) }), showErrorGrid && validationItems.length > 0 ? _jsx(TMCard, { scrollY: true, padding: false, showBorder: false, children: _jsx(TMValidationItemsList, { validationItems: validationItems }) }) : _jsx(_Fragment, {})] }) }), (isModal && onClose) && _jsx("div", { id: "TMSaveFormShowConfirmForClose-" + id })] }));
|
|
112
112
|
};
|
|
113
|
-
return (_jsx(_Fragment, { children: (isModal && onClose) ? _jsx(_Fragment, { children: _jsx(TMModal, { title: `${title}${showTitleFormMode ? ` - ${LocalizeFormModes(formMode)}` : ''}`, onClose: doClose, width: width ?? '100%', height: height ?? '100%', hidePopup: false, askClosingConfirm: askClosingConfirm, children: _jsx("div", { style: { width: "100%", height: "100%", display: 'block' }, children: renderSaveForm() }) }) })
|
|
113
|
+
return (_jsx(_Fragment, { children: (isModal && onClose) ? _jsx(_Fragment, { children: _jsx(TMModal, { title: `${title}${showTitleFormMode ? ` - ${LocalizeFormModes(formMode)}` : ''}`, onClose: doClose, width: width ?? '100%', height: height ?? '100%', hidePopup: false, askClosingConfirm: askClosingConfirm, showCloseButton: showCloseButton, children: _jsx("div", { style: { width: "100%", height: "100%", display: 'block' }, children: renderSaveForm() }) }) })
|
|
114
114
|
: renderSaveForm() }));
|
|
115
115
|
};
|
|
116
116
|
export default TMSaveForm;
|
|
@@ -59,7 +59,8 @@ export { default as TMBlogAttachments } from './grids/TMBlogAttachments';
|
|
|
59
59
|
export { default as TMBlogCommentForm } from './features/blog/TMBlogCommentForm';
|
|
60
60
|
export * from './query/TMQueryEditor';
|
|
61
61
|
export * from './query/TMQuerySummary';
|
|
62
|
-
export
|
|
62
|
+
export { default as ToppyHelpCenter } from './features/assistant/ToppyHelpCenter';
|
|
63
|
+
export { default as ToppyDraggableHelpCenter } from './features/assistant/ToppyDraggableHelpCenter';
|
|
63
64
|
export * from './features/documents/TMDcmtForm';
|
|
64
65
|
export * from './features/documents/TMDcmtIcon';
|
|
65
66
|
export * from './features/documents/TMDcmtPreview';
|
package/lib/components/index.js
CHANGED
|
@@ -66,7 +66,8 @@ export { default as TMBlogCommentForm } from './features/blog/TMBlogCommentForm'
|
|
|
66
66
|
export * from './query/TMQueryEditor';
|
|
67
67
|
export * from './query/TMQuerySummary';
|
|
68
68
|
//assistant
|
|
69
|
-
export
|
|
69
|
+
export { default as ToppyHelpCenter } from './features/assistant/ToppyHelpCenter';
|
|
70
|
+
export { default as ToppyDraggableHelpCenter } from './features/assistant/ToppyDraggableHelpCenter';
|
|
70
71
|
//documents
|
|
71
72
|
export * from './features/documents/TMDcmtForm';
|
|
72
73
|
export * from './features/documents/TMDcmtIcon';
|
|
@@ -67,6 +67,8 @@ export declare class SDKUI_Localizator {
|
|
|
67
67
|
static get AutoAdjust(): "Automatische Anpassung" | "Auto Adjust" | "Ajuste automático" | "Ajustement automatique" | "Regolazione automatica";
|
|
68
68
|
static get Author(): string;
|
|
69
69
|
static get CustomButtons(): string;
|
|
70
|
+
static get CustomButtonAction(): string;
|
|
71
|
+
static get CustomButtonActions(): string;
|
|
70
72
|
static get Back(): "Zurück" | "Back" | "Atrás" | "Dos" | "Voltar" | "Indietro";
|
|
71
73
|
static get BatchUpdate(): "Mehrfachbearbeitung" | "Multiple modification" | "Modificación múltiple" | "Modifie multiple" | "Editar múltipla" | "Modifica multipla";
|
|
72
74
|
static get BlogCase(): "Anschlagbrett" | "Blog board" | "Tablón" | "Tableau d'affichage" | "Bakeca" | "Bacheca";
|
|
@@ -306,6 +308,7 @@ export declare class SDKUI_Localizator {
|
|
|
306
308
|
static get IndexingInformation(): string;
|
|
307
309
|
static get IndexOrReindex(): string;
|
|
308
310
|
static get InProgress(): "Laufende" | "In Progress" | "En curso" | "En cours" | "Em andamento" | "In corso";
|
|
311
|
+
static get InsertCommentToCompleteOperation(): string;
|
|
309
312
|
static get InsertYourEmail(): "Geben Sie Ihre E-Mail ein" | "Insert your Email" | "Inserta tu Email" | "Insérez votre e-mail" | "Insira seu e-mail" | "Inserisci la tua email";
|
|
310
313
|
static get InsertOTP(): "Geben Sie den OTP-Code ein" | "Insert OTP code" | "Insertar código OTP" | "Insérer le code OTP" | "Insira o código OTP" | "Inserisci il codice OTP";
|
|
311
314
|
static get Interrupt(): "Unterbrechen" | "Interrupt" | "interrumpir" | "Interrompre" | "Interromper" | "Interrompere";
|
|
@@ -625,6 +625,26 @@ export class SDKUI_Localizator {
|
|
|
625
625
|
default: return "Bottoni personalizzati";
|
|
626
626
|
}
|
|
627
627
|
}
|
|
628
|
+
static get CustomButtonAction() {
|
|
629
|
+
switch (this._cultureID) {
|
|
630
|
+
case CultureIDs.De_DE: return "Vorgang läuft";
|
|
631
|
+
case CultureIDs.En_US: return "Operation in progress";
|
|
632
|
+
case CultureIDs.Es_ES: return "Operación en curso";
|
|
633
|
+
case CultureIDs.Fr_FR: return "Opération en cours";
|
|
634
|
+
case CultureIDs.Pt_PT: return "Operação em curso";
|
|
635
|
+
default: return "Operazione in corso";
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
static get CustomButtonActions() {
|
|
639
|
+
switch (this._cultureID) {
|
|
640
|
+
case CultureIDs.De_DE: return "Aktion {{0}} von {{1}} wird ausgeführt";
|
|
641
|
+
case CultureIDs.En_US: return "Executing action {{0}} of {{1}}";
|
|
642
|
+
case CultureIDs.Es_ES: return "Ejecutando acción {{0}} de {{1}}";
|
|
643
|
+
case CultureIDs.Fr_FR: return "Exécution de l'action {{0}} sur {{1}}";
|
|
644
|
+
case CultureIDs.Pt_PT: return "Executando ação {{0}} de {{1}}";
|
|
645
|
+
default: return "Esecuzione azione {{0}} di {{1}}";
|
|
646
|
+
}
|
|
647
|
+
}
|
|
628
648
|
static get Back() {
|
|
629
649
|
switch (this._cultureID) {
|
|
630
650
|
case CultureIDs.De_DE: return "Zurück";
|
|
@@ -2989,6 +3009,16 @@ export class SDKUI_Localizator {
|
|
|
2989
3009
|
default: return "In corso";
|
|
2990
3010
|
}
|
|
2991
3011
|
}
|
|
3012
|
+
static get InsertCommentToCompleteOperation() {
|
|
3013
|
+
switch (this._cultureID) {
|
|
3014
|
+
case CultureIDs.De_DE: return "Geben Sie einen Kommentar ein, um den Vorgang abzuschließen.";
|
|
3015
|
+
case CultureIDs.En_US: return "Enter a comment to complete the operation.";
|
|
3016
|
+
case CultureIDs.Es_ES: return "Introduzca un comentario para completar la operación.";
|
|
3017
|
+
case CultureIDs.Fr_FR: return "Saisissez un commentaire pour terminer l’opération.";
|
|
3018
|
+
case CultureIDs.Pt_PT: return "Introduza um comentário para concluir a operação.";
|
|
3019
|
+
default: return "Inserisci un commento per completare l’operazione.";
|
|
3020
|
+
}
|
|
3021
|
+
}
|
|
2992
3022
|
static get InsertYourEmail() {
|
|
2993
3023
|
switch (this._cultureID) {
|
|
2994
3024
|
case CultureIDs.De_DE: return "Geben Sie Ihre E-Mail ein";
|
|
@@ -4,5 +4,5 @@ export declare const hasDetailRelations: (mTID: number | undefined) => Promise<b
|
|
|
4
4
|
/** Check if dcmtType (mTID) has configured Master or Many-to-Many relations */
|
|
5
5
|
export declare const hasMasterRelations: (mTID: number | undefined) => Promise<boolean>;
|
|
6
6
|
export declare const isXMLFileExt: (fileExt: string | undefined) => boolean;
|
|
7
|
-
export declare const
|
|
8
|
-
export declare const
|
|
7
|
+
export declare const getButtonAttributes: (args: string | undefined, formData: MetadataValueDescriptorEx[] | undefined, selectedItems: Array<any> | undefined) => Record<string, any> | undefined;
|
|
8
|
+
export declare const getSelectedItem: (args: string | undefined, formData: MetadataValueDescriptorEx[] | undefined, item: any) => Record<string, any> | undefined;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { RelationCacheService, RelationTypes } from "@topconsultnpm/sdk-ts";
|
|
1
|
+
import { RelationCacheService, RelationTypes, SDK_Globals } from "@topconsultnpm/sdk-ts";
|
|
2
2
|
/** Check if dcmtType (mTID) has configured Detail or Many-to-Many relations */
|
|
3
3
|
export const hasDetailRelations = async (mTID) => {
|
|
4
4
|
let allRelations = await RelationCacheService.GetAllAsync();
|
|
@@ -24,27 +24,59 @@ export const isXMLFileExt = (fileExt) => {
|
|
|
24
24
|
}
|
|
25
25
|
};
|
|
26
26
|
/*utility functions for TMCustomButton*/
|
|
27
|
-
export const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
27
|
+
export const getButtonAttributes = (args, formData, selectedItems) => args && formData ? formDataMap(formData, args, selectedItems) : undefined;
|
|
28
|
+
const getSelectedItems = (selectedItems) => selectedItems && selectedItems.map(item => item["DID"]) || [];
|
|
29
|
+
export const getSelectedItem = (args, formData, item) => {
|
|
30
|
+
//converto item in formData
|
|
31
|
+
const formDataConverted = [];
|
|
32
|
+
for (const key in item) {
|
|
33
|
+
const md = formData?.find(md => `${item["TID"]}_${md.mid}` === key);
|
|
34
|
+
if (md) { // aggiungo solo i metadati
|
|
35
|
+
const name = md.md?.name || "";
|
|
36
|
+
formDataConverted.push({ md: { name: name }, value: item[key] });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return args && formDataConverted ?
|
|
40
|
+
formDataMap(formDataConverted, args, []) : undefined;
|
|
35
41
|
};
|
|
36
|
-
const formDataMap = (data, args,
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
const formDataMap = (data, args, selectedItems) => {
|
|
43
|
+
const session = SDK_Globals.tmSession;
|
|
44
|
+
const sessionDescr = session?.SessionDescr;
|
|
45
|
+
const result = {};
|
|
46
|
+
// Helper per estrarre il valore di un campo
|
|
47
|
+
const getParamValue = (fieldName) => {
|
|
48
|
+
const md = data.find(md => md.md?.name === fieldName);
|
|
49
|
+
switch (fieldName) {
|
|
50
|
+
case 'SelectedDIDs': return getSelectedItems(selectedItems);
|
|
51
|
+
case 'AuthenticationMode': return sessionDescr?.authenticationMode ?? null;
|
|
52
|
+
case 'ArchiveID': return sessionDescr?.archiveID ?? null;
|
|
53
|
+
case 'CultureID': return sessionDescr?.cultureID ?? null;
|
|
54
|
+
case 'Domain': return sessionDescr?.domain ?? null;
|
|
55
|
+
case 'UserID': return sessionDescr?.userID ?? null;
|
|
56
|
+
case 'UserName': return sessionDescr?.userName ?? null;
|
|
57
|
+
case 'Session': return session ?? null;
|
|
58
|
+
default: return md?.value;
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
// Regex per catturare: chiave=[...] o chiave={...} o {@campo}
|
|
62
|
+
const keyValueRegex = /(\w+)=\[([^\]]+)\]|(\w+)=\{@?([^}]+)\}|\{@([^}]+)\}/g;
|
|
63
|
+
for (const match of args.matchAll(keyValueRegex)) {
|
|
64
|
+
if (match[1]) {
|
|
65
|
+
// Formato: chiave=[{@campo} testo {@campo} ...]
|
|
66
|
+
const key = match[1];
|
|
67
|
+
const content = match[2].replace(/\{@([^}]+)\}/g, (_, fieldName) => data.find(md => md.md?.name === fieldName)?.value ?? '');
|
|
68
|
+
result[key] = content;
|
|
44
69
|
}
|
|
45
|
-
else {
|
|
46
|
-
//
|
|
47
|
-
|
|
70
|
+
else if (match[3]) {
|
|
71
|
+
// Formato: chiave={@campo} o chiave={valore}
|
|
72
|
+
const key = match[3];
|
|
73
|
+
const value = match[4];
|
|
74
|
+
result[key] = value.startsWith('@') ? getParamValue(value.substring(1)) : value;
|
|
48
75
|
}
|
|
49
|
-
|
|
76
|
+
else if (match[5]) {
|
|
77
|
+
// Formato: {@campo}
|
|
78
|
+
result[match[5]] = getParamValue(match[5]);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return result;
|
|
50
82
|
};
|
package/lib/ts/types.d.ts
CHANGED
|
@@ -173,6 +173,7 @@ export interface ITMSaveFormBaseProps {
|
|
|
173
173
|
customTooltipSaveButton?: string;
|
|
174
174
|
showBackButton?: boolean;
|
|
175
175
|
showUndoButton?: boolean;
|
|
176
|
+
showCloseButton?: boolean;
|
|
176
177
|
showWarningsCount?: boolean;
|
|
177
178
|
showErrorCount?: boolean;
|
|
178
179
|
hasNavigation?: boolean;
|