@topconsultnpm/sdkui-react-beta 6.14.28 → 6.14.30

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.
@@ -3,7 +3,7 @@ import { Colors, ITMEditorBase } from './TMEditorBase';
3
3
  interface ITMDropDown extends ITMEditorBase {
4
4
  dataSource?: any[];
5
5
  color?: Colors;
6
- value?: string | number | undefined;
6
+ value?: string | number;
7
7
  disabled?: boolean;
8
8
  onValueChanged?: (e: React.ChangeEvent<HTMLSelectElement>) => void;
9
9
  }
@@ -1,4 +1,11 @@
1
+ /** Gestione TMPanel "attivo / non attivo" secondo una pattern ibrido (Controlled/Uncontrolled Component):
2
+ 1) Modalità Non Controllata (Uncontrolled): Se non gli viene passata la prop "isActive", il pannello gestisce lo stato "attivo" internamente. Per l'uso singolo ("plug-and-play").
3
+ 2) Modalità Controllata (Controlled): Se il genitore passa la prop "isActive", il pannello cede il controllo e si affida completamente al genitore. Ideale per layout con più pannelli che necessitano di coordinamento.
4
+ */
1
5
  import React from 'react';
6
+ export interface ITMPanelRef {
7
+ focusPanel: () => void;
8
+ }
2
9
  export interface ITMPanelProps {
3
10
  allowMaximize?: boolean;
4
11
  color?: string;
@@ -12,11 +19,14 @@ export interface ITMPanelProps {
12
19
  toolbar?: any;
13
20
  padding?: string;
14
21
  isVisible?: boolean;
22
+ panelID?: string;
23
+ isActive?: boolean;
24
+ onActivate?: (panelID: string) => void;
25
+ onActiveChanged?: (isActive: boolean) => void;
15
26
  onBack?: () => void;
16
27
  onClose?: () => void;
17
28
  onHeaderDoubleClick?: () => void;
18
29
  onMaximize?: (isMaximized: boolean) => void;
19
- onActiveChanged?: (isActive: boolean) => void;
20
30
  }
21
- declare const TMPanel: React.FC<ITMPanelProps>;
31
+ declare const TMPanel: React.ForwardRefExoticComponent<ITMPanelProps & React.RefAttributes<ITMPanelRef>>;
22
32
  export default TMPanel;
@@ -1,5 +1,9 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useCallback, useRef, useState } from 'react';
2
+ /** Gestione TMPanel "attivo / non attivo" secondo una pattern ibrido (Controlled/Uncontrolled Component):
3
+ 1) Modalità Non Controllata (Uncontrolled): Se non gli viene passata la prop "isActive", il pannello gestisce lo stato "attivo" internamente. Per l'uso singolo ("plug-and-play").
4
+ 2) Modalità Controllata (Controlled): Se il genitore passa la prop "isActive", il pannello cede il controllo e si affida completamente al genitore. Ideale per layout con più pannelli che necessitano di coordinamento.
5
+ */
6
+ import { useCallback, useRef, useState, forwardRef, useImperativeHandle } from 'react';
3
7
  import styled from 'styled-components';
4
8
  import { IconArrowLeft, IconClearButton, IconWindowMaximize, IconWindowMinimize, isPositiveNumber, SDKUI_Globals, SDKUI_Localizator } from '../../helper';
5
9
  import TMButton from './TMButton';
@@ -65,46 +69,57 @@ const StyledPanelContent = styled.div `
65
69
  outline: none;
66
70
  }
67
71
  `;
68
- const TMPanel = ({ allowMaximize = true, color, backgroundColor, backgroundColorContainer, children, showHeader = true, title, totalItems, displayedItemsCount, toolbar, padding = '5px', isVisible = true, onBack, onClose, onHeaderDoubleClick, onMaximize, onActiveChanged }) => {
69
- const [isPanelActive, setIsPanelActive] = useState(false);
72
+ const TMPanel = forwardRef(({ allowMaximize = true, color, backgroundColor, backgroundColorContainer, children, showHeader = true, title, totalItems, displayedItemsCount, toolbar, padding = '5px', isVisible = true, panelID = 'tmpanel', isActive, // Questa prop determinerà la modalità
73
+ onActivate, onBack, onClose, onHeaderDoubleClick, onMaximize, onActiveChanged }, ref) => {
70
74
  const [isMaximized, setIsMaximized] = useState(false);
75
+ // INTERNAL STATUS for UNCONTROLLED mode
76
+ const [internalIsActive, setInternalIsActive] = useState(false);
71
77
  const panelRef = useRef(null);
72
- const pendingFocusCheck = useRef(null); // Per un timer
73
- const setActiveStatus = useCallback((isActive) => {
74
- if (isPanelActive !== isActive) { // Solo se lo stato cambia
75
- setIsPanelActive(isActive);
76
- onActiveChanged?.(isActive);
77
- }
78
- }, [isPanelActive, onActiveChanged]);
79
- const handleFocus = useCallback(() => {
80
- // Se c'è un check di pending blur, annullalo
78
+ // If `isActive` prop is passed (it is not undefined), the component is checked.
79
+ const isControlled = isActive !== undefined;
80
+ // Use parent state if checked, otherwise use internal state.
81
+ const currentIsActive = isControlled ? isActive : internalIsActive;
82
+ // Focus/blur management logic for UNCONTROLLED mode
83
+ const pendingFocusCheck = useRef(null);
84
+ // Espone i metodi imperativi tramite useImperativeHandle
85
+ useImperativeHandle(ref, () => ({
86
+ focusPanel: () => {
87
+ // Imposta il focus sul div StyledPanelContent
88
+ panelRef.current?.focus();
89
+ },
90
+ }));
91
+ const handleFocusUncontrolled = useCallback(() => {
81
92
  if (pendingFocusCheck.current) {
82
93
  clearTimeout(pendingFocusCheck.current);
83
94
  pendingFocusCheck.current = null;
84
95
  }
85
- setActiveStatus(true);
86
- }, [setActiveStatus]);
87
- const handleBlur = useCallback((event) => {
88
- // Pulisci qualsiasi check precedente (importante se ci sono blur rapidi)
96
+ if (!internalIsActive) {
97
+ setInternalIsActive(true);
98
+ onActiveChanged?.(true);
99
+ }
100
+ }, [internalIsActive, onActiveChanged]);
101
+ const handleBlurUncontrolled = useCallback(() => {
89
102
  if (pendingFocusCheck.current) {
90
103
  clearTimeout(pendingFocusCheck.current);
91
104
  }
92
- // Programma un controllo dopo un brevissimo ritardo
93
- // Questo permette al browser e a DevExtreme di stabilizzare il focus
94
105
  pendingFocusCheck.current = setTimeout(() => {
95
- const currentActiveElement = document.activeElement;
96
- // Se l'elemento attivo corrente è all'interno del pannello,
97
- // non fare nulla (il focus è interno)
98
- if (panelRef.current?.contains(currentActiveElement)) {
99
- setActiveStatus(true); // Assicurati che sia attivo
100
- }
101
- else {
102
- // Il focus è realmente uscito
103
- setActiveStatus(false);
106
+ if (panelRef.current && !panelRef.current.contains(document.activeElement)) {
107
+ setInternalIsActive(false);
108
+ onActiveChanged?.(false);
104
109
  }
105
- pendingFocusCheck.current = null; // Resetta il timer
106
- }, 50); // Piccolo ritardo, 50ms è un buon punto di partenza
107
- }, [setActiveStatus]);
110
+ }, 50);
111
+ }, [onActiveChanged]);
112
+ const handleActivation = () => {
113
+ if (isControlled) {
114
+ // In modalità controllata, notifica il genitore.
115
+ // Il genitore sarà responsabile di aggiornare la prop `isActive`.
116
+ onActivate?.(panelID);
117
+ }
118
+ else {
119
+ // In modalità non controllata, gestisci internamente.
120
+ handleFocusUncontrolled();
121
+ }
122
+ };
108
123
  // handler for external maximize management
109
124
  const handleMaximize = () => {
110
125
  setIsMaximized(prevState => {
@@ -115,10 +130,10 @@ const TMPanel = ({ allowMaximize = true, color, backgroundColor, backgroundColor
115
130
  return newValue;
116
131
  });
117
132
  };
118
- return (_jsxs(StyledPanelContainer, { className: "tmpanel-container", ref: panelRef, "$isMaximized": onMaximize ? false : isMaximized, style: {
133
+ return (_jsxs(StyledPanelContainer, { ref: panelRef, "$isMaximized": onMaximize ? false : isMaximized, style: {
119
134
  visibility: isVisible ? 'visible' : 'hidden',
120
- }, tabIndex: -1, onFocus: handleFocus, onBlur: handleBlur, onClick: handleFocus, children: [showHeader &&
121
- _jsx(StyledPanelHeader, { "$backgroundColor": backgroundColor, "$color": color, "$isActive": isPanelActive, onDoubleClick: () => {
135
+ }, tabIndex: -1, onFocus: !isControlled ? handleFocusUncontrolled : undefined, onBlur: !isControlled ? handleBlurUncontrolled : undefined, onClick: handleActivation, children: [showHeader &&
136
+ _jsx(StyledPanelHeader, { "$backgroundColor": backgroundColor, "$color": color, "$isActive": currentIsActive, onDoubleClick: () => {
122
137
  if (onHeaderDoubleClick)
123
138
  onHeaderDoubleClick();
124
139
  else
@@ -161,5 +176,5 @@ const TMPanel = ({ allowMaximize = true, color, backgroundColor, backgroundColor
161
176
  }, children: [toolbar, allowMaximize && _jsx(TMButton, { color: 'primaryOutline', caption: isMaximized ? SDKUI_Localizator.Minimize : SDKUI_Localizator.Maximize, icon: isMaximized
162
177
  ? _jsx(IconWindowMinimize, { fontSize: 16 })
163
178
  : _jsx(IconWindowMaximize, { fontSize: 16 }), btnStyle: 'icon', onClick: handleMaximize }), onClose && _jsx(TMButton, { color: 'primaryOutline', caption: SDKUI_Localizator.Close, icon: _jsx(IconClearButton, {}), btnStyle: 'icon', onClick: () => { setIsMaximized(false); onClose?.(); } })] })] }) }), _jsx(StyledPanelContent, { "$height": showHeader ? "calc(100% - 40px)" : "100%", "$padding": padding, "$backgroundColor": backgroundColorContainer ?? `#FFFFFF`, children: children })] }));
164
- };
179
+ });
165
180
  export default TMPanel;
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useEffect, useRef, useState } from 'react';
2
+ import { useCallback, useEffect, useRef, useState } from 'react';
3
3
  import styled from 'styled-components';
4
4
  import { IconFolderOpen, SDKUI_Localizator, formatBytes, IconClear, extensionHandler, IconCloseOutline } from '../../../helper';
5
5
  import usePreventFileDrop from '../../../hooks/usePreventFileDrop';
@@ -17,10 +17,12 @@ const TMFileUploader = ({ deviceType = DeviceType.DESKTOP, onClose, onFileUpload
17
17
  const [fileSize, setFileSize] = useState(undefined);
18
18
  const [fileExt, setFileExt] = useState(undefined);
19
19
  const uploaderRef = useRef(null);
20
+ // Ref al TMPanel per chiamare focusPanel()
21
+ const fileUploaderPanelRef = useRef(null);
20
22
  usePreventFileDrop([uploaderRef]);
21
23
  useEffect(() => {
22
24
  if (!defaultBlob) {
23
- clearFile();
25
+ clearFile(false);
24
26
  return;
25
27
  }
26
28
  ;
@@ -49,13 +51,20 @@ const TMFileUploader = ({ deviceType = DeviceType.DESKTOP, onClose, onFileUpload
49
51
  const file = e.dataTransfer.files[0];
50
52
  if (file)
51
53
  handleFile(file);
54
+ refocusAfterFileInput();
52
55
  };
53
- const handleInputChange = (e) => {
54
- const file = e.target.files?.[0];
56
+ // Funzione per rifocalizzare il pannello dopo la selezione del file
57
+ const refocusAfterFileInput = useCallback(() => {
58
+ fileUploaderPanelRef.current?.focusPanel();
59
+ }, []);
60
+ const handleInputChange = useCallback((event) => {
61
+ const file = event.target.files?.[0] || null;
55
62
  if (file)
56
63
  handleFile(file);
57
- };
58
- const clearFile = () => {
64
+ // Important: Rifocalizza dopo che il file input ha finito
65
+ refocusAfterFileInput();
66
+ }, [onFileUpload, refocusAfterFileInput]);
67
+ const clearFile = useCallback((refocus) => {
59
68
  setUploadedFile(null);
60
69
  setFileName('');
61
70
  setFileSize(undefined);
@@ -65,15 +74,18 @@ const TMFileUploader = ({ deviceType = DeviceType.DESKTOP, onClose, onFileUpload
65
74
  if (fileInput) {
66
75
  fileInput.value = '';
67
76
  }
68
- };
69
- const browseHandler = () => {
77
+ //Rifocalizza dopo la cancellazione del file
78
+ if (refocus)
79
+ refocusAfterFileInput();
80
+ }, [onFileUpload, refocusAfterFileInput]);
81
+ const browseHandler = useCallback(() => {
70
82
  document.getElementById('fileInput')?.click();
71
- };
83
+ }, []);
72
84
  let content = !uploadedFile ?
73
- _jsx("div", { style: { display: 'flex', gap: 10, width: '100%', height: '100%' }, children: _jsxs(UploadContainer, { ref: uploaderRef, onDragOver: handleDragOver, onDragLeave: handleDragLeave, onDrop: handleDrop, style: { backgroundColor: dragOver ? '#76b1e6' : 'white' }, onDoubleClick: browseHandler, "$isRequired": isRequired, children: [_jsx("div", { style: { display: 'flex', gap: '10px', flexDirection: 'column', position: 'absolute', right: 5, top: 5 }, children: _jsx(TMButton, { btnStyle: 'icon', caption: 'Sfoglia', color: isRequired && !uploadedFile ? 'error' : 'primary', onClick: browseHandler, icon: _jsx(IconFolderOpen, { fontSize: 22 }) }) }), _jsx("p", { style: { fontSize: '1.2rem', fontWeight: 'bold' }, children: deviceType === DeviceType.MOBILE ? 'Clicca per sfogliare il tuo file' : 'Trascina il tuo file qui o fai doppio click per sfogliarlo' }), isRequired && _jsxs("p", { style: { fontWeight: 'bold' }, children: [" ", SDKUI_Localizator.RequiredField, " "] })] }) }) :
74
- _jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 10, width: '100%', height: '100%' }, children: [_jsxs("div", { style: { backgroundColor: 'white', padding: '5px 10px', borderRadius: 8, display: 'flex', alignItems: 'center', justifyContent: 'space-between', color: TMColors.primaryColor }, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 5 }, children: [_jsx("p", { children: "File name:" }), _jsxs("div", { style: { fontWeight: 'bold' }, children: [fileName, " ", _jsxs("span", { children: [" ", ` (${formatBytes(fileSize)})`, " "] })] })] }), uploadedFile && _jsx(TMButton, { btnStyle: 'icon', color: 'error', caption: 'Pulisci', onClick: clearFile, icon: _jsx(IconClear, { fontSize: 22 }) })] }), extensionHandler(fileExt) === FileExtensionHandler.READY_TO_SHOW ? _jsx(TMFileViewer, { fileBlob: uploadedFile, isResizingActive: isResizingActive }) :
85
+ _jsx("div", { style: { display: 'flex', gap: 10, width: '100%', height: '100%' }, children: _jsxs(UploadContainer, { ref: uploaderRef, tabIndex: 0, onDragOver: handleDragOver, onDragLeave: handleDragLeave, onDrop: handleDrop, style: { backgroundColor: dragOver ? '#76b1e6' : 'white' }, onDoubleClick: browseHandler, "$isRequired": isRequired, children: [_jsx("div", { style: { display: 'flex', gap: '10px', flexDirection: 'column', position: 'absolute', right: 5, top: 5 }, children: _jsx(TMButton, { btnStyle: 'icon', caption: 'Sfoglia', color: isRequired && !uploadedFile ? 'error' : 'primary', onClick: browseHandler, icon: _jsx(IconFolderOpen, { fontSize: 22 }) }) }), _jsx("p", { style: { fontSize: '1.2rem', fontWeight: 'bold' }, children: deviceType === DeviceType.MOBILE ? 'Clicca per sfogliare il tuo file' : 'Trascina il tuo file qui o fai doppio click per sfogliarlo' }), isRequired && _jsxs("p", { style: { fontWeight: 'bold' }, children: [" ", SDKUI_Localizator.RequiredField, " "] })] }) }) :
86
+ _jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 10, width: '100%', height: '100%' }, children: [_jsxs("div", { style: { backgroundColor: 'white', padding: '5px 10px', borderRadius: 8, display: 'flex', alignItems: 'center', justifyContent: 'space-between', color: TMColors.primaryColor }, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 5 }, children: [_jsx("p", { children: "File name:" }), _jsxs("div", { style: { fontWeight: 'bold' }, children: [fileName, " ", _jsxs("span", { children: [" ", ` (${formatBytes(fileSize)})`, " "] })] })] }), uploadedFile && _jsx(TMButton, { btnStyle: 'icon', color: 'error', caption: 'Pulisci', onClick: () => clearFile(true), icon: _jsx(IconClear, { fontSize: 22 }) })] }), extensionHandler(fileExt) === FileExtensionHandler.READY_TO_SHOW ? _jsx(TMFileViewer, { fileBlob: uploadedFile, isResizingActive: isResizingActive }) :
75
87
  _jsx("div", { style: { backgroundColor: '#f6dbdb', padding: '5px 10px', borderRadius: 8, display: 'flex', alignItems: 'center', justifyContent: 'space-between', color: TMColors.error }, children: _jsxs("div", { children: [" ", 'Anteprima non disponibile.', fileExt && _jsx("b", { children: ` (*.${fileExt})` })] }) })] });
76
- return (_jsx(TMPanel, { title: SDKUI_Localizator.FileUpload, onBack: deviceType === DeviceType.MOBILE ? () => onClose?.() : undefined, toolbar: deviceType !== DeviceType.MOBILE ? _jsx(StyledHeaderIcon, { onClick: onClose, "$color": 'white', children: _jsx(TMTooltip, { content: SDKUI_Localizator.Close, children: _jsx(IconCloseOutline, {}) }) }) : undefined, children: _jsxs("div", { style: { width: '100%', height: '100%', padding: '2px', display: 'flex', flexDirection: 'column', gap: 10 }, children: [_jsx(HiddenInput, { id: "fileInput", type: "file", onChange: handleInputChange }), content] }) }));
88
+ return (_jsx(TMPanel, { ref: fileUploaderPanelRef, panelID: 'file-uploader-panel', title: SDKUI_Localizator.FileUpload, onBack: deviceType === DeviceType.MOBILE ? () => onClose?.() : undefined, toolbar: deviceType !== DeviceType.MOBILE ? _jsx(StyledHeaderIcon, { onClick: onClose, "$color": 'white', children: _jsx(TMTooltip, { content: SDKUI_Localizator.Close, children: _jsx(IconCloseOutline, {}) }) }) : undefined, children: _jsxs("div", { style: { width: '100%', height: '100%', padding: '2px', display: 'flex', flexDirection: 'column', gap: 10 }, children: [_jsx(HiddenInput, { id: "fileInput", type: "file", onChange: handleInputChange }), content] }) }));
77
89
  };
78
90
  const UploadContainer = styled.div `
79
91
  position: relative;
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useState } from "react";
3
- import { SDK_Globals } from '@topconsultnpm/sdk-ts-beta';
3
+ import { SDK_Globals, UserListCacheService } from '@topconsultnpm/sdk-ts-beta';
4
4
  import styled from "styled-components";
5
5
  import { SDKUI_Localizator, IconApply, IconCloseOutline, IconUser, IconInfo } from "../../../helper";
6
6
  import { TMColors } from "../../../utils/theme";
@@ -92,12 +92,16 @@ export const WorkFlowReAssignPopUp = ({ DID = 0, TID = 0, deviceType = DeviceTyp
92
92
  TMSpinner.hide();
93
93
  }
94
94
  };
95
- // useEffect(() => {
96
- // SDK_Globals.tmSession?.NewWorkflowEngine().RetrieveAllAsync().then((o) => console.log('WFs', o));
97
- // SDK_Globals.tmSession?.NewWorkflowEngine().RetrieveAsync(11283).then((o) => console.log('WF', o));
98
- // }, [])
99
- useEffect(() => { }, []);
100
- return (_jsx(TMModal, { toolbar: _jsx(TMButton, { btnStyle: 'advanced', showTooltip: false, icon: _jsx(IconUser, { fontSize: 16 }), caption: 'Riassegna', disabled: disable, onClick: () => !disable && reAssignWorkFlowAsync(), advancedColor: TMColors.tertiary }), onClose: onClose, width: deviceType === DeviceType.MOBILE ? '95%' : '60%', height: '60%', isModal: true, title: 'Riassegna workflow ' + (selectedItems.length > 0 ? '(' + count() + ')' : ''), children: _jsxs("div", { style: { width: '100%', height: '100%', padding: '10px', display: 'flex', flexDirection: 'column', gap: 5 }, children: [_jsx(TMUserChooser, { values: selectedUserID, onValueChanged: (IDs) => {
95
+ useEffect(() => {
96
+ const items = selectedItems.length > 0
97
+ ? selectedItems.map(({ TID, DID }) => ({ TID, DID }))
98
+ : [{ TID, DID }];
99
+ SDK_Globals.tmSession?.NewWorkflowEngine().WFApprGetWFInfoAsync(items?.[0].TID).then(async (w) => {
100
+ let users = await UserListCacheService.GetAllAsync();
101
+ setParticipants(users.filter((u) => w?.participants?.some((p) => p.userID === u.id)));
102
+ }).catch(err => TMExceptionBoxManager.show({ exception: err }));
103
+ }, [selectedItems]);
104
+ return (_jsx(TMModal, { toolbar: _jsx(TMButton, { btnStyle: 'advanced', showTooltip: false, icon: _jsx(IconUser, { fontSize: 16 }), caption: 'Riassegna', disabled: disable, onClick: () => !disable && reAssignWorkFlowAsync(), advancedColor: TMColors.tertiary }), onClose: onClose, width: deviceType === DeviceType.MOBILE ? '95%' : '60%', height: '60%', isModal: true, title: 'Riassegna workflow ' + (selectedItems.length > 0 ? '(' + count() + ')' : ''), children: _jsxs("div", { style: { width: '100%', height: '100%', padding: '10px', display: 'flex', flexDirection: 'column', gap: 5 }, children: [_jsx(TMUserChooser, { dataSource: participants, values: selectedUserID, onValueChanged: (IDs) => {
101
105
  setSelectedUserID(IDs ?? []);
102
106
  } }), _jsxs("p", { style: { color: commentValue.length === 0 ? TMColors.error : 'black' }, children: ["Commento ", commentValue.length === 0 && _jsx("span", { children: ' (Campo obbligatorio)' }), " "] }), _jsx(StyledTextArea, { "$isValid": commentValue.length !== 0, value: commentValue, onChange: (e) => setCommentValue(e.target.value) })] }) }));
103
107
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@topconsultnpm/sdkui-react-beta",
3
- "version": "6.14.28",
3
+ "version": "6.14.30",
4
4
  "description": "",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1",
@@ -42,7 +42,7 @@
42
42
  "lib"
43
43
  ],
44
44
  "dependencies": {
45
- "@topconsultnpm/sdk-ts-beta": "6.14.4",
45
+ "@topconsultnpm/sdk-ts-beta": "6.14.5",
46
46
  "buffer": "^6.0.3",
47
47
  "devextreme": "24.2.6",
48
48
  "devextreme-react": "24.2.6",