@topconsultnpm/sdkui-react 6.20.0-dev2.53 → 6.20.0-dev2.55

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
 
@@ -158,7 +158,7 @@ const TMHtmlEditor = (props) => {
158
158
  borderStyle: 'solid',
159
159
  borderColor: hasValidationErrors ? "red" : "#e0e0e0 #e0e0e0 #616161",
160
160
  width: "100%",
161
- height: `calc(100% - ${showCount ? 15 : 0}px - ${validationItems.length > 0 ? 15 : 0}px)`,
161
+ height: `calc(100% - ${showCount ? 15 : 0}px - ${validationItems.length > 0 ? 25 : 0}px)`,
162
162
  }, children: _jsx(HtmlEditor, { ref: editorRef, placeholder: SDKUI_Localizator.TypeAMessage + "...", width: "100%", height: "100%", value: markup, onValueChange: onValueChangeCallback, mentions: mentionsConfig, style: { overflow: 'hidden', outline: "none", fontSize: '1rem' }, children: isEditorEnabled && (toolbarMode === 'compact' ?
163
163
  _jsxs(Toolbar, { multiline: false, children: [_jsx(Item, { name: "undo" }), _jsx(Item, { name: "redo" }), _jsx(Item, { name: "separator" }), _jsx(Item, { name: "bold" }), _jsx(Item, { name: "italic" }), _jsx(Item, { name: "strike" }), _jsx(Item, { name: "underline" }), _jsx(Item, { name: "separator" }), _jsx(Item, { name: "orderedList" }), _jsx(Item, { name: "bulletList" })] }) :
164
164
  _jsxs(Toolbar, { children: [_jsx(Item, { name: "undo" }), _jsx(Item, { name: "redo" }), _jsx(Item, { name: "separator" }), _jsx(Item, { name: "separator" }), _jsx(Item, { name: "bold" }), _jsx(Item, { name: "italic" }), _jsx(Item, { name: "strike" }), _jsx(Item, { name: "underline" }), _jsx(Item, { name: "separator" }), _jsx(Item, { name: "alignLeft" }), _jsx(Item, { name: "alignCenter" }), _jsx(Item, { name: "alignRight" }), _jsx(Item, { name: "alignJustify" }), _jsx(Item, { name: "separator" }), _jsx(Item, { name: "color" }), _jsx(Item, { name: "background" }), _jsx(Item, { name: "separator" }), _jsx(Item, { name: "link" }), _jsx(Item, { name: "image" }), _jsx(Item, { name: "separator" })] })) }) }), showCount ? ((() => {
@@ -14,6 +14,7 @@ interface TMBlogCommentFormProps {
14
14
  onFilterCreated?: (predicate: (post: BlogPost) => boolean) => void;
15
15
  refreshCallback?: () => Promise<void>;
16
16
  isCommentRequired?: boolean;
17
+ maxLength?: number;
17
18
  }
18
19
  declare const TMBlogCommentForm: (props: TMBlogCommentFormProps) => import("react/jsx-runtime").JSX.Element;
19
20
  export default TMBlogCommentForm;
@@ -28,8 +28,7 @@ const getNonDirectoryFiles = (items, exclude) => {
28
28
  });
29
29
  };
30
30
  const TMBlogCommentForm = (props) => {
31
- const maxLength = 1000;
32
- const { participants, selectedAttachments, selectedAttachmentDid, allFileItems, allArchivedDocumentsFileItems = [], onClose, context, showAttachmentsSection = true, removeAndEditAttachment = true, onFilterCreated, refreshCallback, isCommentRequired = false } = props;
31
+ const { participants, selectedAttachments, selectedAttachmentDid, allFileItems, allArchivedDocumentsFileItems = [], onClose, context, showAttachmentsSection = true, removeAndEditAttachment = true, onFilterCreated, refreshCallback, isCommentRequired = false, maxLength = 1000 } = props;
33
32
  // Initialize state with combined array
34
33
  const [dataSource, setDataSource] = useState(() => [...getNonDirectoryFiles(allFileItems?.items || [], []), ...allArchivedDocumentsFileItems]);
35
34
  const [isEditorEnabled, setIsEditorEnabled] = useState(true);
@@ -1633,7 +1633,7 @@ const TMDcmtForm = ({ allTasks = [], getAllTasks, deleteTaskByIdsCallback, addTa
1633
1633
  value: FormulaHelper.addFormulaTag(newFormula.expression)
1634
1634
  }));
1635
1635
  } }), 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), getAllTasks: getAllTasks }), (isModal && onClose) && _jsx("div", { id: "TMDcmtFormShowConfirmForClose-" + id })] }) }), _jsx(TMToppyDraggableHelpCenter, { isVisible: isToppyVisible, content: _jsx(TMDcmtFormActionButtons, { showToppyForApprove: showToppyForApprove, workItems: workItems, deviceType: deviceType, isMobile: isMobile, handleSignApprove: handleSignApprove, setShowApprovePopup: setShowApprovePopup, setShowRejectPopup: setShowRejectPopup, setShowReAssignPopup: setShowReAssignPopup, setShowMoreInfoPopup: setShowMoreInfoPopup, fromDTD: fromDTD, showToppyForCompleteMoreInfo: showToppyForCompleteMoreInfo, moreInfoTasks: moreInfoTasks, setShowCommentForm: setShowCommentForm, showToppyForReferences: showToppyForReferences, dcmtReferences: dcmtReferences, referenceActionMap: referenceActionMap, handleNavigateToReference: handleNavigateToReference, setShowMoreInfoTaskPopup: setShowMoreInfoTaskPopup, setShowMoreInfoTaskTask: setShowMoreInfoTaskTask }) })] }), (showCommentForm && TID && DID) &&
1636
- _jsx(TMBlogCommentForm, { context: { engine: 'SearchEngine', object: { tid: TID, did: DID } }, onClose: () => setShowCommentForm(false), refreshCallback: handleCompleteMoreInfo, participants: [], showAttachmentsSection: false, allArchivedDocumentsFileItems: [] }), isOpenDetails &&
1636
+ _jsx(TMBlogCommentForm, { maxLength: 500, context: { engine: 'SearchEngine', object: { tid: TID, did: DID } }, onClose: () => setShowCommentForm(false), refreshCallback: handleCompleteMoreInfo, participants: [], showAttachmentsSection: false, allArchivedDocumentsFileItems: [] }), isOpenDetails &&
1637
1637
  _jsx(StyledModalContainer, { children: _jsx(TMMasterDetailDcmts, { deviceType: deviceType, isForMaster: false, inputDcmts: getSelectionDcmtInfo(), allowNavigation: allowNavigation, canNext: canNext, canPrev: canPrev, onNext: onNext, onPrev: onPrev, onBack: () => setIsOpenDetails(false), allTasks: allTasks, getAllTasks: getAllTasks, deleteTaskByIdsCallback: deleteTaskByIdsCallback, addTaskCallback: addTaskCallback, editTaskCallback: editTaskCallback, handleNavigateToWGs: handleNavigateToWGs, handleNavigateToDossiers: handleNavigateToDossiers }) }), isOpenMaster &&
1638
1638
  _jsxs(StyledModalContainer, { children: [_jsx(TMMasterDetailDcmts, { deviceType: deviceType, inputDcmts: getSelectionDcmtInfo(), isForMaster: true, allowNavigation: allowNavigation, canNext: canNext, canPrev: canPrev, onNext: onNext, onPrev: onPrev, onBack: () => setIsOpenMaster(false), appendMasterDcmts: handleAddItem, allTasks: allTasks, getAllTasks: getAllTasks, deleteTaskByIdsCallback: deleteTaskByIdsCallback, addTaskCallback: addTaskCallback, editTaskCallback: editTaskCallback, handleNavigateToWGs: handleNavigateToWGs, handleNavigateToDossiers: handleNavigateToDossiers }), secondaryMasterDcmts.length > 0 && secondaryMasterDcmts.map((dcmt, index) => {
1639
1639
  return (_jsx(StyledModalContainer, { children: _jsx(TMMasterDetailDcmts, { deviceType: deviceType, inputDcmts: [dcmt], isForMaster: true, allowNavigation: false, onBack: () => handleRemoveItem(dcmt.TID, dcmt.DID), appendMasterDcmts: handleAddItem, allTasks: allTasks, getAllTasks: getAllTasks, deleteTaskByIdsCallback: deleteTaskByIdsCallback, addTaskCallback: addTaskCallback, editTaskCallback: editTaskCallback, handleNavigateToWGs: handleNavigateToWGs, handleNavigateToDossiers: handleNavigateToDossiers }) }, `${index}-${dcmt.DID}`));
@@ -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,
@@ -93,8 +93,12 @@ const TMTaskForm = (props) => {
93
93
  };
94
94
  fetchUsers();
95
95
  }, []);
96
+ // Imposta i campi readonly in base al ruolo utente e al contesto
96
97
  useEffect(() => {
97
- 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) {
98
102
  const taskRole = getCurrentUserTaskRole(formDataOrig);
99
103
  setFieldsReadOnly({
100
104
  name: taskRole === 'receiver',
@@ -108,12 +112,9 @@ const TMTaskForm = (props) => {
108
112
  remTime: false,
109
113
  response: false
110
114
  });
111
- const newTaskDescriptor = new TaskDescriptor();
112
- Object.assign(newTaskDescriptor, formDataOrig);
113
- newTaskDescriptor.isNew = 0;
114
- editTaskCallback(newTaskDescriptor);
115
115
  }
116
- 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) {
117
118
  setFieldsReadOnly({
118
119
  name: true,
119
120
  description: false,
@@ -128,6 +129,17 @@ const TMTaskForm = (props) => {
128
129
  });
129
130
  }
130
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]);
131
143
  // Function to handle changes in the priority value of a TM Drop Down
132
144
  const onPriorityValueChange = (e) => {
133
145
  if (!e?.target?.value)
@@ -493,7 +493,7 @@ export const STATUS_TRANSITIONS = {
493
493
  [Task_States.InProgress]: [Task_States.NotStarted, Task_States.InProgress, Task_States.Waiting, Task_States.Deferred, Task_States.Completed],
494
494
  [Task_States.Waiting]: [Task_States.NotStarted, Task_States.InProgress, Task_States.Waiting, Task_States.Deferred, Task_States.Completed],
495
495
  [Task_States.Deferred]: [Task_States.NotStarted, Task_States.InProgress, Task_States.Waiting, Task_States.Deferred, Task_States.Completed],
496
- [Task_States.Completed]: [Task_States.Closed, Task_States.InProgress],
496
+ [Task_States.Completed]: [Task_States.Completed, Task_States.Closed, Task_States.InProgress],
497
497
  [Task_States.Closed]: [Task_States.NotStarted, Task_States.InProgress, Task_States.Waiting, Task_States.Deferred, Task_States.Completed, Task_States.Closed]
498
498
  },
499
499
  // Destinatario del task: può modificare lo stato liberamente, ma solo riaprire o chiudere se completato
@@ -502,7 +502,7 @@ export const STATUS_TRANSITIONS = {
502
502
  [Task_States.InProgress]: [Task_States.NotStarted, Task_States.InProgress, Task_States.Waiting, Task_States.Deferred, Task_States.Completed],
503
503
  [Task_States.Waiting]: [Task_States.NotStarted, Task_States.InProgress, Task_States.Waiting, Task_States.Deferred, Task_States.Completed],
504
504
  [Task_States.Deferred]: [Task_States.NotStarted, Task_States.InProgress, Task_States.Waiting, Task_States.Deferred, Task_States.Completed],
505
- [Task_States.Completed]: [Task_States.Closed, Task_States.InProgress],
505
+ [Task_States.Completed]: [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
  }
508
508
  };
@@ -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.error, fontSize: 16 })
1370
+ : _jsx(IconSuccess, { color: TMColors.success, 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 = {
@@ -1820,7 +1886,7 @@ const WFDiagram = ({ xmlDiagramString, currentSetID, allowEdit = true, onDiagram
1820
1886
  fullScreenRef.current.focus();
1821
1887
  }
1822
1888
  }, [isFullScreen]);
1823
- const diagramContent = (_jsxs(CanvasContainer, { onDoubleClick: handleCanvasDoubleClick, children: [_jsx("input", { ref: fileInputRef, type: "file", accept: ".xml" // Filtra per file XML
1889
+ const diagramContent = (_jsxs(CanvasContainer, { onDoubleClick: handleCanvasDoubleClick, onContextMenu: (e) => e.preventDefault(), children: [_jsx("input", { ref: fileInputRef, type: "file", accept: ".xml" // Filtra per file XML
1824
1890
  , onChange: handleFileChange, style: { display: 'none' } }), SDK_Globals.tmSession?.SessionDescr?.appModuleID === AppModules.SURFER ?
1825
1891
  _jsx(TMFloatingMenuBar, { containerRef: diagramRef, defaultPosition: { x: 45, y: 85 }, enableConfigMode: false, fixedItems: [
1826
1892
  { icon: _jsx(IconZoomIn, {}), name: SDKUI_Localizator.ZoomIn, disabled: isAutoZoomEnabled, onClick: () => { handleZoomIn(); }, id: 'zoom-in', isPinned: true },
@@ -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.53",
3
+ "version": "6.20.0-dev2.55",
4
4
  "description": "",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1",