@topconsultnpm/sdkui-react 6.21.0-dev2.52 → 6.21.0-dev2.54

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -16,6 +16,24 @@ export declare const isDirectoryPickerSupported: () => boolean;
16
16
  * Verifica se un'estensione (con o senza punto iniziale, case-insensitive) è "pdf".
17
17
  */
18
18
  export declare const isPdfExt: (ext: string | null | undefined) => boolean;
19
+ /**
20
+ * Estrae l'estensione completa da un nome file, gestendo estensioni composite
21
+ * a più livelli come .PDF.P7M, .XML.P7M.TS, etc.
22
+ *
23
+ * Esempi:
24
+ * - "documento.pdf" -> ".pdf"
25
+ * - "DCMT_123.PDF.P7M" -> ".PDF.P7M"
26
+ * - "fattura.xml.p7m" -> ".xml.p7m"
27
+ * - "example.XML.P7M.TS" -> ".XML.P7M.TS"
28
+ * - "file.tar.gz" -> ".gz" (tar.gz non è gestito come firma)
29
+ * - "file" -> ""
30
+ */
31
+ export declare const getFullFileExtension: (fileName: string, depth?: number) => string;
32
+ /**
33
+ * Estrae il nome base del file (senza estensione completa).
34
+ * Gestisce estensioni composite come .PDF.P7M
35
+ */
36
+ export declare const getFileBaseName: (fileName: string) => string;
19
37
  /** Sanitizza il nome file per Windows (rimuove caratteri illegali e limita la lunghezza) */
20
38
  export declare const sanitizeFileName: (fileName: string, fallbackName: string, maxLength?: number) => string;
21
39
  /** Verifica se un file esiste nella cartella (FileSystemDirectoryHandle) */
@@ -58,6 +58,56 @@ export const isPdfExt = (ext) => {
58
58
  const normalized = ext.trim().toLowerCase().replace(/^\./, '');
59
59
  return normalized === 'pdf';
60
60
  };
61
+ /**
62
+ * Estensioni di firma/marca temporale note che possono avvolgere altre estensioni.
63
+ * Es: file.pdf.p7m, file.xml.p7m, file.docx.p7m, file.xml.p7m.ts
64
+ */
65
+ const SIGNATURE_EXTENSIONS = new Set(['p7m', 'p7s', 'm7m', 'tsd', 'tsr', 'ts']);
66
+ /** Profondità massima di ricorsione per la ricerca di estensioni composite */
67
+ const MAX_EXTENSION_DEPTH = 5;
68
+ /**
69
+ * Estrae l'estensione completa da un nome file, gestendo estensioni composite
70
+ * a più livelli come .PDF.P7M, .XML.P7M.TS, etc.
71
+ *
72
+ * Esempi:
73
+ * - "documento.pdf" -> ".pdf"
74
+ * - "DCMT_123.PDF.P7M" -> ".PDF.P7M"
75
+ * - "fattura.xml.p7m" -> ".xml.p7m"
76
+ * - "example.XML.P7M.TS" -> ".XML.P7M.TS"
77
+ * - "file.tar.gz" -> ".gz" (tar.gz non è gestito come firma)
78
+ * - "file" -> ""
79
+ */
80
+ export const getFullFileExtension = (fileName, depth = 0) => {
81
+ if (!fileName || depth > MAX_EXTENSION_DEPTH)
82
+ return '';
83
+ const lastDotIndex = fileName.lastIndexOf('.');
84
+ if (lastDotIndex <= 0)
85
+ return '';
86
+ const lastExtension = fileName.substring(lastDotIndex + 1).toLowerCase();
87
+ // Se l'ultima estensione è una firma/marca, cerca ricorsivamente le estensioni precedenti
88
+ if (SIGNATURE_EXTENSIONS.has(lastExtension)) {
89
+ const nameWithoutLastExt = fileName.substring(0, lastDotIndex);
90
+ const innerExtension = getFullFileExtension(nameWithoutLastExt, depth + 1);
91
+ if (innerExtension) {
92
+ // Concatena l'estensione interna con quella corrente
93
+ return innerExtension + fileName.substring(lastDotIndex);
94
+ }
95
+ }
96
+ // Estensione singola (o estensione base dopo le firme)
97
+ return fileName.substring(lastDotIndex);
98
+ };
99
+ /**
100
+ * Estrae il nome base del file (senza estensione completa).
101
+ * Gestisce estensioni composite come .PDF.P7M
102
+ */
103
+ export const getFileBaseName = (fileName) => {
104
+ if (!fileName)
105
+ return '';
106
+ const fullExtension = getFullFileExtension(fileName);
107
+ if (!fullExtension)
108
+ return fileName;
109
+ return fileName.substring(0, fileName.length - fullExtension.length);
110
+ };
61
111
  // ============================================================
62
112
  // Sanitizzazione e gestione nomi file
63
113
  // ============================================================
@@ -72,10 +122,9 @@ export const sanitizeFileName = (fileName, fallbackName, maxLength = 255) => {
72
122
  .trim();
73
123
  sanitized = sanitized.replace(/[. ]+$/, '');
74
124
  if (sanitized.length > maxLength) {
75
- const lastDotIndex = sanitized.lastIndexOf('.');
76
- if (lastDotIndex > 0) {
77
- const extension = sanitized.substring(lastDotIndex);
78
- const baseName = sanitized.substring(0, lastDotIndex);
125
+ const extension = getFullFileExtension(sanitized);
126
+ if (extension) {
127
+ const baseName = getFileBaseName(sanitized);
79
128
  const maxBaseLength = maxLength - extension.length;
80
129
  sanitized = baseName.substring(0, maxBaseLength) + extension;
81
130
  }
@@ -101,14 +150,9 @@ export const fileExists = async (dirHandle, fileName) => {
101
150
  };
102
151
  /** Genera un nome file univoco aggiungendo un suffisso numerico se già esistente */
103
152
  export const generateUniqueFileName = async (dirHandle, originalName) => {
104
- const splitName = (name) => {
105
- const lastDotIndex = name.lastIndexOf('.');
106
- const baseName = lastDotIndex > 0 ? name.substring(0, lastDotIndex) : name;
107
- const extension = lastDotIndex > 0 ? name.substring(lastDotIndex) : '';
108
- return { baseName, extension };
109
- };
110
153
  try {
111
- const { baseName, extension } = splitName(originalName);
154
+ const baseName = getFileBaseName(originalName);
155
+ const extension = getFullFileExtension(originalName);
112
156
  const MAX_ATTEMPTS = 1000;
113
157
  for (let counter = 1; counter <= MAX_ATTEMPTS; counter++) {
114
158
  const candidateName = counter === 1 ? originalName : `${baseName} (${counter - 1})${extension}`;
@@ -122,7 +166,8 @@ export const generateUniqueFileName = async (dirHandle, originalName) => {
122
166
  return `${baseName}_${Date.now()}${extension}`;
123
167
  }
124
168
  catch {
125
- const { baseName, extension } = splitName(originalName);
169
+ const baseName = getFileBaseName(originalName);
170
+ const extension = getFullFileExtension(originalName);
126
171
  return `${baseName}_${Date.now()}${extension}`;
127
172
  }
128
173
  };
@@ -174,8 +219,8 @@ export const getFilteredMetadata = async (tid, did, separatorChar) => {
174
219
  /** Genera il nome file di destinazione in base alle impostazioni di naming */
175
220
  export const generateTargetFileName = async (file, dcmtInfo, options) => {
176
221
  const { fileNamingMode, separatorChar } = options;
177
- const lastDotIndex = file.name.lastIndexOf('.');
178
- const fileExtension = lastDotIndex > 0 ? file.name.substring(lastDotIndex) : '';
222
+ // Usa getFullFileExtension per gestire estensioni composite come .PDF.P7M, .DOCX.p7m
223
+ const fileExtension = getFullFileExtension(file.name);
179
224
  const fallbackName = dcmtInfo.DID + fileExtension;
180
225
  let targetFileName = file.name;
181
226
  switch (fileNamingMode) {
@@ -16,5 +16,5 @@ interface ITMResultDialogProps {
16
16
  declare const TMResultDialog: React.FC<ITMResultDialogProps>;
17
17
  export default TMResultDialog;
18
18
  export declare class TMResultManager {
19
- static show(result: ResultInfo[], title: string, id1Caption: string | undefined, id2Caption: string | undefined, customMsg?: string, customDuration?: number): void;
19
+ static show(result: ResultInfo[], title: string, id1Caption: string | undefined, id2Caption: string | undefined, customMsg?: string, customDuration?: number, showSuccessAlert?: boolean): void;
20
20
  }
@@ -29,11 +29,13 @@ const TMResultDialog = ({ title, result, id1Caption, id2Caption }) => {
29
29
  };
30
30
  export default TMResultDialog;
31
31
  export class TMResultManager {
32
- static show(result, title, id1Caption, id2Caption, customMsg, customDuration) {
32
+ static show(result, title, id1Caption, id2Caption, customMsg, customDuration, showSuccessAlert = true) {
33
33
  const warningsCount = result.filter(o => o.resultType == ResultTypes.WARNING).length;
34
34
  const errorsCount = result.filter(o => o.resultType == ResultTypes.ERROR).length;
35
35
  if (warningsCount <= 0 && errorsCount <= 0) {
36
- ShowAlert({ message: customMsg && customMsg.length > 0 ? customMsg : SDKUI_Localizator.OperationSuccess, mode: 'success', duration: customDuration && customDuration > 0 ? customDuration : 3000, title: title });
36
+ if (showSuccessAlert) {
37
+ ShowAlert({ message: customMsg && customMsg.length > 0 ? customMsg : SDKUI_Localizator.OperationSuccess, mode: 'success', duration: customDuration && customDuration > 0 ? customDuration : 3000, title: title });
38
+ }
37
39
  return;
38
40
  }
39
41
  let container = document.createElement('div');
@@ -597,6 +597,7 @@ export declare class SDKUI_Localizator {
597
597
  static get Previous(): "Vorherige" | "Previous" | "Anterior" | "Précédent" | "Precedente";
598
598
  static get Priority(): string;
599
599
  static get PriorityLegend(): "Legende der Prioritäten" | "Priority Legend" | "Leyenda de prioridades" | "Légende des priorités" | "Lenda das prioridades" | "Legenda delle priorità";
600
+ static get Print(): "Drucken" | "Print" | "Imprimir" | "Imprimer" | "Stampa";
600
601
  static get ProceedAnyway(): "Dennoch fortfahren?" | "Proceed anyway?" | "¿Proceder de todos modos?" | "Procéder quand même ?" | "Prosseguir mesmo assim?" | "Procedere comunque?";
601
602
  static get ProcessedItems(): "Durchdachte Elemente" | "Processed items" | "Elementos elaborados" | "Items traités" | "Itens processados" | "Elementi elaborati";
602
603
  static get Properties(): "Eigenschaften" | "Properties" | "Propiedades" | "Propriétés" | "Propriedades" | "Proprietà";
@@ -5942,6 +5942,16 @@ export class SDKUI_Localizator {
5942
5942
  default: return "Legenda delle priorità";
5943
5943
  }
5944
5944
  }
5945
+ static get Print() {
5946
+ switch (this._cultureID) {
5947
+ case CultureIDs.De_DE: return "Drucken";
5948
+ case CultureIDs.En_US: return "Print";
5949
+ case CultureIDs.Es_ES: return "Imprimir";
5950
+ case CultureIDs.Fr_FR: return "Imprimer";
5951
+ case CultureIDs.Pt_PT: return "Imprimir";
5952
+ default: return "Stampa";
5953
+ }
5954
+ }
5945
5955
  static get ProceedAnyway() {
5946
5956
  switch (this._cultureID) {
5947
5957
  case CultureIDs.De_DE: return "Dennoch fortfahren?";
@@ -19,7 +19,7 @@ export interface UseDcmtOperationsReturn {
19
19
  waitPanelTextSecondary: string;
20
20
  waitPanelValueSecondary: number;
21
21
  waitPanelMaxValueSecondary: number;
22
- downloadDcmtsAsync: (inputDcmts: DcmtInfo[] | undefined, downloadType?: DownloadTypes, downloadMode?: DownloadModes, onFileDownloaded?: (dcmtFile: File, dcmtInfo: DcmtInfo) => void | Promise<void>, confirmAttachments?: (list: FileDescriptor[]) => Promise<string[] | undefined>, skipConfirmation?: boolean, retrieveOptions?: RetrieveFormatOptions, useCache?: boolean) => Promise<void>;
22
+ downloadDcmtsAsync: (inputDcmts: DcmtInfo[] | undefined, downloadType?: DownloadTypes, downloadMode?: DownloadModes, onFileDownloaded?: (dcmtFile: File, dcmtInfo: DcmtInfo) => void | Promise<void>, confirmAttachments?: (list: FileDescriptor[]) => Promise<string[] | undefined>, skipConfirmation?: boolean, retrieveOptions?: RetrieveFormatOptions, useCache?: boolean, showSuccessAlert?: boolean) => Promise<void>;
23
23
  getDcmtFileAsync: (inputDcmt: DcmtInfo | undefined, rfo: RetrieveFileOptions, operationTitle: string, keepWaitPanelPrimary: boolean, bypassCache?: boolean) => Promise<{
24
24
  file: File | undefined;
25
25
  isFromCache: boolean;
@@ -75,7 +75,7 @@ export const useDcmtOperations = () => {
75
75
  const [waitPanelMaxValueSecondary, setWaitPanelMaxValueSecondary] = useState(0);
76
76
  const { OpenFileDialog } = useFileDialog();
77
77
  const [selectFileSource, FileSourceDialog] = useFileSourceDialog();
78
- const _downloadDcmtsAsync = async (inputDcmts, downloadMode = "download", onFileDownloaded, skipConfirmation = false, retrieveOptions, useCache = true) => {
78
+ const _downloadDcmtsAsync = async (inputDcmts, downloadMode = "download", onFileDownloaded, skipConfirmation = false, retrieveOptions, useCache = true, showSuccessAlert = true) => {
79
79
  if (inputDcmts === undefined)
80
80
  return;
81
81
  if (inputDcmts.length <= 0)
@@ -189,9 +189,9 @@ export const useDcmtOperations = () => {
189
189
  setWaitPanelMaxValueSecondary(0);
190
190
  setWaitPanelValueSecondary(0);
191
191
  setShowWaitPanel(false);
192
- TMResultManager.show(result, operationTitle, "TID", "DID");
192
+ TMResultManager.show(result, operationTitle, "TID", "DID", undefined, undefined, showSuccessAlert);
193
193
  };
194
- const _downloadAttachmentsAsync = async (inputDcmts, confirmAttachments) => {
194
+ const _downloadAttachmentsAsync = async (inputDcmts, confirmAttachments, showSuccessAlert = true) => {
195
195
  if (inputDcmts === undefined)
196
196
  return;
197
197
  if (inputDcmts.length !== 1)
@@ -239,7 +239,7 @@ export const useDcmtOperations = () => {
239
239
  }
240
240
  }
241
241
  }
242
- TMResultManager.show(result, operationTitle, "TID", "DID");
242
+ TMResultManager.show(result, operationTitle, "TID", "DID", undefined, undefined, showSuccessAlert);
243
243
  }
244
244
  catch (ex) {
245
245
  TMSpinner.hide();
@@ -251,11 +251,11 @@ export const useDcmtOperations = () => {
251
251
  TMExceptionBoxManager.show({ exception: err });
252
252
  }
253
253
  };
254
- const downloadDcmtsAsync = async (inputDcmts, downloadType = DownloadTypes.Attachment, downloadMode = "download", onFileDownloaded, confirmAttachments, skipConfirmation = false, retrieveOptions, useCache = true) => {
254
+ const downloadDcmtsAsync = async (inputDcmts, downloadType = DownloadTypes.Attachment, downloadMode = "download", onFileDownloaded, confirmAttachments, skipConfirmation = false, retrieveOptions, useCache = true, showSuccessAlert = true) => {
255
255
  switch (downloadType) {
256
- case DownloadTypes.Dcmt: return await _downloadDcmtsAsync(inputDcmts, downloadMode, onFileDownloaded, skipConfirmation, retrieveOptions, useCache);
257
- case DownloadTypes.Attachment: return await _downloadAttachmentsAsync(inputDcmts, confirmAttachments);
258
- default: return await _downloadDcmtsAsync(inputDcmts, undefined, undefined, skipConfirmation, retrieveOptions, useCache);
256
+ case DownloadTypes.Dcmt: return await _downloadDcmtsAsync(inputDcmts, downloadMode, onFileDownloaded, skipConfirmation, retrieveOptions, useCache, showSuccessAlert);
257
+ case DownloadTypes.Attachment: return await _downloadAttachmentsAsync(inputDcmts, confirmAttachments, showSuccessAlert);
258
+ default: return await _downloadDcmtsAsync(inputDcmts, undefined, undefined, skipConfirmation, retrieveOptions, useCache, showSuccessAlert);
259
259
  }
260
260
  };
261
261
  const uploadDcmtsAsync = async (inputDcmts, operationTitle, operType, actionAfterOperationAsync) => {
@@ -567,6 +567,53 @@ export const useDocumentOperations = (props) => {
567
567
  onClick: () => openEditPdfCallback(selectedDcmtInfos, refreshDocumentPreview),
568
568
  };
569
569
  };
570
+ const printMenuItem = () => {
571
+ // Take the first document (used for validation checks)
572
+ const firstDoc = selectedDcmtInfos?.[0];
573
+ // Check if the selected document is a PDF
574
+ const isPdf = firstDoc?.FILEEXT?.toLowerCase() === "pdf";
575
+ return {
576
+ id: 'print',
577
+ icon: _jsx("i", { className: "dx-icon-print", style: { fontSize: '20px' } }),
578
+ name: SDKUI_Localizator.Print,
579
+ operationType: 'singleRow',
580
+ disabled: !isPdf || isDisabledForSingleRow(),
581
+ onClick: isPdf ? async () => {
582
+ const handlePrint = async (file, _dcmtInfo) => {
583
+ const fileURL = window.URL.createObjectURL(file);
584
+ // Usa un iframe nascosto con sandbox per:
585
+ // 1. Bloccare l'esecuzione di JavaScript nel PDF
586
+ // 2. Non aprire un nuovo tab
587
+ const printFrame = document.createElement('iframe');
588
+ printFrame.style.position = 'fixed';
589
+ printFrame.style.right = '0';
590
+ printFrame.style.bottom = '0';
591
+ printFrame.style.width = '0';
592
+ printFrame.style.height = '0';
593
+ printFrame.style.border = 'none';
594
+ // sandbox senza 'allow-scripts' blocca l'esecuzione di JS
595
+ /* printFrame.sandbox.add('allow-same-origin'); */
596
+ printFrame.src = fileURL;
597
+ document.body.appendChild(printFrame);
598
+ printFrame.onload = () => {
599
+ try {
600
+ printFrame.contentWindow?.focus();
601
+ printFrame.contentWindow?.print();
602
+ }
603
+ catch (e) {
604
+ console.error('Errore durante la stampa:', e);
605
+ }
606
+ // Cleanup dopo un delay per permettere la stampa
607
+ setTimeout(() => {
608
+ document.body.removeChild(printFrame);
609
+ window.URL.revokeObjectURL(fileURL);
610
+ }, 60000);
611
+ };
612
+ };
613
+ await downloadDcmtsAsync(selectedDcmtInfos, DownloadTypes.Dcmt, "download", handlePrint, undefined, true, undefined, true, false);
614
+ } : undefined,
615
+ };
616
+ };
570
617
  const handleSignApprove = () => {
571
618
  if (!onOpenS4TViewerRequest) {
572
619
  ShowAlert({
@@ -1082,6 +1129,7 @@ export const useDocumentOperations = (props) => {
1082
1129
  batchUpdateMenuItem(),
1083
1130
  passToArchive(),
1084
1131
  ...(selectedDcmtInfos.length > 0 && isPdfEditorAvailable(dtd, selectedDcmtInfos[0]?.FILEEXT) && onOpenPdfEditorRequest ? [pdfEditorMenuItem(onOpenPdfEditorRequest)] : []),
1132
+ ...(!isMobile ? [printMenuItem()] : [])
1085
1133
  ]
1086
1134
  },
1087
1135
  signatureMenuItem(),
@@ -1125,6 +1173,7 @@ export const useDocumentOperations = (props) => {
1125
1173
  commentFromWgMenuItem(false),
1126
1174
  relationsMenuItem(true),
1127
1175
  removeFromWgMenuItem(SDKUI_Localizator.RemoveFromWorkgroup),
1176
+ ...(!isMobile ? [printMenuItem()] : [])
1128
1177
  ].sort((a, b) => a.name.localeCompare(b.name));
1129
1178
  return [
1130
1179
  ...sortedItems,
@@ -1148,6 +1197,7 @@ export const useDocumentOperations = (props) => {
1148
1197
  downloadFileMenuItem(),
1149
1198
  downloadXMLAttachmentsMenuItem(),
1150
1199
  ...(selectedDcmtInfos.length > 0 && isPdfEditorAvailable(dtd, selectedDcmtInfos[0]?.FILEEXT) && onOpenPdfEditorRequest ? [pdfEditorMenuItem(onOpenPdfEditorRequest)] : []),
1200
+ ...(!isMobile ? [printMenuItem()] : [])
1151
1201
  ]
1152
1202
  },
1153
1203
  signatureMenuItem(),
@@ -1173,7 +1223,8 @@ export const useDocumentOperations = (props) => {
1173
1223
  ...(SDK_Globals.tmSession?.SessionDescr?.appModuleID === AppModules.SURFER ? [createContextualTaskMenuItem()] : []),
1174
1224
  downloadFileMenuItem(),
1175
1225
  downloadXMLAttachmentsMenuItem(),
1176
- ...(selectedDcmtInfos.length > 0 && isPdfEditorAvailable(dtd, selectedDcmtInfos[0]?.FILEEXT) && onOpenPdfEditorRequest ? [pdfEditorMenuItem(onOpenPdfEditorRequest)] : [])
1226
+ ...(selectedDcmtInfos.length > 0 && isPdfEditorAvailable(dtd, selectedDcmtInfos[0]?.FILEEXT) && onOpenPdfEditorRequest ? [pdfEditorMenuItem(onOpenPdfEditorRequest)] : []),
1227
+ ...(!isMobile ? [printMenuItem()] : [])
1177
1228
  ]
1178
1229
  },
1179
1230
  signatureMenuItem(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@topconsultnpm/sdkui-react",
3
- "version": "6.21.0-dev2.52",
3
+ "version": "6.21.0-dev2.54",
4
4
  "description": "",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1",