@topconsultnpm/sdkui-react 6.20.0-dev1.113 → 6.20.0-dev1.115

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.
@@ -12,6 +12,8 @@ const TMContextMenu = ({ items, trigger = 'right', children, target, externalCon
12
12
  parentNames: [],
13
13
  });
14
14
  const [hoveredSubmenus, setHoveredSubmenus] = useState([]);
15
+ // Tracks items whose rightIconProps have been clicked, so the icon color flips immediately
16
+ const [toggledItemIds, setToggledItemIds] = useState(new Set());
15
17
  const isMobile = useIsMobile();
16
18
  const isIOS = useIsIOS();
17
19
  const menuRef = useRef(null);
@@ -35,6 +37,7 @@ const TMContextMenu = ({ items, trigger = 'right', children, target, externalCon
35
37
  }));
36
38
  }
37
39
  setHoveredSubmenus([]);
40
+ setToggledItemIds(new Set());
38
41
  };
39
42
  // Sync with external control when provided
40
43
  useEffect(() => {
@@ -247,8 +250,17 @@ const TMContextMenu = ({ items, trigger = 'right', children, target, externalCon
247
250
  const handleContextMenu = (e) => {
248
251
  if (trigger === 'right') {
249
252
  e.preventDefault();
250
- e.stopPropagation(); // Prevent event from bubbling to close other menus prematurely
251
- // Small delay to ensure other menus receive the contextmenu event and close first
253
+ // Close any other open context menus by dispatching a synthetic contextmenu event to document
254
+ // This triggers the document listener in other ContextMenu instances to close them
255
+ const syntheticEvent = new MouseEvent('contextmenu', {
256
+ bubbles: true,
257
+ cancelable: true,
258
+ clientX: -1000, // Position outside viewport so it doesn't interfere
259
+ clientY: -1000,
260
+ });
261
+ document.dispatchEvent(syntheticEvent);
262
+ e.stopPropagation(); // Prevent event from bubbling after closing others
263
+ // Small delay to ensure other menus close first
252
264
  setTimeout(() => {
253
265
  setMenuState({
254
266
  visible: true,
@@ -421,13 +433,36 @@ const TMContextMenu = ({ items, trigger = 'right', children, target, externalCon
421
433
  e.stopPropagation();
422
434
  handleItemClick(item);
423
435
  };
424
- const handleRightIconClick = (e) => {
436
+ const handleRightIconPropsClick = (e) => {
437
+ e.preventDefault();
425
438
  e.stopPropagation();
426
- // if (item.disabled) return;
427
- item.onRightIconClick?.();
428
- handleClose();
439
+ e.nativeEvent.stopImmediatePropagation();
440
+ item.rightIconProps?.onClick?.();
441
+ if (item.id) {
442
+ setToggledItemIds(prev => {
443
+ const next = new Set(prev);
444
+ if (next.has(item.id))
445
+ next.delete(item.id);
446
+ else
447
+ next.add(item.id);
448
+ return next;
449
+ });
450
+ }
429
451
  };
430
- return (_jsxs(S.MenuItem, { "$disabled": item.disabled, "$hasSubmenu": !!item.submenu && item.submenu.length > 0, "$beginGroup": item.beginGroup, "data-disabled": item.disabled ? "true" : undefined, onMouseDown: handleClick, onMouseEnter: (e) => !isMobile && handleMouseEnter(item, e, depth + 1), onMouseLeave: () => !isMobile && handleMouseLeave(depth + 1), title: item.tooltip, children: [_jsxs(S.MenuItemContent, { children: [item.icon && _jsx(S.IconWrapper, { children: item.icon }), _jsx(S.MenuItemName, { children: item.name })] }), item.rightIcon && item.onRightIconClick && (_jsx(S.RightIconButton, { onClick: handleRightIconClick, onMouseDown: (e) => e.stopPropagation(), "aria-label": `Action for ${item.name}`, children: item.rightIcon })), item.submenu && item.submenu.length > 0 && (_jsx(S.SubmenuIndicator, { "$isMobile": isMobile, children: isMobile ? '›' : '▸' }))] }, itemKey));
452
+ // rightIconProps takes precedence over rightIcon
453
+ let rightIconElement = null;
454
+ let rightIconClickHandler = null;
455
+ if (item.rightIconProps) {
456
+ const rip = item.rightIconProps;
457
+ const wasToggled = item.id ? toggledItemIds.has(item.id) : false;
458
+ const isActive = wasToggled ? !rip.isActive : rip.isActive;
459
+ const color = isActive ? (rip.activeColor || '#d32f2f') : (rip.inactiveColor || '#626262');
460
+ rightIconElement = _jsx("span", { style: { color, display: 'flex', alignItems: 'center' }, children: rip.icon });
461
+ if (rip.onClick) {
462
+ rightIconClickHandler = handleRightIconPropsClick;
463
+ }
464
+ }
465
+ return (_jsxs(S.MenuItem, { "$disabled": item.disabled, "$hasSubmenu": !!item.submenu && item.submenu.length > 0, "$beginGroup": item.beginGroup, "data-disabled": item.disabled ? "true" : undefined, onMouseDown: handleClick, onMouseEnter: (e) => !isMobile && handleMouseEnter(item, e, depth + 1), onMouseLeave: () => !isMobile && handleMouseLeave(depth + 1), title: item.tooltip, children: [_jsxs(S.MenuItemContent, { children: [item.icon && _jsx(S.IconWrapper, { children: item.icon }), _jsx(S.MenuItemName, { children: item.name })] }), rightIconElement && rightIconClickHandler ? (_jsx(S.RightIconButton, { onClick: rightIconClickHandler, onMouseDown: (e) => e.stopPropagation(), "aria-label": `Action for ${item.name}`, children: rightIconElement })) : rightIconElement ? (_jsx(S.IconWrapper, { children: rightIconElement })) : null, item.submenu && item.submenu.length > 0 && (_jsx(S.SubmenuIndicator, { "$isMobile": isMobile, children: isMobile ? '›' : '▸' }))] }, itemKey));
431
466
  });
432
467
  };
433
468
  const currentParentName = menuState.parentNames.at(-1) || '';
@@ -1,3 +1,10 @@
1
+ export interface RightIconProps {
2
+ icon: React.ReactNode;
3
+ activeColor?: string;
4
+ inactiveColor?: string;
5
+ isActive?: boolean;
6
+ onClick?: () => void;
7
+ }
1
8
  export interface TMContextMenuItemProps {
2
9
  id?: string;
3
10
  name: string;
@@ -6,8 +13,7 @@ export interface TMContextMenuItemProps {
6
13
  onClick?: (data?: any) => void;
7
14
  submenu?: TMContextMenuItemProps[];
8
15
  visible?: boolean;
9
- rightIcon?: React.ReactNode;
10
- onRightIconClick?: () => void;
16
+ rightIconProps?: RightIconProps;
11
17
  beginGroup?: boolean;
12
18
  tooltip?: string;
13
19
  operationType?: 'singleRow' | 'multiRow';
@@ -1,14 +1,14 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useState, useRef, useEffect, useCallback } from 'react';
3
3
  import { ContextMenu } from '../ContextMenu';
4
4
  import ShowAlert from '../../base/TMAlert';
5
5
  import TMTooltip from '../../base/TMTooltip';
6
6
  import * as S from './styles';
7
- import { IconAdd, IconCloseOutline, IconMenuVertical, IconPin, IconSave, IconSeparator, IconUndo, SDKUI_Globals, SDKUI_Localizator } from '../../../helper';
7
+ import { IconAdd, IconCloseOutline, IconDelete, IconMenuVertical, IconPin, IconRotate, IconSave, IconSeparator, IconSettings, IconUndo, SDKUI_Globals, SDKUI_Localizator } from '../../../helper';
8
8
  import { ButtonNames, TMMessageBoxManager } from '../../base/TMPopUp';
9
9
  const Separator = (props) => (_jsx("svg", { viewBox: "0 0 24 24", fill: "currentColor", height: "1em", width: "1em", ...props, children: _jsx("path", { d: "M12 2v20", stroke: "currentColor", strokeWidth: "3", strokeLinecap: "round" }) }));
10
10
  const IconDraggableDots = (props) => (_jsx("svg", { fontSize: 18, viewBox: "0 0 24 24", fill: "currentColor", height: "1em", width: "1em", ...props, children: _jsx("path", { d: "M9 3a2 2 0 11-4 0 2 2 0 014 0zm0 9a2 2 0 11-4 0 2 2 0 014 0zm0 9a2 2 0 11-4 0 2 2 0 014 0zm10-18a2 2 0 11-4 0 2 2 0 014 0zm0 9a2 2 0 11-4 0 2 2 0 014 0zm0 9a2 2 0 11-4 0 2 2 0 014 0z" }) }));
11
- const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], isConstrained = false, defaultPosition = { x: 1, y: 90 }, defaultOrientation = 'horizontal', maxItems = 100, contextMenuDefaultPinnedIds = [], defaultItems = [], disbaleConfigMode = false, bgColor = undefined, }) => {
11
+ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], isConstrained = false, defaultPosition = { x: 1, y: 90 }, defaultOrientation = 'horizontal', maxItems = 100, contextMenuDefaultPinnedIds = [], defaultItems = [], disbaleConfigMode = false, bgColor = undefined, hasContextMenu = true, pinnedItemIds: externalPinnedItemIds, onPinChange, }) => {
12
12
  const percentToPixels = (percent, containerSize) => {
13
13
  return (percent / 100) * containerSize;
14
14
  };
@@ -145,6 +145,10 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], isConstrained
145
145
  const positionBeforeOrientationChange = useRef(null);
146
146
  const floatingBarItemIds = useRef(new Set());
147
147
  const floatingBarItemNames = useRef(new Set());
148
+ const isSyncingFromExternal = useRef(false);
149
+ const isExitingConfigMode = useRef(false);
150
+ const isLocalChange = useRef(false);
151
+ const dragStartPosition = useRef(null);
148
152
  useEffect(() => {
149
153
  floatingBarItemIds.current = new Set(state.items.map(i => i.id));
150
154
  floatingBarItemNames.current = new Set(state.items.map(i => i.name));
@@ -247,6 +251,44 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], isConstrained
247
251
  }
248
252
  }
249
253
  }, disbaleConfigMode ? [defaultItems] : []); // Update when defaultItems change if config mode is disabled
254
+ // Sync with external pinnedItemIds when they change (from parent component)
255
+ useEffect(() => {
256
+ // Skip sync if exiting config mode - let save effect update external state first
257
+ if (isExitingConfigMode.current) {
258
+ isExitingConfigMode.current = false;
259
+ return;
260
+ }
261
+ // Skip sync if a local change was just made (e.g. right-click remove/add separator)
262
+ if (isLocalChange.current) {
263
+ isLocalChange.current = false;
264
+ return;
265
+ }
266
+ if (externalPinnedItemIds === undefined || disbaleConfigMode || state.isConfigMode)
267
+ return;
268
+ const flatItems = flattenMenuItems(contextMenuItems);
269
+ // Build items from external pinned IDs
270
+ const itemsFromExternal = externalPinnedItemIds
271
+ .map((id) => {
272
+ if (id.startsWith('separator-')) {
273
+ return {
274
+ id,
275
+ name: 'Separator',
276
+ icon: _jsx(Separator, {}),
277
+ onClick: () => { },
278
+ isSeparator: true,
279
+ };
280
+ }
281
+ return flatItems.find(item => item.id === id);
282
+ })
283
+ .filter((item) => item !== undefined);
284
+ // Only update if different
285
+ const currentIds = state.items.map(i => i.id).join(',');
286
+ const newIds = itemsFromExternal.map(i => i.id).join(',');
287
+ if (currentIds !== newIds) {
288
+ isSyncingFromExternal.current = true; // Mark that we're syncing from external
289
+ setState(s => ({ ...s, items: itemsFromExternal }));
290
+ }
291
+ }, [externalPinnedItemIds, contextMenuItems, disbaleConfigMode, state.isConfigMode, flattenMenuItems]);
250
292
  const togglePin = useCallback((item) => {
251
293
  setState(s => {
252
294
  const isInFloatingBar = s.items.some(i => i.id === item.id);
@@ -336,6 +378,8 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], isConstrained
336
378
  const isAlreadyPinned = currentItemIds.has(itemId);
337
379
  const pinItem = {
338
380
  ...item,
381
+ // Remove rightIconProps in config mode - clicking the item itself pins it
382
+ rightIconProps: undefined,
339
383
  onClick: item.onClick && !item.submenu ? () => {
340
384
  if (flatItem && !isAlreadyPinned) {
341
385
  togglePin(flatItem);
@@ -370,9 +414,12 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], isConstrained
370
414
  const isPinned = currentItemIds.has(itemId);
371
415
  const itemWithPin = {
372
416
  ...item,
373
- rightIcon: flatItem ? _jsx(IconPin, { color: isPinned ? 'red' : 'black' }) : undefined,
374
- onRightIconClick: flatItem ? () => {
375
- togglePin(flatItem);
417
+ rightIconProps: flatItem ? {
418
+ icon: _jsx(IconPin, {}),
419
+ activeColor: 'red',
420
+ inactiveColor: 'black',
421
+ isActive: isPinned,
422
+ onClick: () => togglePin(flatItem),
376
423
  } : undefined,
377
424
  };
378
425
  if (item.submenu) {
@@ -404,6 +451,8 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], isConstrained
404
451
  };
405
452
  }
406
453
  }
454
+ // Save starting position to detect if user actually moved the bar
455
+ dragStartPosition.current = { ...pixelPosition };
407
456
  setState(s => ({ ...s, isDragging: true }));
408
457
  };
409
458
  const handleGripDoubleClick = () => {
@@ -443,6 +492,15 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], isConstrained
443
492
  x: pixelsToPercent(pixelPosition.x, containerSizeRef.current.width),
444
493
  y: pixelsToPercent(pixelPosition.y, containerSizeRef.current.height),
445
494
  };
495
+ // Only clear saved position if user actually moved the bar significantly (more than 5 pixels)
496
+ if (dragStartPosition.current) {
497
+ const dx = Math.abs(pixelPosition.x - dragStartPosition.current.x);
498
+ const dy = Math.abs(pixelPosition.y - dragStartPosition.current.y);
499
+ if (dx > 5 || dy > 5) {
500
+ positionBeforeOrientationChange.current = null;
501
+ }
502
+ }
503
+ dragStartPosition.current = null;
446
504
  setState(s => ({
447
505
  ...s,
448
506
  isDragging: false,
@@ -473,6 +531,8 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], isConstrained
473
531
  };
474
532
  }
475
533
  }
534
+ // Save starting position to detect if user actually moved the bar
535
+ dragStartPosition.current = { ...pixelPosition };
476
536
  setState(s => ({ ...s, isDragging: true }));
477
537
  };
478
538
  const handleTouchMove = useCallback((e) => {
@@ -502,6 +562,15 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], isConstrained
502
562
  x: pixelsToPercent(pixelPosition.x, containerSizeRef.current.width),
503
563
  y: pixelsToPercent(pixelPosition.y, containerSizeRef.current.height),
504
564
  };
565
+ // Only clear saved position if user actually moved the bar significantly (more than 5 pixels)
566
+ if (dragStartPosition.current) {
567
+ const dx = Math.abs(pixelPosition.x - dragStartPosition.current.x);
568
+ const dy = Math.abs(pixelPosition.y - dragStartPosition.current.y);
569
+ if (dx > 5 || dy > 5) {
570
+ positionBeforeOrientationChange.current = null;
571
+ }
572
+ }
573
+ dragStartPosition.current = null;
505
574
  setState(s => ({
506
575
  ...s,
507
576
  isDragging: false,
@@ -533,11 +602,12 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], isConstrained
533
602
  return; // Don't save during edit mode
534
603
  if (disbaleConfigMode)
535
604
  return; // Don't save if config mode is disabled
605
+ const pinnedIds = state.items.map(item => item.id);
536
606
  try {
537
607
  // Replace the entire object to trigger the Proxy
538
608
  SDKUI_Globals.userSettings.searchSettings.floatingMenuBar = {
539
609
  orientation: state.orientation,
540
- itemIds: state.items.map(item => item.id),
610
+ itemIds: pinnedIds,
541
611
  position: state.position,
542
612
  positionFormat: 'percentage',
543
613
  };
@@ -545,7 +615,13 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], isConstrained
545
615
  catch (error) {
546
616
  console.error('Failed to save FloatingMenuBar config:', error);
547
617
  }
548
- }, [state.orientation, state.items, state.position, state.isConfigMode, disbaleConfigMode]);
618
+ // Notify parent about pin changes only if not syncing from external
619
+ // This prevents infinite loop: external change -> sync -> save -> onPinChange -> external change
620
+ if (!isSyncingFromExternal.current) {
621
+ onPinChange?.(pinnedIds);
622
+ }
623
+ isSyncingFromExternal.current = false; // Reset the flag
624
+ }, [state.orientation, state.items, state.position, state.isConfigMode, disbaleConfigMode, onPinChange]);
549
625
  const toggleConfigMode = () => {
550
626
  setState(s => {
551
627
  if (!s.isConfigMode) {
@@ -560,6 +636,7 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], isConstrained
560
636
  else {
561
637
  // Exiting edit mode (applying changes) - clean up trailing separators and clear snapshot
562
638
  stateSnapshot.current = null;
639
+ isExitingConfigMode.current = true; // Prevent sync effect from overwriting changes
563
640
  const cleanedItems = removeTrailingSeparators(s.items);
564
641
  return { ...s, isConfigMode: false, items: cleanedItems };
565
642
  }
@@ -679,11 +756,11 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], isConstrained
679
756
  };
680
757
  const toggleOrientation = () => {
681
758
  const newOrientation = state.orientation === 'horizontal' ? 'vertical' : 'horizontal';
682
- // Hide the bar during transition to prevent flicker
683
- setIsOrientationChanging(true);
684
759
  // When switching from vertical back to horizontal, restore the original position
760
+ // Use visibility hiding only for this case to prevent flicker during position restoration
685
761
  if (state.orientation === 'vertical' && newOrientation === 'horizontal') {
686
762
  if (positionBeforeOrientationChange.current) {
763
+ setIsOrientationChanging(true); // Hide only when restoring position
687
764
  const savedPosition = positionBeforeOrientationChange.current.position;
688
765
  const savedPixelPosition = positionBeforeOrientationChange.current.pixelPosition;
689
766
  setPixelPosition(savedPixelPosition);
@@ -702,7 +779,9 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], isConstrained
702
779
  }
703
780
  }
704
781
  // When switching to vertical, save current position and check if we need to expand upward
782
+ // Use opacity hiding (doesn't affect focus unlike visibility:hidden)
705
783
  if (state.orientation === 'horizontal' && newOrientation === 'vertical') {
784
+ setIsOrientationChanging(true);
706
785
  // Save the current position before changing orientation
707
786
  positionBeforeOrientationChange.current = {
708
787
  position: { ...state.position },
@@ -750,7 +829,7 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], isConstrained
750
829
  ...s,
751
830
  position: { ...s.position, y: newPercentY },
752
831
  }));
753
- setIsOrientationChanging(false); // Show bar after position is set
832
+ setIsOrientationChanging(false);
754
833
  });
755
834
  });
756
835
  return;
@@ -787,16 +866,105 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], isConstrained
787
866
  }));
788
867
  }
789
868
  }
790
- setIsOrientationChanging(false); // Show bar after position is set
869
+ setIsOrientationChanging(false);
791
870
  });
792
871
  });
793
872
  };
794
873
  const removeItem = (itemId) => {
874
+ isLocalChange.current = true;
795
875
  setState(s => ({
796
876
  ...s,
797
877
  items: s.items.filter(item => item.id !== itemId),
798
878
  }));
799
879
  };
880
+ const getItemRightClickMenuItems = useCallback((item, index) => {
881
+ const hasSeparatorOnRight = index < state.items.length - 1 && state.items[index + 1]?.isSeparator;
882
+ const hasSeparatorOnLeft = index > 0 && state.items[index - 1]?.isSeparator;
883
+ return [
884
+ {
885
+ name: 'Rimuovi',
886
+ icon: _jsx(IconDelete, { fontSize: 16 }),
887
+ onClick: () => removeItem(item.id),
888
+ },
889
+ {
890
+ name: 'Aggiungi separatore a destra',
891
+ icon: _jsx(IconSeparator, { style: { transform: 'rotate(-90deg)' }, fontSize: 16 }),
892
+ disabled: hasSeparatorOnRight,
893
+ onClick: () => {
894
+ isLocalChange.current = true;
895
+ const separator = createSeparator();
896
+ setState(s => {
897
+ const newItems = [...s.items];
898
+ newItems.splice(index + 1, 0, separator);
899
+ return { ...s, items: newItems };
900
+ });
901
+ },
902
+ },
903
+ {
904
+ name: 'Aggiungi separatore a sinistra',
905
+ icon: _jsx(IconSeparator, { fontSize: 16 }),
906
+ disabled: hasSeparatorOnLeft,
907
+ onClick: () => {
908
+ isLocalChange.current = true;
909
+ const separator = createSeparator();
910
+ setState(s => {
911
+ const newItems = [...s.items];
912
+ newItems.splice(index, 0, separator);
913
+ return { ...s, items: newItems };
914
+ });
915
+ },
916
+ },
917
+ ...(!disbaleConfigMode ? [{
918
+ beginGroup: true,
919
+ name: SDKUI_Localizator.Configure,
920
+ icon: _jsx(IconSettings, { fontSize: 16 }),
921
+ onClick: () => {
922
+ if (!state.isConfigMode) {
923
+ toggleConfigMode();
924
+ }
925
+ },
926
+ }] : []),
927
+ {
928
+ name: state.orientation === 'horizontal' ? 'Floating bar verticale' : 'Floating bar orizzontale',
929
+ icon: _jsx(IconRotate, { fontSize: 16 }),
930
+ onClick: toggleOrientation,
931
+ },
932
+ ];
933
+ }, [state.items, state.isConfigMode, state.orientation, removeItem, createSeparator, toggleConfigMode, toggleOrientation, disbaleConfigMode]);
934
+ const getSeparatorRightClickMenuItems = useCallback((index) => {
935
+ return [
936
+ {
937
+ name: 'Rimuovi',
938
+ icon: _jsx(IconDelete, { fontSize: 16 }),
939
+ onClick: () => removeItem(state.items[index].id),
940
+ },
941
+ {
942
+ name: 'Aggiungi separatore a destra',
943
+ icon: _jsx(IconSeparator, { style: { transform: 'rotate(-90deg)' }, fontSize: 16 }),
944
+ disabled: true,
945
+ },
946
+ {
947
+ name: 'Aggiungi separatore a sinistra',
948
+ icon: _jsx(IconSeparator, { fontSize: 16 }),
949
+ disabled: true,
950
+ },
951
+ ...(!disbaleConfigMode ? [{
952
+ beginGroup: true,
953
+ name: SDKUI_Localizator.Configure,
954
+ icon: _jsx(IconSettings, { fontSize: 16 }),
955
+ onClick: () => {
956
+ if (!state.isConfigMode) {
957
+ toggleConfigMode();
958
+ }
959
+ },
960
+ }] : []),
961
+ {
962
+ name: state.orientation === 'horizontal' ? 'Floating bar verticale' : 'Floating bar orizzontale',
963
+ icon: _jsx(IconRotate, { fontSize: 16 }),
964
+ onClick: toggleOrientation,
965
+ },
966
+ ];
967
+ }, [state.items, state.isConfigMode, state.orientation, removeItem, toggleConfigMode, toggleOrientation, disbaleConfigMode]);
800
968
  // Drag and drop for reordering
801
969
  const handleDragStart = (e, index) => {
802
970
  if (!state.isConfigMode)
@@ -851,9 +1019,10 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], isConstrained
851
1019
  setState(s => ({ ...s, draggedItemIndex: null }));
852
1020
  setDragOverIndex(null);
853
1021
  };
854
- return (_jsxs(_Fragment, { children: [_jsx(S.Overlay, { "$visible": state.isConfigMode }), _jsxs(S.FloatingContainer, { ref: floatingRef, "$x": pixelPosition.x, "$y": pixelPosition.y, "$orientation": state.orientation, "$verticalDirection": state.verticalDirection, "$isDragging": state.isDragging, "$isConfigMode": state.isConfigMode, "$isConstrained": isConstrained, "$isHidden": isOrientationChanging, "$bgColor": bgColor, onContextMenu: (e) => e.preventDefault(), children: [!state.isConfigMode ? (_jsx(ContextMenu, { items: [
1022
+ return (_jsxs(_Fragment, { children: [_jsx(S.Overlay, { "$visible": state.isConfigMode }), _jsxs(S.FloatingContainer, { ref: floatingRef, "$x": pixelPosition.x, "$y": pixelPosition.y, "$orientation": state.orientation, "$verticalDirection": state.verticalDirection, "$isDragging": state.isDragging, "$isConfigMode": state.isConfigMode, "$isConstrained": isConstrained, "$isHidden": isOrientationChanging, "$bgColor": bgColor, onContextMenu: state.isConfigMode ? (e) => e.preventDefault() : undefined, children: [!state.isConfigMode ? (_jsx(ContextMenu, { items: [
855
1023
  ...(!disbaleConfigMode ? [{
856
1024
  name: SDKUI_Localizator.Configure,
1025
+ icon: _jsx(IconSettings, { fontSize: 16 }),
857
1026
  onClick: () => {
858
1027
  if (!state.isConfigMode) {
859
1028
  toggleConfigMode();
@@ -862,12 +1031,13 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], isConstrained
862
1031
  }] : []),
863
1032
  {
864
1033
  name: state.orientation === 'horizontal' ? 'Floating bar verticale' : 'Floating bar orizzontale',
1034
+ icon: _jsx(IconRotate, { fontSize: 16, style: { transform: state.orientation === 'horizontal' ? 'rotate(90deg)' : 'rotate(0deg)' } }),
865
1035
  onClick: toggleOrientation,
866
1036
  },
867
1037
  ], trigger: "right", children: _jsx(S.GripHandle, { "$orientation": state.orientation, onMouseDown: handleMouseDown, onTouchStart: handleTouchStart, onDoubleClick: handleGripDoubleClick, children: _jsx(IconDraggableDots, {}) }) })) : (_jsx(S.GripHandle, { "$orientation": state.orientation, onMouseDown: handleMouseDown, onTouchStart: handleTouchStart, onDoubleClick: handleGripDoubleClick, children: _jsx(IconDraggableDots, {}) })), _jsx(S.Separator, { "$orientation": state.orientation }), state.items.map((item, index) => {
868
1038
  // Handle separator items
869
1039
  if (item.isSeparator) {
870
- return (_jsxs(S.DraggableItem, { "$isDragging": state.draggedItemIndex === index, "$isDragOver": dragOverIndex === index && state.draggedItemIndex !== index, draggable: state.isConfigMode, onDragStart: (e) => handleDragStart(e, index), onDragEnter: (e) => handleDragEnter(e, index), onDragOver: handleDragOver, onDragLeave: (e) => handleDragLeave(e, index), onDrop: (e) => handleDrop(e, index), onDragEnd: handleDragEnd, children: [_jsx(S.ItemSeparator, { "$orientation": state.orientation, "$isConfigMode": state.isConfigMode }), state.isConfigMode && (_jsx(S.RemoveButton, { onClick: () => removeItem(item.id), children: "\u00D7" }))] }, item.id));
1040
+ return (_jsx(S.DraggableItem, { "$isDragging": state.draggedItemIndex === index, "$isDragOver": dragOverIndex === index && state.draggedItemIndex !== index, draggable: state.isConfigMode, onDragStart: (e) => handleDragStart(e, index), onDragEnter: (e) => handleDragEnter(e, index), onDragOver: handleDragOver, onDragLeave: (e) => handleDragLeave(e, index), onDrop: (e) => handleDrop(e, index), onDragEnd: handleDragEnd, children: state.isConfigMode ? (_jsxs(_Fragment, { children: [_jsx(S.ItemSeparator, { "$orientation": state.orientation, "$isConfigMode": state.isConfigMode }), _jsx(S.RemoveButton, { onClick: () => removeItem(item.id), children: "\u00D7" })] })) : (_jsx(ContextMenu, { items: getSeparatorRightClickMenuItems(index), trigger: "right", children: _jsx(S.ItemSeparator, { "$orientation": state.orientation, "$isConfigMode": state.isConfigMode }) })) }, item.id));
871
1041
  }
872
1042
  // Get current state (disabled and onClick) from contextMenuItems
873
1043
  const currentState = getCurrentItemState(item.id);
@@ -876,18 +1046,18 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], isConstrained
876
1046
  ? currentState.disabled === true
877
1047
  : item.disabled === true;
878
1048
  const currentOnClick = currentState.onClick || item.onClick; // Fallback to stored onClick if not found
879
- return (_jsx(S.DraggableItem, { "$isDragging": state.draggedItemIndex === index, "$isDragOver": dragOverIndex === index && state.draggedItemIndex !== index, draggable: state.isConfigMode, onDragStart: (e) => handleDragStart(e, index), onDragEnter: (e) => handleDragEnter(e, index), onDragOver: handleDragOver, onDragLeave: (e) => handleDragLeave(e, index), onDrop: (e) => handleDrop(e, index), onDragEnd: handleDragEnd, children: item.staticItem ? (
880
- // Visualizza l'elemento statico personalizzato
881
- _jsxs(_Fragment, { children: [item.staticItem, state.isConfigMode && (_jsx(S.RemoveButton, { onClick: () => removeItem(item.id), children: "\u00D7" }))] })) : (
882
- // Visualizza il pulsante standard
883
- _jsxs(_Fragment, { children: [_jsx(TMTooltip, { content: item.name, position: "bottom", children: _jsx(S.MenuButton, { onClick: () => {
884
- if (state.isConfigMode || isDisabled)
885
- return;
886
- if (currentOnClick) {
887
- currentOnClick();
888
- }
889
- }, disabled: isDisabled, "$isActive": item.isToggle, children: item.icon }) }), state.isConfigMode && (_jsx(S.RemoveButton, { onClick: () => removeItem(item.id), children: "\u00D7" }))] })) }, item.id));
890
- }), !state.isConfigMode && !disbaleConfigMode && contextMenuItems.length > 0 && (_jsx(ContextMenu, { items: getContextMenuItemsWithPinIcons(), trigger: "left", keepOpenOnClick: false, children: _jsx(S.ContextMenuButton, { children: _jsx(IconMenuVertical, {}) }) })), state.isConfigMode && state.items.length < maxItems && contextMenuItems.length > 0 && (_jsx(ContextMenu, { items: getPinContextMenuItems(), trigger: "left", keepOpenOnClick: true, children: _jsx(TMTooltip, { content: SDKUI_Localizator.Add, children: _jsx(S.AddButton, { children: _jsx(IconAdd, {}) }) }) })), state.isConfigMode && (_jsxs(_Fragment, { children: [_jsx(S.Separator, { "$orientation": state.orientation }), _jsxs(S.ButtonGroup, { "$orientation": state.orientation, children: [_jsx(TMTooltip, { content: SDKUI_Localizator.Undo, position: state.orientation === 'horizontal' ? 'right' : 'top', children: _jsx(S.UndoButton, { onClick: handleUndo, disabled: !hasChanges(), children: _jsx(IconUndo, { fontSize: 18 }) }) }), _jsx(TMTooltip, { content: state.items.length === 0
1049
+ return (_jsx(S.DraggableItem, { "$isDragging": state.draggedItemIndex === index, "$isDragOver": dragOverIndex === index && state.draggedItemIndex !== index, draggable: state.isConfigMode, onDragStart: (e) => handleDragStart(e, index), onDragEnter: (e) => handleDragEnter(e, index), onDragOver: handleDragOver, onDragLeave: (e) => handleDragLeave(e, index), onDrop: (e) => handleDrop(e, index), onDragEnd: handleDragEnd, children: state.isConfigMode ? (
1050
+ // Config mode: show remove button, no right-click menu
1051
+ _jsxs(_Fragment, { children: [item.staticItem ? (item.staticItem) : (_jsx(TMTooltip, { content: item.name, position: "bottom", children: _jsx(S.MenuButton, { onClick: () => { }, disabled: isDisabled, "$isActive": item.isToggle, children: item.icon }) })), _jsx(S.RemoveButton, { onClick: () => removeItem(item.id), children: "\u00D7" })] })) : (
1052
+ // Normal mode: wrap in right-click context menu
1053
+ _jsx(ContextMenu, { items: getItemRightClickMenuItems(item, index), trigger: "right", children: item.staticItem ? (_jsx("div", { children: item.staticItem })) : (_jsx(TMTooltip, { content: item.name, position: "bottom", children: _jsx(S.MenuButton, { onClick: () => {
1054
+ if (isDisabled)
1055
+ return;
1056
+ if (currentOnClick) {
1057
+ currentOnClick();
1058
+ }
1059
+ }, disabled: isDisabled, "$isActive": item.isToggle, children: item.icon }) })) })) }, item.id));
1060
+ }), !state.isConfigMode && !disbaleConfigMode && hasContextMenu && contextMenuItems.length > 0 && (_jsx(ContextMenu, { items: getContextMenuItemsWithPinIcons(), trigger: "left", keepOpenOnClick: false, children: _jsx(S.ContextMenuButton, { children: _jsx(IconMenuVertical, {}) }) })), state.isConfigMode && state.items.length < maxItems && contextMenuItems.length > 0 && (_jsx(ContextMenu, { items: getPinContextMenuItems(), trigger: "left", keepOpenOnClick: true, children: _jsx(TMTooltip, { content: SDKUI_Localizator.Add, children: _jsx(S.AddButton, { children: _jsx(IconAdd, {}) }) }) })), state.isConfigMode && (_jsxs(_Fragment, { children: [_jsx(S.Separator, { "$orientation": state.orientation }), _jsxs(S.ButtonGroup, { "$orientation": state.orientation, children: [_jsx(TMTooltip, { content: SDKUI_Localizator.Undo, position: state.orientation === 'horizontal' ? 'right' : 'top', children: _jsx(S.UndoButton, { onClick: handleUndo, disabled: !hasChanges(), children: _jsx(IconUndo, { fontSize: 18 }) }) }), _jsx(TMTooltip, { content: state.items.length === 0
891
1061
  ? 'Devi aggiungere almeno un item'
892
1062
  : SDKUI_Localizator.Save, position: state.orientation === 'horizontal' ? 'right' : 'top', children: _jsx(S.ApplyButton, { onClick: toggleConfigMode, disabled: state.items.length === 0 || !hasChanges(), children: _jsx(IconSave, { fontSize: 20 }) }) }), _jsx(TMTooltip, { content: SDKUI_Localizator.Close, position: state.orientation === 'horizontal' ? 'right' : 'top', children: _jsx(S.CloseButton, { onClick: handleClose, children: _jsx(IconCloseOutline, { fontSize: 20 }) }) })] })] }))] })] }));
893
1063
  };
@@ -39,7 +39,8 @@ export const FloatingContainer = styled.div.attrs(props => ({
39
39
  position: ${props => props.$isConstrained ? 'absolute' : 'fixed'};
40
40
  z-index: ${props => props.$isConfigMode ? 9999 : 1500};
41
41
  display: flex;
42
- visibility: ${props => props.$isHidden ? 'hidden' : 'visible'};
42
+ opacity: ${props => props.$isHidden ? 0 : 1};
43
+ pointer-events: ${props => props.$isHidden ? 'none' : 'auto'};
43
44
  flex-direction: ${props => props.$orientation === 'horizontal' ? 'row' : (props.$verticalDirection === 'up' ? 'column-reverse' : 'column')};
44
45
  align-items: center;
45
46
  background: ${props => props.$bgColor || 'linear-gradient(135deg, #0071BC 0%, #1B1464 100%)'};
@@ -139,7 +140,7 @@ export const ItemSeparator = styled.div `
139
140
  ` : `
140
141
  /* Normal mode: simple line with tight spacing */
141
142
  background: rgba(255, 255, 255, 0.25);
142
- width: ${props.$orientation === 'horizontal' ? '1px' : '100%'};
143
+ width: ${props.$orientation === 'horizontal' ? '1px' : '20px'};
143
144
  height: ${props.$orientation === 'horizontal' ? '20px' : '1px'};
144
145
  margin: ${props.$orientation === 'horizontal' ? '0 2px' : '2px 0'};
145
146
  `}
@@ -21,6 +21,9 @@ export interface TMFloatingMenuBarProps {
21
21
  defaultItems?: TMFloatingMenuItem[];
22
22
  disbaleConfigMode?: boolean;
23
23
  bgColor?: string;
24
+ hasContextMenu?: boolean;
25
+ pinnedItemIds?: string[];
26
+ onPinChange?: (pinnedIds: string[]) => void;
24
27
  }
25
28
  export interface Position {
26
29
  x: number;
@@ -246,7 +246,7 @@ const TMListView = ({ customGroupingHeaders, headerBackGroundColor, header, show
246
246
  onClick: () => { setOrderedBy(key); setShowOrderOptions(false); setOrderMenuVisible(false); },
247
247
  icon: _jsx(IconColumns, { fontSize: 16 }),
248
248
  name: key,
249
- rightIcon: key === orderedBy ? _jsx(IconApply, { fontSize: 14, color: 'gray' }) : undefined
249
+ rightIconProps: key === orderedBy ? { icon: _jsx(IconApply, { fontSize: 14, color: 'gray' }) } : undefined
250
250
  });
251
251
  }
252
252
  }
@@ -341,32 +341,28 @@ const ImageViewer = ({ fileBlob, alt = 'Image', className }) => {
341
341
  const doc = iframe.contentWindow?.document;
342
342
  if (!doc)
343
343
  return;
344
- doc.open();
345
- doc.write(`
346
- <html>
347
- <head>
348
- <title>Print Image</title>
349
- <style>
350
- body, html {
351
- margin: 0;
352
- padding: 0;
353
- height: 100%;
354
- display: flex;
355
- justify-content: center;
356
- align-items: center;
357
- }
358
- img {
359
- max-width: 100%;
360
- max-height: 100%;
361
- }
362
- </style>
363
- </head>
364
- <body>
365
- <img src="${dataUrl}" onload="setTimeout(() => { window.print(); window.close(); }, 100);" />
366
- </body>
367
- </html>
368
- `);
369
- doc.close();
344
+ doc.documentElement.innerHTML = `
345
+ <head>
346
+ <title>Print Image</title>
347
+ <style>
348
+ body, html {
349
+ margin: 0;
350
+ padding: 0;
351
+ height: 100%;
352
+ display: flex;
353
+ justify-content: center;
354
+ align-items: center;
355
+ }
356
+ img {
357
+ max-width: 100%;
358
+ max-height: 100%;
359
+ }
360
+ </style>
361
+ </head>
362
+ <body>
363
+ <img src="${dataUrl}" onload="setTimeout(() => { window.print(); window.close(); }, 100);" />
364
+ </body>
365
+ `;
370
366
  iframe.contentWindow?.addEventListener('afterprint', () => {
371
367
  document.body.removeChild(iframe);
372
368
  });
@@ -44,6 +44,7 @@ import { getDcmtCicoStatus } from '../../../helper/checkinCheckoutManager';
44
44
  import TMViewHistoryDcmt from './TMViewHistoryDcmt';
45
45
  import TMBlogCommentForm from '../blog/TMBlogCommentForm';
46
46
  import { useCheckInOutOperations } from '../../../hooks/useCheckInOutOperations';
47
+ import { useFloatingBarPinnedItems } from '../../../hooks/useFloatingBarPinnedItems';
47
48
  import TMDcmtCheckoutInfoForm from './TMDcmtCheckoutInfoForm';
48
49
  import { useDataListItem } from '../../../hooks/useDataListItem';
49
50
  import { useDataUserIdItem } from '../../../hooks/useDataUserIdItem';
@@ -107,6 +108,7 @@ const TMSearchResult = ({ allTasks = [], getAllTasks, deleteTaskByIdsCallback, a
107
108
  const [customButton, setCustomButton] = useState();
108
109
  const deviceType = useDeviceType();
109
110
  const isMobile = deviceType === DeviceType.MOBILE;
111
+ const { pinnedItemIds, togglePin, setPinnedItemIds } = useFloatingBarPinnedItems();
110
112
  const selectedDocs = getSelectedDcmtsOrFocused(selectedItems, focusedItem);
111
113
  const allFieldSelectedDocs = useMemo(() => getAllFieldSelectedDcmtsOrFocused(selectedItems, focusedItem), [selectedItems, focusedItem]);
112
114
  // Disable the "Sign/Approve" button if:
@@ -594,14 +596,14 @@ const TMSearchResult = ({ allTasks = [], getAllTasks, deleteTaskByIdsCallback, a
594
596
  && !showTodoDcmtForm);
595
597
  }, [showToppyForApprove, showToppyDraggableHelpCenter, selectedDocs, showApprovePopup, showRejectPopup, showReAssignPopup, showMoreInfoPopup, editPdfForm, openS4TViewer, showTodoDcmtForm]);
596
598
  const floatingMenuItems = useMemo(() => {
597
- const baseMenuItems = getCommandsMenuItems(isMobile, fromDTD, allUsers, selectedItems, focusedItem, context, showFloatingBar, workingGroupContext, showSearch, setShowFloatingBar, openFormHandler, openSharedArchiveHandler, showSharedDcmtsHandler, downloadDcmtsAsync, runOperationAsync, onRefreshSearchAsync, refreshSelectionDataRowsAsync, onRefreshAfterAddDcmtToFavs, confirmFormat, openConfirmAttachmentsDialog, openTaskFormHandler, openDetailDcmtsFormHandler, openMasterDcmtsFormHandler, openBatchUpdateFormHandler, openExportForm, handleToggleSearch, handleSignApprove, openSignSettingsForm, handleCheckOutOperationCallback, handleCheckInOperationCallback, showCheckoutInformationFormCallback, showHistoryCallback, copyCheckoutPathToClipboardOperationCallback, openWGsCopyMoveForm, openCommentFormCallback, openEditPdf, openAddDocumentForm, passToArchiveCallback, archiveMasterDocuments, archiveDetailDocuments, currentTIDHasMasterRelations, currentTIDHasDetailRelations, canArchiveMasterRelation, canArchiveDetailRelation, pairManyToMany, hasManyToManyRelation);
599
+ const baseMenuItems = getCommandsMenuItems(isMobile, fromDTD, allUsers, selectedItems, focusedItem, context, showFloatingBar, workingGroupContext, showSearch, setShowFloatingBar, openFormHandler, openSharedArchiveHandler, showSharedDcmtsHandler, downloadDcmtsAsync, runOperationAsync, onRefreshSearchAsync, refreshSelectionDataRowsAsync, onRefreshAfterAddDcmtToFavs, confirmFormat, openConfirmAttachmentsDialog, openTaskFormHandler, openDetailDcmtsFormHandler, openMasterDcmtsFormHandler, openBatchUpdateFormHandler, openExportForm, handleToggleSearch, handleSignApprove, openSignSettingsForm, handleCheckOutOperationCallback, handleCheckInOperationCallback, showCheckoutInformationFormCallback, showHistoryCallback, copyCheckoutPathToClipboardOperationCallback, openWGsCopyMoveForm, openCommentFormCallback, openEditPdf, openAddDocumentForm, passToArchiveCallback, archiveMasterDocuments, archiveDetailDocuments, currentTIDHasMasterRelations, currentTIDHasDetailRelations, canArchiveMasterRelation, canArchiveDetailRelation, pairManyToMany, hasManyToManyRelation, pinnedItemIds, togglePin);
598
600
  const customButtons = customButtonMenuItems();
599
601
  return customButtons.name ? baseMenuItems.concat([customButtons]) : baseMenuItems;
600
602
  }, [
601
603
  isMobile, fromDTD, allUsers, selectedItems, focusedItem, context,
602
604
  showFloatingBar, workingGroupContext, showSearch, currentTIDHasMasterRelations,
603
605
  currentTIDHasDetailRelations, canArchiveMasterRelation, canArchiveDetailRelation,
604
- hasManyToManyRelation, customButtonsLayout
606
+ hasManyToManyRelation, customButtonsLayout, pinnedItemIds, togglePin
605
607
  ]);
606
608
  const searchResutlToolbar = _jsxs(_Fragment, { children: [(dcmtsReturned != dcmtsFound) && _jsx("p", { style: { textAlign: 'center', padding: '1px 4px', borderRadius: '3px', display: 'flex' }, children: `${dcmtsReturned}/${dcmtsFound} restituiti` }), context === SearchResultContext.FAVORITES_AND_RECENTS &&
607
609
  _jsx("div", { style: { display: 'flex', alignItems: 'center', gap: '5px' }, children: _jsx(TMButton, { btnStyle: 'icon', icon: _jsx(IconDelete, { color: 'white' }), caption: "Rimuovi da " + (selectedSearchResult?.category === "Favorites" ? '"Preferiti"' : '"Recenti"'), disabled: getSelectedDcmtsOrFocused(selectedItems, focusedItem).length <= 0, onClick: removeDcmtFromFavsOrRecents }) }), _jsx(TMButton, { btnStyle: 'icon', icon: _jsx(IconRefresh, { color: 'white' }), caption: SDKUI_Localizator.Refresh, onClick: onRefreshSearchAsync }), _jsx(TMContextMenu, { items: floatingMenuItems, trigger: "left", children: _jsx(IconMenuVertical, { color: 'white', cursor: 'pointer' }) })] });
@@ -612,7 +614,7 @@ const TMSearchResult = ({ allTasks = [], getAllTasks, deleteTaskByIdsCallback, a
612
614
  _jsx(TMLayoutItem, { children: _jsx(TMSearchResultSelector, { searchResults: currentSearchResults, disableAccordionIfSingleCategory: disableAccordionIfSingleCategory, selectedTID: selectedSearchResultTID, selectedSearchResult: selectedSearchResult, autoSelectFirst: !isMobile || currentSearchResults.length === 1, onSelectionChanged: onSearchResultSelectionChanged }) })
613
615
  :
614
616
  _jsx(_Fragment, {}), _jsx(TMLayoutItem, { children: _jsx(TMSearchResultGrid, { showSearch: showSearch, fromDTD: fromDTD, allUsers: allUsers, inputFocusedItem: focusedItem, inputSelectedItems: selectedItems, searchResult: searchResults.length > 1 ? selectedSearchResult : searchResults[0], lastUpdateSearchTime: lastUpdateSearchTime, openInOffice: openInOffice, onDblClick: () => openFormHandler(LayoutModes.Update), floatingMenuItems: floatingMenuItems, onSelectionChanged: (items) => { setSelectedItems(items); }, onVisibleItemChanged: setVisibleItems, onFocusedItemChanged: setFocusedItem, onDownloadDcmtsAsync: async (inputDcmts, downloadType, downloadMode, _y, confirmAttachments) => await downloadDcmtsAsync(inputDcmts, downloadType, downloadMode, onFileOpened, confirmAttachments), showExportForm: showExportForm, onCloseExportForm: onCloseExportForm }) })] }), allowFloatingBar && showFloatingBar && deviceType !== DeviceType.MOBILE &&
615
- _jsx(TMFloatingMenuBar, { containerRef: floatingBarContainerRef, contextMenuItems: floatingMenuItems, isConstrained: true, defaultPosition: { x: 1, y: 88 }, contextMenuDefaultPinnedIds: ['rel-det', 'rel-mst', 'dl'], defaultOrientation: 'horizontal' })] }), showApprovePopup && _jsx(WorkFlowApproveRejectPopUp, { deviceType: deviceType, onCompleted: onWFOperationCompleted, selectedItems: getSelectedDcmtsOrFocused(selectedItems, focusedItem), isReject: 0, onClose: () => setShowApprovePopup(false) }), showRejectPopup && _jsx(WorkFlowApproveRejectPopUp, { deviceType: deviceType, onCompleted: onWFOperationCompleted, selectedItems: getSelectedDcmtsOrFocused(selectedItems, focusedItem), isReject: 1, onClose: () => setShowRejectPopup(false) }), showReAssignPopup && _jsx(WorkFlowReAssignPopUp, { deviceType: deviceType, onCompleted: onWFOperationCompleted, selectedItems: getSelectedDcmtsOrFocused(selectedItems, focusedItem), onClose: () => setShowReAssignPopup(false) }), showMoreInfoPopup && _jsx(WorkFlowMoreInfoPopUp, { TID: focusedItem?.TID, DID: focusedItem?.DID, deviceType: deviceType, onCompleted: onWFOperationCompleted, onClose: () => setShowMoreInfoPopup(false) }), isOpenBatchUpdate && _jsx(TMBatchUpdateForm, { isModal: true, titleModal: `${SDKUI_Localizator.BatchUpdate} (${getSelectionDcmtInfo().length} documenti selezionati)`, inputDcmts: getSelectionDcmtInfo(), TID: focusedItem ? focusedItem?.TID : selectedItems[0]?.TID, DID: focusedItem ? focusedItem?.DID : selectedItems[0]?.DID, onBack: () => {
617
+ _jsx(TMFloatingMenuBar, { containerRef: floatingBarContainerRef, contextMenuItems: floatingMenuItems, isConstrained: true, defaultPosition: { x: 1, y: 88 }, contextMenuDefaultPinnedIds: ['rel-det', 'rel-mst', 'dl'], defaultOrientation: 'horizontal', hasContextMenu: false, pinnedItemIds: pinnedItemIds, onPinChange: setPinnedItemIds })] }), showApprovePopup && _jsx(WorkFlowApproveRejectPopUp, { deviceType: deviceType, onCompleted: onWFOperationCompleted, selectedItems: getSelectedDcmtsOrFocused(selectedItems, focusedItem), isReject: 0, onClose: () => setShowApprovePopup(false) }), showRejectPopup && _jsx(WorkFlowApproveRejectPopUp, { deviceType: deviceType, onCompleted: onWFOperationCompleted, selectedItems: getSelectedDcmtsOrFocused(selectedItems, focusedItem), isReject: 1, onClose: () => setShowRejectPopup(false) }), showReAssignPopup && _jsx(WorkFlowReAssignPopUp, { deviceType: deviceType, onCompleted: onWFOperationCompleted, selectedItems: getSelectedDcmtsOrFocused(selectedItems, focusedItem), onClose: () => setShowReAssignPopup(false) }), showMoreInfoPopup && _jsx(WorkFlowMoreInfoPopUp, { TID: focusedItem?.TID, DID: focusedItem?.DID, deviceType: deviceType, onCompleted: onWFOperationCompleted, onClose: () => setShowMoreInfoPopup(false) }), isOpenBatchUpdate && _jsx(TMBatchUpdateForm, { isModal: true, titleModal: `${SDKUI_Localizator.BatchUpdate} (${getSelectionDcmtInfo().length} documenti selezionati)`, inputDcmts: getSelectionDcmtInfo(), TID: focusedItem ? focusedItem?.TID : selectedItems[0]?.TID, DID: focusedItem ? focusedItem?.DID : selectedItems[0]?.DID, onBack: () => {
616
618
  setIsOpenBatchUpdate(false);
617
619
  }, onSavedCallbackAsync: async () => {
618
620
  setIsOpenBatchUpdate(false);
@@ -8,4 +8,4 @@ export declare const signatureInformationCallback: (isMobile: boolean, inputDcmt
8
8
  export declare const getCommandsMenuItems: (isMobile: boolean, dtd: DcmtTypeDescriptor | undefined, allUsers: Array<UserDescriptor>, selectedItems: Array<any>, focusedItem: any, context: SearchResultContext, showFloatingBar: boolean, workingGroupContext: WorkingGroupDescriptor | undefined, showSearch: boolean, setShowFloatingBar: React.Dispatch<React.SetStateAction<boolean>>, openFormHandler: (layoutMode: LayoutModes) => void, openSharedArchiveHandler: () => Promise<void>, showSharedDcmtsHandler: () => Promise<void>, downloadDcmtsAsync: (inputDcmts: DcmtInfo[] | undefined, downloadType: DownloadTypes, downloadMode: DownloadModes, onFileDownloaded?: (dcmtFile: File | undefined) => void, confirmAttachments?: (list: FileDescriptor[]) => Promise<string[] | undefined>, skipConfirmation?: boolean) => Promise<void>, runOperationAsync: (inputDcmts: DcmtInfo[] | undefined, dcmtOperationType: DcmtOperationTypes, actionAfterOperationAsync?: () => Promise<void>) => Promise<void>, onRefreshSearchAsync: (() => Promise<void>) | undefined, onRefreshDataRowsAsync: (() => Promise<void>) | undefined, onRefreshAfterAddDcmtToFavs: (() => void) | undefined, confirmFormat: () => Promise<FileFormats>, confirmAttachments: (list: FileDescriptor[]) => Promise<string[] | undefined>, openTaskFormHandler: () => void, openDetailDcmtsFormHandler: (value: boolean) => void, openMasterDcmtsFormHandler: (value: boolean) => void, openBatchUpdateFormHandler: (value: boolean) => void, openExportForm: () => void, handleToggleSearch: () => void, handleSignApprove: () => void, openSignSettingsForm: () => void, handleCheckOutOperationCallback: (checkout: boolean) => Promise<void>, handleCheckInOperationCallback: () => void, showCheckoutInformationFormCallback: () => void, viewHistoryCallback: () => void, copyCheckoutPathToClipboardOperationCallback: () => void, openWGsCopyMoveForm?: ((mode: "copyToWgDraft" | "copyToWgArchivedDoc", dcmtTypeDescriptor: DcmtTypeDescriptor, documents: Array<DcmtInfo>) => void), openCommentFormCallback?: ((documents: Array<DcmtInfo>) => void), openEditPdf?: ((documents: Array<DcmtInfo>) => void), openAddDocumentForm?: () => void, passToArchiveCallback?: (outputMids: Array<{
9
9
  mid: number;
10
10
  value: string;
11
- }>, tid?: number) => void, archiveMasterDocuments?: (tid: number | undefined) => Promise<void>, archiveDetailDocuments?: (tid: number | undefined) => Promise<void>, hasMasterRelation?: boolean, hasDetailRelation?: boolean, canArchiveMasterRelation?: boolean, canArchiveDetailRelation?: boolean, pairManyToManyDocuments?: (isPairing: boolean) => Promise<void>, hasManyToManyRelation?: boolean) => Array<TMContextMenuItemProps>;
11
+ }>, tid?: number) => void, archiveMasterDocuments?: (tid: number | undefined) => Promise<void>, archiveDetailDocuments?: (tid: number | undefined) => Promise<void>, hasMasterRelation?: boolean, hasDetailRelation?: boolean, canArchiveMasterRelation?: boolean, canArchiveDetailRelation?: boolean, pairManyToManyDocuments?: (isPairing: boolean) => Promise<void>, hasManyToManyRelation?: boolean, pinnedItemIds?: string[], onTogglePin?: (id: string) => void) => Array<TMContextMenuItemProps>;
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { AccessLevels, AccessLevelsEx, AppModules, FileFormats, LayoutModes, SDK_Globals, DcmtTypeListCacheService, LicenseModuleStatus } from '@topconsultnpm/sdk-ts';
3
- import { IconActivity, IconArchiveDoc, IconBatchUpdate, IconCheckFile, IconCheckIn, IconCircleInfo, IconCloseCircle, IconConvertFilePdf, IconDelete, IconDotsVerticalCircleOutline, IconDownload, IconEdit, IconExportTo, IconFileDots, IconHide, IconInfo, IconMenuCAArchive, IconPlatform, IconPreview, IconRelation, IconSearch, IconShow, IconStar, IconSubstFile, IconUndo, IconUserGroupOutline, SDKUI_Localizator, searchResultToMetadataValues, IconSignaturePencil, IconArchiveMaster, IconArchiveDetail, IconDetailDcmts, isPdfEditorEnabled, IconPair, IconUnpair, IconSharedDcmt, IconShare, IconCopy, IconMoveToFolder } from '../../../helper';
3
+ import { IconActivity, IconArchiveDoc, IconBatchUpdate, IconCheckFile, IconCheckIn, IconCircleInfo, IconCloseCircle, IconConvertFilePdf, IconDelete, IconDotsVerticalCircleOutline, IconDownload, IconEdit, IconExportTo, IconFileDots, IconHide, IconInfo, IconMenuCAArchive, IconPlatform, IconPreview, IconRelation, IconSearch, IconShow, IconStar, IconSubstFile, IconUndo, IconUserGroupOutline, SDKUI_Localizator, searchResultToMetadataValues, IconSignaturePencil, IconArchiveMaster, IconArchiveDetail, IconDetailDcmts, isPdfEditorEnabled, IconPair, IconUnpair, IconSharedDcmt, IconShare, IconCopy, IconMoveToFolder, IconPin } from '../../../helper';
4
4
  import ShowAlert from '../../base/TMAlert';
5
5
  import { TMMessageBoxManager, ButtonNames, TMExceptionBoxManager } from '../../base/TMPopUp';
6
6
  import TMSpinner from '../../base/TMSpinner';
@@ -80,8 +80,28 @@ export const signatureInformationCallback = async (isMobile, inputDcmts) => {
80
80
  TMSpinner.hide();
81
81
  }
82
82
  };
83
- export const getCommandsMenuItems = (isMobile, dtd, allUsers, selectedItems, focusedItem, context, showFloatingBar, workingGroupContext, showSearch, setShowFloatingBar, openFormHandler, openSharedArchiveHandler, showSharedDcmtsHandler, downloadDcmtsAsync, runOperationAsync, onRefreshSearchAsync, onRefreshDataRowsAsync, onRefreshAfterAddDcmtToFavs, confirmFormat, confirmAttachments, openTaskFormHandler, openDetailDcmtsFormHandler, openMasterDcmtsFormHandler, openBatchUpdateFormHandler, openExportForm, handleToggleSearch, handleSignApprove, openSignSettingsForm, handleCheckOutOperationCallback, handleCheckInOperationCallback, showCheckoutInformationFormCallback, viewHistoryCallback, copyCheckoutPathToClipboardOperationCallback, openWGsCopyMoveForm, openCommentFormCallback, openEditPdf, openAddDocumentForm, passToArchiveCallback, archiveMasterDocuments, archiveDetailDocuments, hasMasterRelation, hasDetailRelation, canArchiveMasterRelation, canArchiveDetailRelation, pairManyToManyDocuments, hasManyToManyRelation) => {
83
+ export const getCommandsMenuItems = (isMobile, dtd, allUsers, selectedItems, focusedItem, context, showFloatingBar, workingGroupContext, showSearch, setShowFloatingBar, openFormHandler, openSharedArchiveHandler, showSharedDcmtsHandler, downloadDcmtsAsync, runOperationAsync, onRefreshSearchAsync, onRefreshDataRowsAsync, onRefreshAfterAddDcmtToFavs, confirmFormat, confirmAttachments, openTaskFormHandler, openDetailDcmtsFormHandler, openMasterDcmtsFormHandler, openBatchUpdateFormHandler, openExportForm, handleToggleSearch, handleSignApprove, openSignSettingsForm, handleCheckOutOperationCallback, handleCheckInOperationCallback, showCheckoutInformationFormCallback, viewHistoryCallback, copyCheckoutPathToClipboardOperationCallback, openWGsCopyMoveForm, openCommentFormCallback, openEditPdf, openAddDocumentForm, passToArchiveCallback, archiveMasterDocuments, archiveDetailDocuments, hasMasterRelation, hasDetailRelation, canArchiveMasterRelation, canArchiveDetailRelation, pairManyToManyDocuments, hasManyToManyRelation, pinnedItemIds, onTogglePin) => {
84
84
  const isPdfEditorLicensed = SDK_Globals?.license?.dcmtArchiveLicenses?.[0]?.siX_60007?.status === LicenseModuleStatus.Licensed;
85
+ const addPinIconToItems = (items) => {
86
+ if (isMobile || !onTogglePin)
87
+ return items;
88
+ return items.map(item => {
89
+ const newItem = { ...item };
90
+ if (item.id && item.onClick && !item.submenu) {
91
+ newItem.rightIconProps = {
92
+ icon: _jsx(IconPin, {}),
93
+ activeColor: 'red',
94
+ inactiveColor: 'black',
95
+ isActive: pinnedItemIds?.includes(item.id) ?? false,
96
+ onClick: () => onTogglePin(item.id),
97
+ };
98
+ }
99
+ if (item.submenu && item.submenu.length > 0) {
100
+ newItem.submenu = addPinIconToItems(item.submenu);
101
+ }
102
+ return newItem;
103
+ });
104
+ };
85
105
  let pdfEditorAvailable = false;
86
106
  if (dtd && dtd.widgets && dtd.widgets.length > 0) {
87
107
  pdfEditorAvailable = isPdfEditorEnabled(dtd.widgets);
@@ -744,7 +764,7 @@ export const getCommandsMenuItems = (isMobile, dtd, allUsers, selectedItems, foc
744
764
  ];
745
765
  };
746
766
  if (context === SearchResultContext.ARCHIVED_WORKGROUP) {
747
- return getArchivedWorkgroupMenuItems();
767
+ return addPinIconToItems(getArchivedWorkgroupMenuItems());
748
768
  }
749
- return getDefaultMenuItems();
769
+ return addPinIconToItems(getDefaultMenuItems());
750
770
  };
@@ -264,7 +264,7 @@ const TMLoginForm = (props) => {
264
264
  { value: AuthenticationModes.TopMediaWithMFA, display: LOGINLocalizator.TopMediaWithMFA },
265
265
  { value: AuthenticationModes.TopMediaOnBehalfOf, display: SDKUI_Localizator.AuthMode_OnBehalfOf },
266
266
  { value: AuthenticationModes.WindowsThroughTopMedia, display: SDKUI_Localizator.AuthMode_WindowsViaTopMedia },
267
- { value: AuthenticationModes.MSAzure, display: 'MSAzure' }];
267
+ { value: AuthenticationModes.MSAzure, display: 'Microsoft Entra ID' }];
268
268
  }, [props.cultureID]);
269
269
  const accessPointAdditionalIcons = useMemo(() => {
270
270
  return [
@@ -67,7 +67,8 @@ export declare enum LandingPages {
67
67
  RECENT = "recent",
68
68
  AREA_MANAGER = "areaManager",
69
69
  NOTIFICATIONS = "notifications",
70
- ACTIVITIES = "activities"
70
+ ACTIVITIES = "activities",
71
+ WORKFLOW_CTRL = "workflowCtrl"
71
72
  }
72
73
  export declare function getExceptionMessage(ex: any): string;
73
74
  export declare function getContrastColor(inputColor: string): {
@@ -728,6 +728,7 @@ export var LandingPages;
728
728
  LandingPages["AREA_MANAGER"] = "areaManager";
729
729
  LandingPages["NOTIFICATIONS"] = "notifications";
730
730
  LandingPages["ACTIVITIES"] = "activities";
731
+ LandingPages["WORKFLOW_CTRL"] = "workflowCtrl";
731
732
  })(LandingPages || (LandingPages = {}));
732
733
  // #endregion
733
734
  // #region Exception
@@ -0,0 +1,11 @@
1
+ interface UseFloatingBarPinnedItemsOptions {
2
+ defaultPinnedIds?: string[];
3
+ }
4
+ interface UseFloatingBarPinnedItemsReturn {
5
+ pinnedItemIds: string[];
6
+ isPinned: (id: string) => boolean;
7
+ togglePin: (id: string) => void;
8
+ setPinnedItemIds: (ids: string[]) => void;
9
+ }
10
+ export declare const useFloatingBarPinnedItems: (options?: UseFloatingBarPinnedItemsOptions) => UseFloatingBarPinnedItemsReturn;
11
+ export default useFloatingBarPinnedItems;
@@ -0,0 +1,54 @@
1
+ import { useState, useCallback } from 'react';
2
+ import { SDKUI_Globals } from '../helper';
3
+ export const useFloatingBarPinnedItems = (options = {}) => {
4
+ const { defaultPinnedIds = [] } = options;
5
+ const loadPinnedIds = useCallback(() => {
6
+ try {
7
+ const settings = SDKUI_Globals.userSettings?.searchSettings?.floatingMenuBar || {};
8
+ if (settings.itemIds && Array.isArray(settings.itemIds) && settings.itemIds.length > 0) {
9
+ return settings.itemIds;
10
+ }
11
+ return defaultPinnedIds;
12
+ }
13
+ catch (error) {
14
+ console.error('Failed to load pinned items from localStorage:', error);
15
+ return defaultPinnedIds;
16
+ }
17
+ }, [defaultPinnedIds]);
18
+ const [internalPinnedItemIds, setInternalPinnedItemIds] = useState(loadPinnedIds);
19
+ const savePinnedIds = useCallback((ids) => {
20
+ try {
21
+ const currentSettings = SDKUI_Globals.userSettings?.searchSettings?.floatingMenuBar || {};
22
+ SDKUI_Globals.userSettings.searchSettings.floatingMenuBar = {
23
+ ...currentSettings,
24
+ itemIds: ids,
25
+ };
26
+ }
27
+ catch (error) {
28
+ console.error('Failed to save pinned items to localStorage:', error);
29
+ }
30
+ }, []);
31
+ const setPinnedItemIds = useCallback((ids) => {
32
+ setInternalPinnedItemIds(ids);
33
+ savePinnedIds(ids);
34
+ }, [savePinnedIds]);
35
+ const isPinned = useCallback((id) => {
36
+ return internalPinnedItemIds.includes(id);
37
+ }, [internalPinnedItemIds]);
38
+ const togglePin = useCallback((id) => {
39
+ setInternalPinnedItemIds(prev => {
40
+ const newIds = prev.includes(id)
41
+ ? prev.filter(pinnedId => pinnedId !== id)
42
+ : [...prev, id];
43
+ savePinnedIds(newIds);
44
+ return newIds;
45
+ });
46
+ }, [savePinnedIds]);
47
+ return {
48
+ pinnedItemIds: internalPinnedItemIds,
49
+ isPinned,
50
+ togglePin,
51
+ setPinnedItemIds,
52
+ };
53
+ };
54
+ export default useFloatingBarPinnedItems;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@topconsultnpm/sdkui-react",
3
- "version": "6.20.0-dev1.113",
3
+ "version": "6.20.0-dev1.115",
4
4
  "description": "",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1",