@topconsultnpm/sdkui-react 6.22.0-dev2.27 → 6.22.0-dev2.29

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.
@@ -36,6 +36,9 @@ const TMDataGrid = React.forwardRef((props, ref) => {
36
36
  const [customContextMenuVisible, setCustomContextMenuVisible] = useState(false);
37
37
  const [customContextMenuPosition, setCustomContextMenuPosition] = useState({ x: 0, y: 0 });
38
38
  const [customContextMenuRowKey, setCustomContextMenuRowKey] = useState(undefined);
39
+ // true quando il menu contestuale viene aperto su spazio vuoto senza alcuna riga di riferimento:
40
+ // in tal caso i comandi legati alla riga (singleRow/multiRow) devono risultare disabilitati
41
+ const [customContextMenuOnEmptyArea, setCustomContextMenuOnEmptyArea] = useState(false);
39
42
  const gridContainerRef = useRef(null);
40
43
  // Ref per evitare focus ripetuti sulla search panel
41
44
  const searchPanelFocusedRef = useRef(false);
@@ -76,16 +79,17 @@ const TMDataGrid = React.forwardRef((props, ref) => {
76
79
  const rowKey = internalRef.current.instance().getKeyByRowIndex(rowIndex);
77
80
  internalRef.current.instance().option('focusedRowKey', rowKey);
78
81
  setCustomContextMenuRowKey(rowKey);
82
+ setCustomContextMenuOnEmptyArea(false);
79
83
  }
80
84
  else {
81
85
  // Right-click su spazio vuoto: ricade sulla riga focalizzata solo se esiste ancora.
82
- // Dopo un'eliminazione focusedRowKey può puntare a una riga non più presente:
83
- // in tal caso non si mostra il menu (evita di riproporre le azioni della riga eliminata)
86
+ // Se non c'è una riga focalizzata (es. primo click dopo l'apertura) o se focusedRowKey punta a
87
+ // una riga non più presente (dopo un'eliminazione), il menu viene comunque mostrato ma senza
88
+ // riga di riferimento: i comandi di riga restano disabilitati, quelli generali utilizzabili
84
89
  const grid = internalRef.current?.instance();
85
90
  const focusedRowExists = focusedRowKey !== undefined && !!grid && grid.getRowIndexByKey(focusedRowKey) >= 0;
86
- if (!focusedRowExists)
87
- return;
88
- setCustomContextMenuRowKey(focusedRowKey);
91
+ setCustomContextMenuRowKey(focusedRowExists ? focusedRowKey : undefined);
92
+ setCustomContextMenuOnEmptyArea(!focusedRowExists);
89
93
  }
90
94
  setCustomContextMenuVisible(true);
91
95
  setCustomContextMenuPosition({ x: e.clientX, y: e.clientY });
@@ -122,6 +126,7 @@ const TMDataGrid = React.forwardRef((props, ref) => {
122
126
  setCustomContextMenuVisible(true);
123
127
  setCustomContextMenuPosition({ x: clientX, y: clientY });
124
128
  setCustomContextMenuRowKey(rowKey);
129
+ setCustomContextMenuOnEmptyArea(false);
125
130
  },
126
131
  });
127
132
  // Creating a ref to store the timestamp of the last selection change
@@ -230,7 +235,8 @@ const TMDataGrid = React.forwardRef((props, ref) => {
230
235
  return items.map(item => {
231
236
  let disabled = item.disabled ?? false;
232
237
  let disabledCalculation = false;
233
- const id = focusedRowEnabled ? focusedRowKey : rowID;
238
+ // Menu aperto su spazio vuoto senza riga di riferimento: nessun id, i comandi di riga sono disabilitati
239
+ const id = customContextMenuOnEmptyArea ? undefined : (focusedRowEnabled ? focusedRowKey : rowID);
234
240
  if (item.operationType === 'singleRow') {
235
241
  disabledCalculation = selectedRowKeys.length > 1 || id === undefined;
236
242
  }
@@ -245,7 +251,10 @@ const TMDataGrid = React.forwardRef((props, ref) => {
245
251
  if (item.operationType === 'singleRow' && id !== undefined) {
246
252
  originalOnClick(id);
247
253
  }
248
- else if (item.operationType === 'multiRow' && id !== undefined) {
254
+ else if (item.operationType === 'multiRow' && (selectedRowKeys.length > 0 || id !== undefined)) {
255
+ // Condizione allineata a quella di disabilitazione: con righe selezionate il comando è
256
+ // attivo anche senza riga di riferimento (es. menu aperto su spazio vuoto), quindi qui
257
+ // devono comunque essere passate le chiavi selezionate e non un onClick senza argomenti
249
258
  if (selectedRowKeys.length > 0) {
250
259
  originalOnClick(selectedRowKeys);
251
260
  }
@@ -260,7 +269,7 @@ const TMDataGrid = React.forwardRef((props, ref) => {
260
269
  submenu: item.submenu ? processCustomContextMenuItems(item.submenu, id) : undefined,
261
270
  };
262
271
  });
263
- }, [focusedRowEnabled, focusedRowKey, selectedRowKeys]);
272
+ }, [focusedRowEnabled, focusedRowKey, selectedRowKeys, customContextMenuOnEmptyArea]);
264
273
  // Gestisce la preparazione del menu contestuale della griglia (header e righe)
265
274
  const onContextMenuPreparingCallback = useCallback((e) => {
266
275
  if (e === undefined)
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useCallback, useEffect, useMemo, useState } from "react";
3
- import { extractTextsFromDirectory, findFileItems, setFolderTreeViewItems } from "./TMFileManagerUtils";
3
+ import { extractTextsFromDirectory, findFileItems, resolveFileItemUserNames, setFolderTreeViewItems } from "./TMFileManagerUtils";
4
4
  import { DeviceType, useDeviceType } from "./TMDeviceProvider";
5
5
  import { formatBytes, Globalization, IconDashboard, IconFolder, IconHide, IconList, IconMenuVertical, IconRefresh, IconShow, SDKUI_Globals, SDKUI_Localizator, TMCommandsContextMenu, TMConditionalWrapper } from "../../helper";
6
6
  import TMContextMenu from '../NewComponents/ContextMenu/TMContextMenu';
@@ -91,10 +91,12 @@ const TMFileManager = (props) => {
91
91
  (Globalization.getDateTimeDisplayValue(item.lastUpdateTime)?.toLowerCase().toString().includes(searchText.toLowerCase().trim())) ||
92
92
  (Globalization.getDateTimeDisplayValue(item.creationTime)?.toLowerCase().toString().includes(searchText.toLowerCase().trim())) ||
93
93
  (item.version?.toString().includes(searchText.toLowerCase().trim()))));
94
- setFilteredFileItems(filtered);
94
+ // I nomi utente possono mancare quando la lista viene ricostruita senza i partecipanti
95
+ // (es. refresh dopo la condivisione di un file): vengono risolti qui dagli ID
96
+ setFilteredFileItems(filtered.map(item => resolveFileItemUserNames(item, allUsers)));
95
97
  };
96
98
  filterItems();
97
- }, [searchText, selectedFolder]);
99
+ }, [searchText, selectedFolder, allUsers]);
98
100
  // Callback to handle the "back" action (closing the draft list view)
99
101
  const onBackCallback = useCallback(() => {
100
102
  setOpenDraftList(false);
@@ -26,6 +26,14 @@ export interface FileItem {
26
26
  version?: number;
27
27
  isSigned?: number;
28
28
  }
29
+ /**
30
+ * Completa i nomi utente (autore e utente di check-out) di un FileItem quando mancano, risolvendoli dagli ID.
31
+ * buildFolderHierarchy popola i nomi solo se riceve la lista dei partecipanti: nei refresh parziali
32
+ * (es. dopo la condivisione di un nuovo file) i partecipanti possono non essere disponibili e i nomi
33
+ * arrivano vuoti, svuotando la colonna Autore fino al ricaricamento completo.
34
+ * Restituisce lo stesso oggetto se non c'è nulla da risolvere, per non invalidare le reference inutilmente.
35
+ */
36
+ export declare const resolveFileItemUserNames: (item: FileItem, users: Array<UserDescriptor>) => FileItem;
29
37
  export interface TMFileManagerTreeViewDirectory {
30
38
  id: number;
31
39
  text: string;
@@ -12,6 +12,28 @@ export const isSigned = (ext) => {
12
12
  const parts = normalized.split('.');
13
13
  return parts.some(part => SIGNATURE_EXTENSIONS.has(part));
14
14
  };
15
+ /**
16
+ * Completa i nomi utente (autore e utente di check-out) di un FileItem quando mancano, risolvendoli dagli ID.
17
+ * buildFolderHierarchy popola i nomi solo se riceve la lista dei partecipanti: nei refresh parziali
18
+ * (es. dopo la condivisione di un nuovo file) i partecipanti possono non essere disponibili e i nomi
19
+ * arrivano vuoti, svuotando la colonna Autore fino al ricaricamento completo.
20
+ * Restituisce lo stesso oggetto se non c'è nulla da risolvere, per non invalidare le reference inutilmente.
21
+ */
22
+ export const resolveFileItemUserNames = (item, users) => {
23
+ if (!users || users.length === 0)
24
+ return item;
25
+ const isValidUserID = (id) => id !== undefined && Number.isFinite(id) && id > 0;
26
+ const needsUpdaterName = !item.updaterName && isValidUserID(item.updaterID);
27
+ const needsCheckOutUserName = !item.checkOutUserName && isValidUserID(item.checkOutUserID);
28
+ if (!needsUpdaterName && !needsCheckOutUserName)
29
+ return item;
30
+ const resolved = { ...item };
31
+ if (needsUpdaterName)
32
+ resolved.updaterName = users.find(user => user.id === item.updaterID)?.name ?? item.updaterName;
33
+ if (needsCheckOutUserName)
34
+ resolved.checkOutUserName = users.find(user => user.id === item.checkOutUserID)?.name ?? item.checkOutUserName;
35
+ return resolved;
36
+ };
15
37
  export var TMFileManagerPageSize;
16
38
  (function (TMFileManagerPageSize) {
17
39
  TMFileManagerPageSize[TMFileManagerPageSize["Small"] = 30] = "Small";
@@ -60,6 +60,9 @@ export const sanitizeException = (exception) => {
60
60
  catch {
61
61
  return exception;
62
62
  }
63
+ if (plain && typeof plain === 'object' && !Array.isArray(plain) && plain.message === undefined && typeof exception.message === 'string') {
64
+ plain.message = exception.message;
65
+ }
63
66
  return redactSensitiveValue(plain);
64
67
  };
65
68
  const StyledExeptionToolbar = styled.div `
@@ -821,7 +821,21 @@ export var LandingPages;
821
821
  // #region Exception
822
822
  export function getExceptionMessage(ex) {
823
823
  let msg = ex.isApiException ? ex.response.title : ex.message;
824
- return msg ?? ex.toString();
824
+ if (msg !== undefined && msg !== null)
825
+ return msg;
826
+ let asString = ex.toString();
827
+ if (asString !== '[object Object]')
828
+ return asString;
829
+ if (ex.errorMessage)
830
+ return ex.errorMessage;
831
+ if (ex.title)
832
+ return ex.title;
833
+ try {
834
+ return JSON.stringify(ex) ?? asString;
835
+ }
836
+ catch {
837
+ return asString;
838
+ }
825
839
  }
826
840
  export function getContrastColor(inputColor) {
827
841
  let hex = inputColor;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@topconsultnpm/sdkui-react",
3
- "version": "6.22.0-dev2.27",
3
+ "version": "6.22.0-dev2.29",
4
4
  "description": "",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1",
@@ -39,7 +39,7 @@
39
39
  "lib"
40
40
  ],
41
41
  "dependencies": {
42
- "@topconsultnpm/sdk-ts": "6.22.0-dev2.6",
42
+ "@topconsultnpm/sdk-ts": "6.22.0-dev2.7",
43
43
  "@zip.js/zip.js": "2.8.26",
44
44
  "buffer": "^6.0.3",
45
45
  "devextreme": "^25.2.6",