@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.
@@ -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>, never>> & string;
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 if we've already set the initial focused item
160
- const initialFocusSetRef = React.useRef(false);
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 initial focused item when showMainDocument = true
693
- * Focuses on the main document (first inputDcmts) when data is loaded
694
- * For master mode with invertMasterNavigation=false, focuses on the first master document
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
- // Only execute if:
698
- // 1. showMainDocument is true
699
- // 2. onFocusedItemChanged callback exists
700
- // 3. We have tree data
701
- // 4. We have input documents
702
- // 5. We haven't already set the initial focus
703
- if (!showMainDocument || !onFocusedItemChanged || !treeData.length || !inputDcmts?.length || initialFocusSetRef.current) {
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
- initialFocusSetRef.current = true;
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, Task_States } from '@topconsultnpm/sdk-ts';
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 && formMode === FormModes.Update) {
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
- else if (formDataOrig && formMode === FormModes.Create && taskContext?.dossier && taskContext?.dossier?.origin === 'DossierAction' && currentTask) {
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
- return (_jsxs("g", { children: [_jsx(StyledPathHitArea, { d: connection.PathGeometry, onClick: (e) => onClick(connection.ID, e), onDoubleClick: handleDoubleClick }), _jsx(StyledPath, { d: connection.PathGeometry, "$isSelected": isSelected, "$isTemporary": isTemporary, "$outputStatus": connection.OutputStatus, onClick: (e) => onClick(connection.ID, e), onDoubleClick: handleDoubleClick }), 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) => {
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
- outputStatus = WorkItemStatus.Rejected;
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";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@topconsultnpm/sdkui-react",
3
- "version": "6.20.0-dev2.52",
3
+ "version": "6.20.0-dev2.54",
4
4
  "description": "",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1",