@topconsultnpm/sdkui-react 6.20.0-dev2.52 → 6.20.0-dev2.54
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/NewComponents/ContextMenu/styles.d.ts +3 -1
- package/lib/components/NewComponents/ContextMenu/styles.js +7 -5
- package/lib/components/features/documents/TMDcmtForm.js +1 -1
- package/lib/components/features/documents/TMDcmtTasks.d.ts +3 -1
- package/lib/components/features/documents/TMDcmtTasks.js +2 -2
- package/lib/components/features/documents/TMRelationViewer.js +27 -23
- package/lib/components/features/tasks/TMTaskForm.js +19 -13
- package/lib/components/features/tasks/TMTaskFormUtils.js +4 -4
- package/lib/components/features/workflow/diagram/ConnectionComponent.d.ts +1 -0
- package/lib/components/features/workflow/diagram/ConnectionComponent.js +6 -2
- package/lib/components/features/workflow/diagram/WFDiagram.js +74 -4
- package/lib/helper/SDKUI_Localizator.d.ts +1 -0
- package/lib/helper/SDKUI_Localizator.js +10 -0
- package/package.json +1 -1
|
@@ -14,7 +14,9 @@ export declare const MenuItem: import("styled-components/dist/types").IStyledCom
|
|
|
14
14
|
$beginGroup?: boolean;
|
|
15
15
|
}>> & string;
|
|
16
16
|
export declare const MenuItemContent: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>> & string;
|
|
17
|
-
export declare const IconWrapper: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>,
|
|
17
|
+
export declare const IconWrapper: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("styled-components").FastOmit<import("styled-components/dist/types").Substitute<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "ref"> & {
|
|
18
|
+
ref?: ((instance: HTMLSpanElement | null) => void | import("react").DO_NOT_USE_OR_YOU_WILL_BE_FIRED_CALLBACK_REF_RETURN_VALUES[keyof import("react").DO_NOT_USE_OR_YOU_WILL_BE_FIRED_CALLBACK_REF_RETURN_VALUES]) | import("react").RefObject<HTMLSpanElement> | null | undefined;
|
|
19
|
+
}>, never>, never>> & string;
|
|
18
20
|
export declare const MenuItemName: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, never>> & string;
|
|
19
21
|
export declare const RightIconButton: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("styled-components").FastOmit<import("styled-components/dist/types").Substitute<import("react").DetailedHTMLProps<import("react").ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, Omit<import("react").DetailedHTMLProps<import("react").ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, "ref"> & {
|
|
20
22
|
ref?: ((instance: HTMLButtonElement | null) => void | import("react").DO_NOT_USE_OR_YOU_WILL_BE_FIRED_CALLBACK_REF_RETURN_VALUES[keyof import("react").DO_NOT_USE_OR_YOU_WILL_BE_FIRED_CALLBACK_REF_RETURN_VALUES]) | import("react").RefObject<HTMLButtonElement> | null | undefined;
|
|
@@ -69,7 +69,7 @@ export const MenuContainer = styled.div `
|
|
|
69
69
|
`}
|
|
70
70
|
|
|
71
71
|
/* Reset color inheritance from parent with !important to override panel header styles */
|
|
72
|
-
& *:not(svg):not(.right-icon-btn):not(.right-icon-btn *) {
|
|
72
|
+
& *:not(svg):not(.right-icon-btn):not(.right-icon-btn *):not(.icon-wrapper):not(.icon-wrapper *) {
|
|
73
73
|
color: #1a1a1a !important;
|
|
74
74
|
}
|
|
75
75
|
|
|
@@ -80,7 +80,7 @@ export const MenuContainer = styled.div `
|
|
|
80
80
|
0 2px 8px rgba(0, 0, 0, 0.3);
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
[data-theme='dark'] & *:not(svg):not(.right-icon-btn):not(.right-icon-btn *) {
|
|
83
|
+
[data-theme='dark'] & *:not(svg):not(.right-icon-btn):not(.right-icon-btn *):not(.icon-wrapper):not(.icon-wrapper *) {
|
|
84
84
|
color: #e0e0e0 !important;
|
|
85
85
|
}
|
|
86
86
|
|
|
@@ -174,7 +174,9 @@ export const MenuItemContent = styled.div `
|
|
|
174
174
|
gap: 10px;
|
|
175
175
|
flex: 1;
|
|
176
176
|
`;
|
|
177
|
-
export const IconWrapper = styled.span
|
|
177
|
+
export const IconWrapper = styled.span.attrs({
|
|
178
|
+
className: 'icon-wrapper'
|
|
179
|
+
}) `
|
|
178
180
|
display: flex;
|
|
179
181
|
align-items: center;
|
|
180
182
|
justify-content: center;
|
|
@@ -289,7 +291,7 @@ export const Submenu = styled.div `
|
|
|
289
291
|
}
|
|
290
292
|
|
|
291
293
|
/* Reset color inheritance from parent with !important to override panel header styles */
|
|
292
|
-
& *:not(svg):not(.right-icon-btn):not(.right-icon-btn *) {
|
|
294
|
+
& *:not(svg):not(.right-icon-btn):not(.right-icon-btn *):not(.icon-wrapper):not(.icon-wrapper *) {
|
|
293
295
|
color: #1a1a1a !important;
|
|
294
296
|
}
|
|
295
297
|
|
|
@@ -300,7 +302,7 @@ export const Submenu = styled.div `
|
|
|
300
302
|
0 2px 8px rgba(0, 0, 0, 0.3);
|
|
301
303
|
}
|
|
302
304
|
|
|
303
|
-
[data-theme='dark'] & *:not(svg):not(.right-icon-btn):not(.right-icon-btn *) {
|
|
305
|
+
[data-theme='dark'] & *:not(svg):not(.right-icon-btn):not(.right-icon-btn *):not(.icon-wrapper):not(.icon-wrapper *) {
|
|
304
306
|
color: #e0e0e0 !important;
|
|
305
307
|
}
|
|
306
308
|
|
|
@@ -1348,7 +1348,7 @@ const TMDcmtForm = ({ allTasks = [], getAllTasks, deleteTaskByIdsCallback, addTa
|
|
|
1348
1348
|
did: Number(DID),
|
|
1349
1349
|
name: fromDTD?.nameLoc ?? SDKUI_Localizator.Widget_Activities,
|
|
1350
1350
|
},
|
|
1351
|
-
}, allTasks: allTasks, getAllTasks: getAllTasks, deleteTaskByIdsCallback: deleteTaskByIdsCallback, addTaskCallback: addTaskCallback, editTaskCallback: editTaskCallback, afterTaskSaved: afterTaskSaved }));
|
|
1351
|
+
}, allTasks: allTasks, getAllTasks: getAllTasks, deleteTaskByIdsCallback: deleteTaskByIdsCallback, addTaskCallback: addTaskCallback, editTaskCallback: editTaskCallback, afterTaskSaved: afterTaskSaved, handleNavigateToWGs: handleNavigateToWGs, handleNavigateToDossiers: handleNavigateToDossiers }));
|
|
1352
1352
|
}, [allTasks, TID, DID, fromDTD]);
|
|
1353
1353
|
const normalizedTID = TID !== undefined ? Number(TID) : undefined;
|
|
1354
1354
|
const defaultPanelDimensions = {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { FormModes, TaskContext } from "../../../ts";
|
|
2
|
-
import { TaskDescriptor } from "@topconsultnpm/sdk-ts";
|
|
2
|
+
import { HomeBlogPost, TaskDescriptor } from "@topconsultnpm/sdk-ts";
|
|
3
3
|
interface TMDcmtTasksProps {
|
|
4
4
|
taskContext: TaskContext;
|
|
5
5
|
allTasks: Array<TaskDescriptor>;
|
|
@@ -8,6 +8,8 @@ interface TMDcmtTasksProps {
|
|
|
8
8
|
addTaskCallback: (task: TaskDescriptor) => Promise<void>;
|
|
9
9
|
editTaskCallback: (task: TaskDescriptor) => Promise<void>;
|
|
10
10
|
afterTaskSaved: (task: TaskDescriptor | undefined, formMode: FormModes | undefined, forceRefresh?: boolean) => Promise<void>;
|
|
11
|
+
handleNavigateToWGs?: (value: HomeBlogPost | number) => Promise<void>;
|
|
12
|
+
handleNavigateToDossiers?: (value: HomeBlogPost | number) => Promise<void>;
|
|
11
13
|
}
|
|
12
14
|
declare const TMDcmtTasks: (props: TMDcmtTasksProps) => import("react/jsx-runtime").JSX.Element;
|
|
13
15
|
export default TMDcmtTasks;
|
|
@@ -6,7 +6,7 @@ import { useTMPanelManagerContext } from "../../layout/panelManager/TMPanelManag
|
|
|
6
6
|
import TMPanel from "../../base/TMPanel";
|
|
7
7
|
import TMTasksPanelContent from "../tasks/TMTasksPanelContent";
|
|
8
8
|
const TMDcmtTasks = (props) => {
|
|
9
|
-
const { taskContext, allTasks, getAllTasks, deleteTaskByIdsCallback, addTaskCallback, editTaskCallback, afterTaskSaved } = props;
|
|
9
|
+
const { taskContext, allTasks, getAllTasks, deleteTaskByIdsCallback, addTaskCallback, editTaskCallback, afterTaskSaved, handleNavigateToWGs, handleNavigateToDossiers } = props;
|
|
10
10
|
// Get the current device type (e.g., mobile, tablet, desktop) using a custom hook.
|
|
11
11
|
const deviceType = useDeviceType();
|
|
12
12
|
// This avoids unnecessary re-renders by only recalculating when deviceType changes.
|
|
@@ -19,6 +19,6 @@ const TMDcmtTasks = (props) => {
|
|
|
19
19
|
text: SDKUI_Localizator.Refresh,
|
|
20
20
|
},
|
|
21
21
|
], children: _jsx(IconMenuVertical, { id: "TMTaksPanel-Commands-Header", color: 'white', cursor: 'pointer' }) }), []);
|
|
22
|
-
return _jsx("div", { style: { width: "100%", height: "100%", position: 'relative' }, children: _jsx(TMPanel, { title: SDKUI_Localizator.Widget_Activities, allowMaximize: !isMobile && countVisibleLeafPanels() > 1, onClose: countVisibleLeafPanels() > 1 ? () => togglePanelVisibility("tmDcmtTasks") : undefined, onMaximize: countVisibleLeafPanels() > 1 ? () => toggleMaximize("tmDcmtTasks") : undefined, toolbar: toolbar, children: _jsx(TMTasksPanelContent, { id: "dcmtTasks", taskContext: taskContext, allTasks: allTasks, getAllTasks: getAllTasks, deleteTaskByIdsCallback: deleteTaskByIdsCallback, addTaskCallback: addTaskCallback, editTaskCallback: editTaskCallback, handleNavigateToWGs: () => { return Promise.resolve(); }, handleNavigateToDossiers: () => { return Promise.resolve(); }, afterTaskSaved: afterTaskSaved }) }) });
|
|
22
|
+
return _jsx("div", { style: { width: "100%", height: "100%", position: 'relative' }, children: _jsx(TMPanel, { title: SDKUI_Localizator.Widget_Activities, allowMaximize: !isMobile && countVisibleLeafPanels() > 1, onClose: countVisibleLeafPanels() > 1 ? () => togglePanelVisibility("tmDcmtTasks") : undefined, onMaximize: countVisibleLeafPanels() > 1 ? () => toggleMaximize("tmDcmtTasks") : undefined, toolbar: toolbar, children: _jsx(TMTasksPanelContent, { id: "dcmtTasks", taskContext: taskContext, allTasks: allTasks, getAllTasks: getAllTasks, deleteTaskByIdsCallback: deleteTaskByIdsCallback, addTaskCallback: addTaskCallback, editTaskCallback: editTaskCallback, handleNavigateToWGs: handleNavigateToWGs ? handleNavigateToWGs : () => { return Promise.resolve(); }, handleNavigateToDossiers: handleNavigateToDossiers ? handleNavigateToDossiers : () => { return Promise.resolve(); }, afterTaskSaved: afterTaskSaved }) }) });
|
|
23
23
|
};
|
|
24
24
|
export default TMDcmtTasks;
|
|
@@ -154,10 +154,12 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
|
|
|
154
154
|
const [expansionAbortController, setExpansionAbortController] = useState(undefined);
|
|
155
155
|
// Ref to track last loaded input to prevent unnecessary reloads
|
|
156
156
|
const lastLoadedInputRef = React.useRef('');
|
|
157
|
+
// State to track loaded input key - triggers re-render for focus selection
|
|
158
|
+
const [loadedInputKey, setLoadedInputKey] = React.useState('');
|
|
157
159
|
// Ref to track if user has manually expanded/collapsed static items
|
|
158
160
|
const userInteractedWithStaticItemsRef = React.useRef(false);
|
|
159
|
-
// Ref to track
|
|
160
|
-
const
|
|
161
|
+
// Ref to track the last inputKey for which we set the focused item
|
|
162
|
+
const lastFocusedInputRef = React.useRef('');
|
|
161
163
|
/**
|
|
162
164
|
* Generate a stable key from inputDcmts to detect real changes
|
|
163
165
|
*/
|
|
@@ -657,16 +659,18 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
|
|
|
657
659
|
if (!inputDcmts || inputDcmts.length === 0 || dcmtTypes.length === 0) {
|
|
658
660
|
setTreeData([]);
|
|
659
661
|
lastLoadedInputRef.current = '';
|
|
662
|
+
lastFocusedInputRef.current = '';
|
|
663
|
+
setLoadedInputKey('');
|
|
660
664
|
userInteractedWithStaticItemsRef.current = false; // Reset interaction flag
|
|
661
665
|
return;
|
|
662
666
|
}
|
|
663
667
|
// Generate current input key
|
|
664
668
|
const currentKey = getInputKey();
|
|
665
|
-
// Skip if we already loaded this exact data
|
|
669
|
+
// Skip if we already loaded or are loading this exact data
|
|
666
670
|
if (currentKey === lastLoadedInputRef.current && treeData.length > 0) {
|
|
667
671
|
return;
|
|
668
672
|
}
|
|
669
|
-
// Mark as loading this key
|
|
673
|
+
// Mark as loading this key to prevent duplicate loads
|
|
670
674
|
lastLoadedInputRef.current = currentKey;
|
|
671
675
|
// Reset interaction flag when loading new data
|
|
672
676
|
userInteractedWithStaticItemsRef.current = false;
|
|
@@ -675,6 +679,9 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
|
|
|
675
679
|
setWaitPanelValuePrimary(0);
|
|
676
680
|
// Call loadData and use .then() instead of await to allow React to render
|
|
677
681
|
loadData().then(() => {
|
|
682
|
+
// Mark as loaded AFTER data is ready - state update triggers re-render for focus selection
|
|
683
|
+
lastLoadedInputRef.current = currentKey;
|
|
684
|
+
setLoadedInputKey(currentKey);
|
|
678
685
|
setShowWaitPanel(false);
|
|
679
686
|
setWaitPanelTextPrimary('');
|
|
680
687
|
setWaitPanelMaxValuePrimary(0);
|
|
@@ -689,18 +696,21 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
|
|
|
689
696
|
setTreeData(prevData => updateHiddenProperty(prevData));
|
|
690
697
|
}, [showZeroDcmts, updateHiddenProperty]);
|
|
691
698
|
/**
|
|
692
|
-
* Set
|
|
693
|
-
* Focuses on the
|
|
694
|
-
*
|
|
699
|
+
* Set focused item when data finishes loading
|
|
700
|
+
* Focuses on the first document (under root) every time new data is loaded
|
|
701
|
+
* Works both on initial load and on navigation (onPrev/onNext)
|
|
695
702
|
*/
|
|
696
703
|
useEffect(() => {
|
|
697
|
-
|
|
698
|
-
//
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
+
const currentInputKey = getInputKey();
|
|
705
|
+
// Ensure data has finished loading for current input
|
|
706
|
+
if (loadedInputKey !== currentInputKey) {
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
if (!showMainDocument || !onFocusedItemChanged || !treeData.length || !inputDcmts?.length) {
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
// Skip if we already focused for this inputKey
|
|
713
|
+
if (lastFocusedInputRef.current === currentInputKey) {
|
|
704
714
|
return;
|
|
705
715
|
}
|
|
706
716
|
// Helper function to recursively find the first document with isRoot=true
|
|
@@ -722,17 +732,11 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
|
|
|
722
732
|
// Find the first document marked as root (set by setupInitialTreeExpansion)
|
|
723
733
|
const docNode = findFirstRootDocument(treeData);
|
|
724
734
|
if (docNode) {
|
|
725
|
-
// Set the focused item
|
|
735
|
+
// Set the focused item and mark this inputKey as focused
|
|
726
736
|
onFocusedItemChanged(docNode);
|
|
727
|
-
|
|
737
|
+
lastFocusedInputRef.current = currentInputKey;
|
|
728
738
|
}
|
|
729
|
-
}, [treeData, showMainDocument, onFocusedItemChanged, inputDcmts]);
|
|
730
|
-
/**
|
|
731
|
-
* Reset initial focus flag when input documents change
|
|
732
|
-
*/
|
|
733
|
-
useEffect(() => {
|
|
734
|
-
initialFocusSetRef.current = false;
|
|
735
|
-
}, [getInputKey()]);
|
|
739
|
+
}, [treeData, loadedInputKey, showMainDocument, onFocusedItemChanged, inputDcmts, getInputKey]);
|
|
736
740
|
/**
|
|
737
741
|
* Sync static items state when additionalStaticItems change
|
|
738
742
|
* IMPORTANT: Only update if user hasn't manually interacted with the tree,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useRef, useState } from 'react';
|
|
3
|
-
import { ObjectClasses, TaskDescriptor, PdGs, UserListCacheService, SDK_Localizator
|
|
3
|
+
import { ObjectClasses, TaskDescriptor, PdGs, UserListCacheService, SDK_Localizator } from '@topconsultnpm/sdk-ts';
|
|
4
4
|
import { gotoPDGExtendedLabel, taskValidatorAsync } from './TMTasksUtils';
|
|
5
5
|
import ScrollView from 'devextreme-react/scroll-view';
|
|
6
6
|
import TMLayoutContainer from '../../base/TMLayout';
|
|
@@ -22,12 +22,6 @@ const TMTaskForm = (props) => {
|
|
|
22
22
|
const sfo = new SaveFormOptions();
|
|
23
23
|
sfo.objClass = ObjectClasses.Task;
|
|
24
24
|
const customizeFormData = (task) => {
|
|
25
|
-
if (formMode === FormModes.Duplicate) {
|
|
26
|
-
task.toID = undefined;
|
|
27
|
-
task.toName = undefined;
|
|
28
|
-
task.state = Task_States.NotStarted;
|
|
29
|
-
return task;
|
|
30
|
-
}
|
|
31
25
|
if (!isContextualCreate)
|
|
32
26
|
return task;
|
|
33
27
|
if (formMode !== FormModes.Create)
|
|
@@ -99,8 +93,12 @@ const TMTaskForm = (props) => {
|
|
|
99
93
|
};
|
|
100
94
|
fetchUsers();
|
|
101
95
|
}, []);
|
|
96
|
+
// Imposta i campi readonly in base al ruolo utente e al contesto
|
|
102
97
|
useEffect(() => {
|
|
103
|
-
if (formDataOrig
|
|
98
|
+
if (!formDataOrig)
|
|
99
|
+
return;
|
|
100
|
+
// UPDATE: se l'utente è receiver (destinatario del task), i campi principali sono readonly
|
|
101
|
+
if (formMode === FormModes.Update) {
|
|
104
102
|
const taskRole = getCurrentUserTaskRole(formDataOrig);
|
|
105
103
|
setFieldsReadOnly({
|
|
106
104
|
name: taskRole === 'receiver',
|
|
@@ -114,12 +112,9 @@ const TMTaskForm = (props) => {
|
|
|
114
112
|
remTime: false,
|
|
115
113
|
response: false
|
|
116
114
|
});
|
|
117
|
-
const newTaskDescriptor = new TaskDescriptor();
|
|
118
|
-
Object.assign(newTaskDescriptor, formDataOrig);
|
|
119
|
-
newTaskDescriptor.isNew = 0;
|
|
120
|
-
editTaskCallback(newTaskDescriptor);
|
|
121
115
|
}
|
|
122
|
-
|
|
116
|
+
// CREATE/DUPLICATE da DossierAction: il nome è precompilato e bloccato
|
|
117
|
+
if ((formMode === FormModes.Create || formMode === FormModes.Duplicate) && taskContext?.dossier && taskContext?.dossier?.origin === 'DossierAction' && currentTask) {
|
|
123
118
|
setFieldsReadOnly({
|
|
124
119
|
name: true,
|
|
125
120
|
description: false,
|
|
@@ -134,6 +129,17 @@ const TMTaskForm = (props) => {
|
|
|
134
129
|
});
|
|
135
130
|
}
|
|
136
131
|
}, [formDataOrig, formMode]);
|
|
132
|
+
// Notifica il task come "non nuovo" quando si è in modalità Update
|
|
133
|
+
useEffect(() => {
|
|
134
|
+
if (!formDataOrig)
|
|
135
|
+
return;
|
|
136
|
+
if (formMode !== FormModes.Update)
|
|
137
|
+
return;
|
|
138
|
+
const newTaskDescriptor = new TaskDescriptor();
|
|
139
|
+
Object.assign(newTaskDescriptor, formDataOrig);
|
|
140
|
+
newTaskDescriptor.isNew = 0;
|
|
141
|
+
editTaskCallback(newTaskDescriptor);
|
|
142
|
+
}, [formDataOrig, formMode]);
|
|
137
143
|
// Function to handle changes in the priority value of a TM Drop Down
|
|
138
144
|
const onPriorityValueChange = (e) => {
|
|
139
145
|
if (!e?.target?.value)
|
|
@@ -498,10 +498,10 @@ export const STATUS_TRANSITIONS = {
|
|
|
498
498
|
},
|
|
499
499
|
// Destinatario del task: può modificare lo stato liberamente, ma solo riaprire o chiudere se completato
|
|
500
500
|
receiver: {
|
|
501
|
-
[Task_States.NotStarted]: [Task_States.InProgress, Task_States.Waiting, Task_States.Deferred, Task_States.Completed],
|
|
502
|
-
[Task_States.InProgress]: [Task_States.InProgress, Task_States.Waiting, Task_States.Deferred, Task_States.Completed],
|
|
503
|
-
[Task_States.Waiting]: [Task_States.InProgress, Task_States.Waiting, Task_States.Deferred, Task_States.Completed],
|
|
504
|
-
[Task_States.Deferred]: [Task_States.InProgress, Task_States.Waiting, Task_States.Deferred, Task_States.Completed],
|
|
501
|
+
[Task_States.NotStarted]: [Task_States.NotStarted, Task_States.InProgress, Task_States.Waiting, Task_States.Deferred, Task_States.Completed],
|
|
502
|
+
[Task_States.InProgress]: [Task_States.NotStarted, Task_States.InProgress, Task_States.Waiting, Task_States.Deferred, Task_States.Completed],
|
|
503
|
+
[Task_States.Waiting]: [Task_States.NotStarted, Task_States.InProgress, Task_States.Waiting, Task_States.Deferred, Task_States.Completed],
|
|
504
|
+
[Task_States.Deferred]: [Task_States.NotStarted, Task_States.InProgress, Task_States.Waiting, Task_States.Deferred, Task_States.Completed],
|
|
505
505
|
[Task_States.Completed]: [Task_States.Closed, Task_States.InProgress],
|
|
506
506
|
[Task_States.Closed]: [Task_States.NotStarted, Task_States.InProgress, Task_States.Waiting, Task_States.Deferred, Task_States.Completed, Task_States.Closed]
|
|
507
507
|
}
|
|
@@ -9,6 +9,7 @@ interface ConnectionComponentProps {
|
|
|
9
9
|
onClick: (id: string, event: React.MouseEvent) => void;
|
|
10
10
|
onDoubleClick: (id: string) => void;
|
|
11
11
|
onConnectionEndpointMouseDown?: (connectionId: string, endpointType: 'source' | 'sink', mouseEvent: React.MouseEvent) => void;
|
|
12
|
+
onContextMenu?: (id: string, event: React.MouseEvent) => void;
|
|
12
13
|
}
|
|
13
14
|
declare const _default: React.NamedExoticComponent<ConnectionComponentProps>;
|
|
14
15
|
export default _default;
|
|
@@ -37,7 +37,7 @@ const StyledSquareConnector = styled.rect `
|
|
|
37
37
|
stroke-width: 1;
|
|
38
38
|
pointer-events: all;
|
|
39
39
|
`;
|
|
40
|
-
const ConnectionComponent = ({ connection, isSelected, sourcePoint, sinkPoint, isTemporary, onClick, onDoubleClick, onConnectionEndpointMouseDown }) => {
|
|
40
|
+
const ConnectionComponent = ({ connection, isSelected, sourcePoint, sinkPoint, isTemporary, onClick, onDoubleClick, onConnectionEndpointMouseDown, onContextMenu }) => {
|
|
41
41
|
const connectionColor = getConnectionColor(connection.OutputStatus);
|
|
42
42
|
// Funzione per renderizzare la forma della freccia
|
|
43
43
|
const renderArrow = useCallback((arrowType, angle) => {
|
|
@@ -82,7 +82,11 @@ const ConnectionComponent = ({ connection, isSelected, sourcePoint, sinkPoint, i
|
|
|
82
82
|
event.stopPropagation();
|
|
83
83
|
onDoubleClick?.(connection.ID);
|
|
84
84
|
}, [onDoubleClick, connection.ID]);
|
|
85
|
-
|
|
85
|
+
const handleContextMenu = useCallback((event) => {
|
|
86
|
+
event.stopPropagation();
|
|
87
|
+
onContextMenu?.(connection.ID, event);
|
|
88
|
+
}, [onContextMenu, connection.ID]);
|
|
89
|
+
return (_jsxs("g", { children: [_jsx(StyledPathHitArea, { d: connection.PathGeometry, onClick: (e) => onClick(connection.ID, e), onDoubleClick: handleDoubleClick, onContextMenu: handleContextMenu }), _jsx(StyledPath, { d: connection.PathGeometry, "$isSelected": isSelected, "$isTemporary": isTemporary, "$outputStatus": connection.OutputStatus, onClick: (e) => onClick(connection.ID, e), onDoubleClick: handleDoubleClick, onContextMenu: handleContextMenu }), connection.SinkArrowSymbol !== ArrowSymbol.None && (_jsxs("g", { transform: calculateArrowTransform(true), children: [renderArrow(connection.SinkArrowSymbol, 0), " "] })), connection.SourceArrowSymbol !== ArrowSymbol.None && (_jsx("g", { transform: calculateArrowTransform(false) + ' rotate(180)', children: renderArrow(connection.SourceArrowSymbol, 0) })), isSelected && (_jsxs(_Fragment, { children: [_jsx(StyledSquareConnector, { "$color": connectionColor, x: sourcePoint.x - 5, y: sourcePoint.y - 5, width: 10, height: 10 }), _jsx(StyledSquareConnector, { "$color": connectionColor, x: sinkPoint.x - 5, y: sinkPoint.y - 5, width: 10, height: 10, cursor: 'move', onMouseDown: (e) => {
|
|
86
90
|
e.stopPropagation(); // Impedisce la propagazione dell'evento a elementi sottostanti
|
|
87
91
|
onConnectionEndpointMouseDown?.(connection.ID, 'sink', e);
|
|
88
92
|
} })] }))] }));
|
|
@@ -8,13 +8,15 @@ import ConnectionComponent from './ConnectionComponent';
|
|
|
8
8
|
import DiagramItemComponent from './DiagramItemComponent';
|
|
9
9
|
import DiagramItemSvgContent from './DiagramItemSvgContent';
|
|
10
10
|
import { calculateArrowAngle, downloadFile, getConnectionPoint, getNewWfDiagram, isConnectionNonLinear, validateDiagram } from './workflowHelpers';
|
|
11
|
-
import { IconFlowChart, IconUndo, IconRestore, IconAdjust, IconCopy, IconCut, IconPaste, IconPin, IconUnpin, IconChevronRight, IconCloseOutline, IconNew, SDKUI_Localizator, generateUUID, IconExport, IconImport, IconWindowMaximize, IconZoomIn, IconZoomOut, IconPencil, IconLock, LocalizeDiagramItemType, IconWindowMinimize, IconZoomAuto } from '../../../../helper';
|
|
11
|
+
import { IconFlowChart, IconUndo, IconRestore, IconAdjust, IconCopy, IconCut, IconPaste, IconPin, IconUnpin, IconChevronRight, IconCloseOutline, IconNew, SDKUI_Localizator, generateUUID, IconExport, IconImport, IconWindowMaximize, IconZoomIn, IconZoomOut, IconPencil, IconLock, LocalizeDiagramItemType, IconWindowMinimize, IconZoomAuto, IconCloseCircle, IconSuccess } from '../../../../helper';
|
|
12
12
|
import { ButtonNames, TMExceptionBoxManager, TMMessageBoxManager } from '../../../base/TMPopUp';
|
|
13
13
|
import { StyledLoadingContainer, StyledSpinner } from '../../../base/Styled';
|
|
14
14
|
import DiagramItemForm from './DiagramItemForm';
|
|
15
15
|
import ReactDOM from 'react-dom';
|
|
16
16
|
import ConnectionForm from './ConnectionForm';
|
|
17
17
|
import TMFloatingMenuBar from '../../../NewComponents/FloatingMenuBar';
|
|
18
|
+
import TMContextMenu from '../../../NewComponents/ContextMenu/TMContextMenu';
|
|
19
|
+
import { TMColors } from '../../../../utils/theme';
|
|
18
20
|
const ZoomLevelText = styled.span `
|
|
19
21
|
font-size: 0.9em;
|
|
20
22
|
color: #555;
|
|
@@ -331,6 +333,9 @@ const WFDiagram = ({ xmlDiagramString, currentSetID, allowEdit = true, onDiagram
|
|
|
331
333
|
const [wfDiagram, setWfDiagram] = useState(null);
|
|
332
334
|
const [selectedItems, setSelectedItems] = useState(new Set());
|
|
333
335
|
const [selectedConnections, setSelectedConnections] = useState(new Set());
|
|
336
|
+
// Context menu per le connections
|
|
337
|
+
const [connectionContextMenuPosition, setConnectionContextMenuPosition] = useState({ x: 0, y: 0 });
|
|
338
|
+
const [contextMenuConnectionId, setContextMenuConnectionId] = useState(null);
|
|
334
339
|
const [wfDiagramHistory, setWfDiagramHistory] = useState([]);
|
|
335
340
|
const [historyIndex, setHistoryIndex] = useState(-1);
|
|
336
341
|
const isUndoingRedoing = useRef(false);
|
|
@@ -1311,6 +1316,62 @@ const WFDiagram = ({ xmlDiagramString, currentSetID, allowEdit = true, onDiagram
|
|
|
1311
1316
|
if (!isCtrlPressed)
|
|
1312
1317
|
setSelectedItems(new Set());
|
|
1313
1318
|
}, [isReadOnly]);
|
|
1319
|
+
const handleConnectionContextMenu = useCallback((id, event) => {
|
|
1320
|
+
if (isReadOnly)
|
|
1321
|
+
return;
|
|
1322
|
+
event.preventDefault();
|
|
1323
|
+
event.stopPropagation();
|
|
1324
|
+
// Trova la connection
|
|
1325
|
+
const connection = wfDiagram?.Connections.find(conn => conn.ID === id);
|
|
1326
|
+
if (!connection)
|
|
1327
|
+
return;
|
|
1328
|
+
// Verifica se esce da un DiagramItemTypes.Condition o Approval
|
|
1329
|
+
const sourceItem = wfDiagram?.DiagramItems.find(item => item.ID === connection.Source.ParentDiagramItem.ID);
|
|
1330
|
+
if (!sourceItem || ![DiagramItemTypes.Condition, DiagramItemTypes.Approval].includes(sourceItem.Type))
|
|
1331
|
+
return;
|
|
1332
|
+
// Mostra il context menu
|
|
1333
|
+
setContextMenuConnectionId(id);
|
|
1334
|
+
setConnectionContextMenuPosition({ x: event.clientX, y: event.clientY });
|
|
1335
|
+
}, [isReadOnly, wfDiagram]);
|
|
1336
|
+
const handleChangeConnectionOutputStatus = useCallback((connectionId) => {
|
|
1337
|
+
if (isReadOnly || !wfDiagram)
|
|
1338
|
+
return;
|
|
1339
|
+
const connection = wfDiagram.Connections.find(conn => conn.ID === connectionId);
|
|
1340
|
+
if (!connection)
|
|
1341
|
+
return;
|
|
1342
|
+
// Cambia l'OutputStatus da Completed a Rejected e viceversa
|
|
1343
|
+
const newStatus = connection.OutputStatus === WorkItemStatus.Completed
|
|
1344
|
+
? WorkItemStatus.Rejected
|
|
1345
|
+
: WorkItemStatus.Completed;
|
|
1346
|
+
const updatedDiagram = {
|
|
1347
|
+
...wfDiagram,
|
|
1348
|
+
Connections: wfDiagram.Connections.map(conn => conn.ID === connectionId ? { ...conn, OutputStatus: newStatus } : conn)
|
|
1349
|
+
};
|
|
1350
|
+
updateDiagram(updatedDiagram);
|
|
1351
|
+
setWfDiagram(updatedDiagram);
|
|
1352
|
+
setContextMenuConnectionId(null);
|
|
1353
|
+
}, [isReadOnly, wfDiagram, updateDiagram]);
|
|
1354
|
+
const closeConnectionContextMenu = useCallback(() => {
|
|
1355
|
+
setContextMenuConnectionId(null);
|
|
1356
|
+
}, []);
|
|
1357
|
+
// Menu items per il context menu delle connections
|
|
1358
|
+
const connectionContextMenuItems = useMemo(() => {
|
|
1359
|
+
if (!contextMenuConnectionId || !wfDiagram)
|
|
1360
|
+
return [];
|
|
1361
|
+
const connection = wfDiagram.Connections.find(conn => conn.ID === contextMenuConnectionId);
|
|
1362
|
+
if (!connection)
|
|
1363
|
+
return [];
|
|
1364
|
+
const targetStatus = connection.OutputStatus === WorkItemStatus.Completed
|
|
1365
|
+
? SDKUI_Localizator.WorkItemStatus_Rejected
|
|
1366
|
+
: SDKUI_Localizator.WorkItemStatus_Completed;
|
|
1367
|
+
return [{
|
|
1368
|
+
icon: connection.OutputStatus === WorkItemStatus.Completed
|
|
1369
|
+
? _jsx(IconCloseCircle, { color: TMColors.success, fontSize: 16 })
|
|
1370
|
+
: _jsx(IconSuccess, { color: TMColors.error, fontSize: 16 }),
|
|
1371
|
+
name: SDKUI_Localizator.ChangeStatusTo.replaceParams(targetStatus),
|
|
1372
|
+
onClick: () => handleChangeConnectionOutputStatus(contextMenuConnectionId)
|
|
1373
|
+
}];
|
|
1374
|
+
}, [contextMenuConnectionId, wfDiagram, handleChangeConnectionOutputStatus]);
|
|
1314
1375
|
const handleDrag = useCallback((id, newX, newY) => {
|
|
1315
1376
|
if (isReadOnly)
|
|
1316
1377
|
return;
|
|
@@ -1393,10 +1454,15 @@ const WFDiagram = ({ xmlDiagramString, currentSetID, allowEdit = true, onDiagram
|
|
|
1393
1454
|
if (sourceItem.Type === DiagramItemTypes.Condition || sourceItem.Type === DiagramItemTypes.Approval) {
|
|
1394
1455
|
const existingConnectionsFromSource = wfDiagram.Connections.filter(conn => conn.Source.ParentDiagramItem.ID === sourceItem.ID);
|
|
1395
1456
|
if (existingConnectionsFromSource.length === 0) {
|
|
1457
|
+
// Prima connessione → Completed
|
|
1396
1458
|
outputStatus = WorkItemStatus.Completed;
|
|
1397
1459
|
}
|
|
1398
1460
|
else if (existingConnectionsFromSource.length === 1) {
|
|
1399
|
-
|
|
1461
|
+
// Seconda connessione → contrario della prima
|
|
1462
|
+
const firstConnection = existingConnectionsFromSource[0];
|
|
1463
|
+
outputStatus = firstConnection.OutputStatus === WorkItemStatus.Completed
|
|
1464
|
+
? WorkItemStatus.Rejected
|
|
1465
|
+
: WorkItemStatus.Completed;
|
|
1400
1466
|
}
|
|
1401
1467
|
}
|
|
1402
1468
|
const newConnection = {
|
|
@@ -1842,8 +1908,12 @@ const WFDiagram = ({ xmlDiagramString, currentSetID, allowEdit = true, onDiagram
|
|
|
1842
1908
|
const sinkPoint = getConnectionPoint(sinkItem, connection.Sink.ConnectorName);
|
|
1843
1909
|
// Determina se questa è la connessione che stiamo trascinando
|
|
1844
1910
|
const isThisConnectionBeingDragged = isDraggingExistingConnectionEndpoint && draggingConnectionId === connection.ID;
|
|
1845
|
-
return (_jsx(ConnectionComponent, { connection: connection, isSelected: selectedConnections.has(connection.ID), sourcePoint: sourcePoint, sinkPoint: sinkPoint, isTemporary: isThisConnectionBeingDragged, onClick: handleConnectionClick, onDoubleClick: handleDoubleClickConnection, onConnectionEndpointMouseDown: handleConnectionEndpointMouseDown }, connection.ID));
|
|
1846
|
-
}), isDrawingConnection && tempConnectionPathData && (_jsx(TempConnectionPath, { d: tempConnectionPathData })), isDraggingExistingConnectionEndpoint && tempConnectionPathData && (_jsx(TempConnectionPath, { d: tempConnectionPathData })), isDrawingSelectionRect && currentSelectionRect && (_jsx(SelectionRect, { x: currentSelectionRect.x, y: currentSelectionRect.y, width: currentSelectionRect.width, height: currentSelectionRect.height }))] }) })) : (_jsx(DiagramMessage, { children: `${SDKUI_Localizator.WorkflowDiagramMissingOrInvalid} ...` })) }), isModalOpen && itemToEdit && (_jsx(DiagramItemForm, { itemToEdit: itemToEdit, wf: wfDiagram?.Info, onClose: handleCloseModal, onApply: handleUpdateDiagramItem })), isConnectionModalOpen && connectionToEdit && (_jsx(ConnectionForm, { connectionToEdit: connectionToEdit, onClose: () => setIsConnectionModalOpen(false), onApply: handleUpdateConnection }))
|
|
1911
|
+
return (_jsx(ConnectionComponent, { connection: connection, isSelected: selectedConnections.has(connection.ID), sourcePoint: sourcePoint, sinkPoint: sinkPoint, isTemporary: isThisConnectionBeingDragged, onClick: handleConnectionClick, onDoubleClick: handleDoubleClickConnection, onConnectionEndpointMouseDown: handleConnectionEndpointMouseDown, onContextMenu: handleConnectionContextMenu }, connection.ID));
|
|
1912
|
+
}), isDrawingConnection && tempConnectionPathData && (_jsx(TempConnectionPath, { d: tempConnectionPathData })), isDraggingExistingConnectionEndpoint && tempConnectionPathData && (_jsx(TempConnectionPath, { d: tempConnectionPathData })), isDrawingSelectionRect && currentSelectionRect && (_jsx(SelectionRect, { x: currentSelectionRect.x, y: currentSelectionRect.y, width: currentSelectionRect.width, height: currentSelectionRect.height }))] }) })) : (_jsx(DiagramMessage, { children: `${SDKUI_Localizator.WorkflowDiagramMissingOrInvalid} ...` })) }), isModalOpen && itemToEdit && (_jsx(DiagramItemForm, { itemToEdit: itemToEdit, wf: wfDiagram?.Info, onClose: handleCloseModal, onApply: handleUpdateDiagramItem })), isConnectionModalOpen && connectionToEdit && (_jsx(ConnectionForm, { connectionToEdit: connectionToEdit, onClose: () => setIsConnectionModalOpen(false), onApply: handleUpdateConnection })), _jsx(TMContextMenu, { items: connectionContextMenuItems, externalControl: {
|
|
1913
|
+
visible: contextMenuConnectionId !== null,
|
|
1914
|
+
position: connectionContextMenuPosition,
|
|
1915
|
+
onClose: closeConnectionContextMenu
|
|
1916
|
+
} })] }));
|
|
1847
1917
|
return (_jsxs(_Fragment, { children: [!isFullScreen && (_jsx(DiagramWrapper, { ref: diagramRef, children: diagramContent })), isFullScreen && ReactDOM.createPortal(_jsx(FullScreenContainer, { ref: fullScreenRef, tabIndex: 0, onKeyDown: handleFullScreenKeyDown, children: diagramContent }), document.body)] }));
|
|
1848
1918
|
};
|
|
1849
1919
|
export default WFDiagram;
|
|
@@ -85,6 +85,7 @@ export declare class SDKUI_Localizator {
|
|
|
85
85
|
static get CancelCheckOut(): string;
|
|
86
86
|
static get Cancel(): "Abbrechen" | "Cancel" | "Anular" | "Annuler" | "Cancelar" | "Annulla";
|
|
87
87
|
static get ChangePassword(): "Kennwort ändern" | "Change password" | "Cambiar la contraseña" | "Changer le mot de passe" | "Alterar a senha" | "Cambia password";
|
|
88
|
+
static get ChangeStatusTo(): string;
|
|
88
89
|
static get CharactersRemaining(): "verbleibende Zeichen" | "characters remaining" | "caracteres restantes" | "caractères restants" | "caratteri rimanenti";
|
|
89
90
|
static get CheckIn(): "Check in" | "Enregistrement";
|
|
90
91
|
static get CheckInElementConfirm(): string;
|
|
@@ -805,6 +805,16 @@ export class SDKUI_Localizator {
|
|
|
805
805
|
default: return "Cambia password";
|
|
806
806
|
}
|
|
807
807
|
}
|
|
808
|
+
static get ChangeStatusTo() {
|
|
809
|
+
switch (this._cultureID) {
|
|
810
|
+
case CultureIDs.De_DE: return "Ändern in {{0}}";
|
|
811
|
+
case CultureIDs.En_US: return "Change to {{0}}";
|
|
812
|
+
case CultureIDs.Es_ES: return "Cambiar a {{0}}";
|
|
813
|
+
case CultureIDs.Fr_FR: return "Changer en {{0}}";
|
|
814
|
+
case CultureIDs.Pt_PT: return "Alterar para {{0}}";
|
|
815
|
+
default: return "Cambia in {{0}}";
|
|
816
|
+
}
|
|
817
|
+
}
|
|
808
818
|
static get CharactersRemaining() {
|
|
809
819
|
switch (this._cultureID) {
|
|
810
820
|
case CultureIDs.De_DE: return "verbleibende Zeichen";
|