@topconsultnpm/sdkui-react 6.21.0-dev4.8 → 6.21.0-dev4.9

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.
@@ -26,7 +26,9 @@ export declare const TMNothingToShow: ({ text, secondText, fileExt, icon }: {
26
26
  fileExt?: string;
27
27
  icon?: any;
28
28
  }) => import("react/jsx-runtime").JSX.Element;
29
- export declare const StyledHeaderIcon: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components/dist/types").Substitute<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, {
29
+ export declare const StyledHeaderIcon: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("styled-components").FastOmit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "$color"> & {
30
30
  $color: string;
31
- }>> & string;
32
- export declare const StyledPanelStatusContainer: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>> & string;
31
+ }, never> & Partial<Pick<import("styled-components").FastOmit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "$color"> & {
32
+ $color: string;
33
+ }, never>>> & string;
34
+ export declare const StyledPanelStatusContainer: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never> & Partial<Pick<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>>> & string;
@@ -1,10 +1,10 @@
1
- import React from 'react';
2
1
  import { DcmtInfo } from '../../../ts';
3
2
  import { HomeBlogPost, TaskDescriptor } from '@topconsultnpm/sdk-ts';
4
3
  import { TMCopyToFolderMode } from '../../../hooks/useDocumentOperations';
5
4
  interface ITMMergeToPdfFormProps {
6
5
  mode: TMCopyToFolderMode;
7
6
  selectedDcmtInfos: Array<DcmtInfo>;
7
+ selectedItemsFull: Array<any>;
8
8
  onClose: () => void;
9
9
  showTMRelationViewer: boolean;
10
10
  allTasks?: Array<TaskDescriptor>;
@@ -20,5 +20,5 @@ interface ITMMergeToPdfFormProps {
20
20
  * Condivide TMDownloadRelationViewerSection e gli helper in copyAndMergeDcmtsShared
21
21
  * con TMCopyToFolderForm.
22
22
  */
23
- declare const TMMergeToPdfForm: React.FC<ITMMergeToPdfFormProps>;
23
+ declare const TMMergeToPdfForm: (props: ITMMergeToPdfFormProps) => import("react/jsx-runtime").JSX.Element;
24
24
  export default TMMergeToPdfForm;
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useRef, useState } from 'react';
3
3
  import { DownloadTypes } from '../../../ts';
4
- import { calcResponsiveSizes, IconFolderOpen, IconPlay, IconUndo, isConvertibleToPdfExt, SDKUI_Globals, SDKUI_Localizator, } from '../../../helper';
4
+ import { calcResponsiveSizes, getExceptionMessage, IconFolderOpen, IconPlay, IconUndo, SDKUI_Globals, SDKUI_Localizator, } from '../../../helper';
5
5
  import { DcmtOpers, FileFormats, GeneralRetrieveFormats, ResultTypes, RetrieveFileOptions, ValidationItem, } from '@topconsultnpm/sdk-ts';
6
6
  import TMModal from '../../base/TMModal';
7
7
  import { useDeviceType } from '../../base/TMDeviceProvider';
@@ -12,16 +12,19 @@ import { TMLayoutWaitingContainer } from '../../base/TMWaitPanel';
12
12
  import { TMSplitterLayout } from '../../base/TMLayout';
13
13
  import { TMColors } from '../../../utils/theme';
14
14
  import TMDownloadRelationViewerSection from './TMDownloadRelationViewerSection';
15
- import { getDcmtInfosToDownload, getFloatingLabelStyle, isDirectoryPickerSupported, isPdfExt, MIN_PDF_FOR_MERGE, } from './copyAndMergeDcmtsShared';
15
+ import { getDcmtInfosToDownload, getFloatingLabelStyle, isDirectoryPickerSupported, MIN_PDF_FOR_MERGE, } from './copyAndMergeDcmtsShared';
16
+ import { createMetadataPdfFromDocument, getMergePdfSelectionStats, isMetadataOnlyDcmt } from './mergePdfUtils';
16
17
  import ShowAlert from '../../base/TMAlert';
17
18
  import TMTooltip from '../../base/TMTooltip';
18
19
  import MergePdfManager from '../../../helper/MergePdfManager';
20
+ import { TMResultManager } from '../../forms/TMResultDialog';
19
21
  /**
20
22
  * Form per l'unione di più documenti PDF in un singolo file.
21
23
  * Condivide TMDownloadRelationViewerSection e gli helper in copyAndMergeDcmtsShared
22
24
  * con TMCopyToFolderForm.
23
25
  */
24
- const TMMergeToPdfForm = ({ mode, selectedDcmtInfos, onClose, showTMRelationViewer, allTasks, getAllTasks, deleteTaskByIdsCallback, addTaskCallback, editTaskCallback, handleNavigateToWGs, handleNavigateToDossiers }) => {
26
+ const TMMergeToPdfForm = (props) => {
27
+ const { mode, selectedDcmtInfos, selectedItemsFull, onClose, showTMRelationViewer, allTasks, getAllTasks, deleteTaskByIdsCallback, addTaskCallback, editTaskCallback, handleNavigateToWGs, handleNavigateToDossiers } = props;
25
28
  const { abortController, showWaitPanel, waitPanelTitle, showPrimary, waitPanelTextPrimary, waitPanelValuePrimary, waitPanelMaxValuePrimary, showSecondary, waitPanelTextSecondary, waitPanelValueSecondary, waitPanelMaxValueSecondary, downloadDcmtsAsync, } = useDcmtOperations();
26
29
  const deviceType = useDeviceType();
27
30
  const [pdfFileName, setPdfFileName] = useState(`${SDKUI_Localizator.Result.toLowerCase()}.pdf`);
@@ -79,36 +82,7 @@ const TMMergeToPdfForm = ({ mode, selectedDcmtInfos, onClose, showTMRelationView
79
82
  pdfValidationItems.push(new ValidationItem(ResultTypes.ERROR, SDKUI_Localizator.PdfFileName, SDKUI_Localizator.RequiredField));
80
83
  }
81
84
  // ---- Statistiche selezione PDF / convertibili / non-PDF ----
82
- const convertibleSelectedItems = (() => {
83
- if (showTMRelationViewer) {
84
- return selectedItemsRelationViewer
85
- .filter(i => i.isDcmt && isConvertibleToPdfExt(i.fileExt))
86
- .map(i => ({ key: `${i.tid}_${i.did}`, ext: i.fileExt ?? undefined }));
87
- }
88
- return selectedDcmtInfos
89
- .filter(d => isConvertibleToPdfExt(d.FILEEXT))
90
- .map(d => ({ key: `${d.TID}_${d.DID}`, ext: d.FILEEXT }));
91
- })();
92
- const hasConvertibleSelected = convertibleSelectedItems.length > 0;
93
- const nonPdfSelectedItems = (() => {
94
- if (showTMRelationViewer) {
95
- return selectedItemsRelationViewer
96
- .filter(i => i.isDcmt && !isPdfExt(i.fileExt) && !isConvertibleToPdfExt(i.fileExt))
97
- .map(i => ({ key: `${i.tid}_${i.did}`, ext: i.fileExt ?? undefined }));
98
- }
99
- return selectedDcmtInfos
100
- .filter(d => !isPdfExt(d.FILEEXT) && !isConvertibleToPdfExt(d.FILEEXT))
101
- .map(d => ({ key: `${d.TID}_${d.DID}`, ext: d.FILEEXT }));
102
- })();
103
- const hasNonPdfSelected = nonPdfSelectedItems.length > 0;
104
- // Conteggio file unibili: PDF nativi + file convertibili
105
- const mergeableSelectedCount = (() => {
106
- if (showTMRelationViewer) {
107
- return selectedItemsRelationViewer.filter(i => i.isDcmt && (isPdfExt(i.fileExt) || isConvertibleToPdfExt(i.fileExt))).length;
108
- }
109
- return selectedDcmtInfos.filter(d => isPdfExt(d.FILEEXT) || isConvertibleToPdfExt(d.FILEEXT)).length;
110
- })();
111
- const hasEnoughPdfForMerge = mergeableSelectedCount >= MIN_PDF_FOR_MERGE;
85
+ const { convertibleSelectedItems, hasConvertibleSelected, nonPdfSelectedItems, hasNonPdfSelected, metadataOnlySelectedItems, hasMetadataOnlySelected, mergeableSelectedCount, hasEnoughPdfForMerge, } = getMergePdfSelectionStats(selectedDcmtInfos, selectedItemsFull, selectedItemsRelationViewer, showTMRelationViewer);
112
86
  const isFormValid = () => pdfValidationItems.length === 0;
113
87
  // ---- Recupero dei file PDF da unire (condiviso tra Esegui e Anteprima) ----
114
88
  const collectPdfFilesToMerge = async () => {
@@ -133,17 +107,81 @@ const TMMergeToPdfForm = ({ mode, selectedDcmtInfos, onClose, showTMRelationView
133
107
  });
134
108
  return null;
135
109
  }
110
+ // Mappa per lookup veloce di FILECOUNT e FILEEXT da selectedItemsFull
111
+ const fileInfoMap = new Map(selectedItemsFull.map(d => [`${d.TID}_${d.DID}`, { fileExt: d.FILEEXT, fileCount: d.FILECOUNT }]));
112
+ // Mappa per i risultati: mantiene l'ordine originale
113
+ const pdfFilesMap = new Map();
114
+ // Identifica documenti di soli metadati e documenti con file
115
+ const metadataOnlyDcmts = [];
116
+ const dcmtsWithFile = [];
117
+ for (const dcmt of pdfDcmtInfosToDownload) {
118
+ const key = `${dcmt.TID}_${dcmt.DID}`;
119
+ const info = fileInfoMap.get(key);
120
+ if (isMetadataOnlyDcmt(info?.fileExt, info?.fileCount)) {
121
+ metadataOnlyDcmts.push(dcmt);
122
+ }
123
+ else {
124
+ dcmtsWithFile.push(dcmt);
125
+ }
126
+ }
127
+ // Per i documenti di soli metadati, genera PDF con createMetadataPdfFromDocument
128
+ // Raccoglie gli errori come downloadDcmtsAsync
129
+ const metadataConversionResults = [];
130
+ if (metadataOnlyDcmts.length > 0) {
131
+ setIsMergingPdf(true);
132
+ setMergeProgressMax(metadataOnlyDcmts.length);
133
+ setMergeProgressValue(0);
134
+ setMergeProgressText('Creazione PDF metadati...');
135
+ for (let i = 0; i < metadataOnlyDcmts.length; i++) {
136
+ const dcmt = metadataOnlyDcmts[i];
137
+ try {
138
+ setMergeProgressValue(i);
139
+ setMergeProgressText(`Creazione PDF metadati... (${i + 1}/${metadataOnlyDcmts.length})`);
140
+ const conversionResult = await createMetadataPdfFromDocument(dcmt);
141
+ if (conversionResult) {
142
+ pdfFilesMap.set(`${dcmt.TID}_${dcmt.DID}`, conversionResult.file);
143
+ metadataConversionResults.push({ rowIndex: i, id1: dcmt.TID, id2: dcmt.DID, resultType: ResultTypes.SUCCESS });
144
+ }
145
+ else {
146
+ // createMetadataPdfFromDocument ha restituito null (errore interno loggato)
147
+ metadataConversionResults.push({ rowIndex: i, id1: dcmt.TID, id2: dcmt.DID, resultType: ResultTypes.ERROR, description: 'Creazione PDF metadati fallita' });
148
+ }
149
+ }
150
+ catch (error) {
151
+ console.error(`Errore creazione PDF metadati per TID=${dcmt.TID}, DID=${dcmt.DID}`, error);
152
+ metadataConversionResults.push({ rowIndex: i, id1: dcmt.TID, id2: dcmt.DID, resultType: ResultTypes.ERROR, description: getExceptionMessage(error) });
153
+ }
154
+ }
155
+ setMergeProgressValue(metadataOnlyDcmts.length);
156
+ setIsMergingPdf(false);
157
+ // Mostra riepilogo errori se presenti (come downloadDcmtsAsync)
158
+ const hasErrors = metadataConversionResults.some(r => r.resultType === ResultTypes.ERROR);
159
+ if (hasErrors) {
160
+ TMResultManager.show(metadataConversionResults, 'Conversione PDF metadati', 'TID', 'DID', undefined, undefined, false);
161
+ }
162
+ }
163
+ // Per i documenti con file, usa il download normale
164
+ if (dcmtsWithFile.length > 0) {
165
+ const rfo = new RetrieveFileOptions();
166
+ rfo.retrieveReason = DcmtOpers.None;
167
+ rfo.generalRetrieveFormat = GeneralRetrieveFormats.OriginalUnsigned;
168
+ rfo.cvtFormat = FileFormats.PDF;
169
+ rfo.invoiceRetrieveFormat = SDKUI_Globals.userSettings?.searchSettings.invoiceRetrieveFormat;
170
+ rfo.orderRetrieveFormat = SDKUI_Globals.userSettings?.searchSettings.orderRetrieveFormat;
171
+ const collectFileForMerge = async (file, dcmtInfo) => {
172
+ pdfFilesMap.set(`${dcmtInfo.TID}_${dcmtInfo.DID}`, file);
173
+ };
174
+ await downloadDcmtsAsync({ inputDcmts: dcmtsWithFile, downloadType: DownloadTypes.Dcmt, downloadMode: 'download', onFileDownloaded: collectFileForMerge, skipConfirmation: true, retrieveOptions: rfo, useCache: false });
175
+ }
176
+ // Ricostruisci l'array pdfFiles nell'ordine originale di selezione
136
177
  const pdfFiles = [];
137
- const rfo = new RetrieveFileOptions();
138
- rfo.retrieveReason = DcmtOpers.None;
139
- rfo.generalRetrieveFormat = GeneralRetrieveFormats.OriginalUnsigned;
140
- rfo.cvtFormat = FileFormats.PDF;
141
- rfo.invoiceRetrieveFormat = SDKUI_Globals.userSettings?.searchSettings.invoiceRetrieveFormat;
142
- rfo.orderRetrieveFormat = SDKUI_Globals.userSettings?.searchSettings.orderRetrieveFormat;
143
- const collectFileForMerge = async (file, _dcmtInfo) => {
144
- pdfFiles.push(file);
145
- };
146
- await downloadDcmtsAsync({ inputDcmts: pdfDcmtInfosToDownload, downloadType: DownloadTypes.Dcmt, downloadMode: 'download', onFileDownloaded: collectFileForMerge, skipConfirmation: true, retrieveOptions: rfo, useCache: false });
178
+ for (const dcmt of pdfDcmtInfosToDownload) {
179
+ const key = `${dcmt.TID}_${dcmt.DID}`;
180
+ const file = pdfFilesMap.get(key);
181
+ if (file) {
182
+ pdfFiles.push(file);
183
+ }
184
+ }
147
185
  if (pdfFiles.length === 0) {
148
186
  TMMessageBoxManager.show({ message: SDKUI_Localizator.NoFilesAvailableForMerge, buttons: [ButtonNames.OK] });
149
187
  return null;
@@ -244,7 +282,7 @@ const TMMergeToPdfForm = ({ mode, selectedDcmtInfos, onClose, showTMRelationView
244
282
  width: 'max-content',
245
283
  backgroundColor: TMColors.default_background,
246
284
  zIndex: 1,
247
- }, children: SDKUI_Localizator.Path }), _jsxs("div", { style: { border: `1px solid ${TMColors.border_normal}`, borderRadius: '5px', padding: '4px 10px 4px 13px', display: 'flex', alignItems: 'center', gap: '8px', backgroundColor: '#fafafa' }, children: [_jsx(IconFolderOpen, {}), _jsx("span", { style: { fontSize: '0.9rem', color: '#333' }, children: "Download" })] }), _jsx("span", { style: { fontSize: '0.8rem', color: '#888', fontStyle: 'italic', marginTop: '4px', display: 'block' }, children: SDKUI_Localizator.BrowserDoesNotSupportFolderSelection })] }) })), _jsx("div", { style: { flex: '1.5 1 420px', minWidth: '350px', width: '100%' }, children: _jsx(TMTextBox, { label: SDKUI_Localizator.PdfFileName, value: pdfFileName, validationItems: pdfValidationItems, autoComplete: "one-time-code", onValueChanged: (e) => setPdfFileName(e.target.value) }) })] }), (hasConvertibleSelected || !hasEnoughPdfForMerge || hasNonPdfSelected) && (_jsxs("div", { style: {
285
+ }, children: SDKUI_Localizator.Path }), _jsxs("div", { style: { border: `1px solid ${TMColors.border_normal}`, borderRadius: '5px', padding: '4px 10px 4px 13px', display: 'flex', alignItems: 'center', gap: '8px', backgroundColor: '#fafafa' }, children: [_jsx(IconFolderOpen, {}), _jsx("span", { style: { fontSize: '0.9rem', color: '#333' }, children: "Download" })] }), _jsx("span", { style: { fontSize: '0.8rem', color: '#888', fontStyle: 'italic', marginTop: '4px', display: 'block' }, children: SDKUI_Localizator.BrowserDoesNotSupportFolderSelection })] }) })), _jsx("div", { style: { flex: '1.5 1 420px', minWidth: '350px', width: '100%' }, children: _jsx(TMTextBox, { label: SDKUI_Localizator.PdfFileName, value: pdfFileName, validationItems: pdfValidationItems, autoComplete: "one-time-code", onValueChanged: (e) => setPdfFileName(e.target.value) }) })] }), (hasConvertibleSelected || hasMetadataOnlySelected || !hasEnoughPdfForMerge || hasNonPdfSelected) && (_jsxs("div", { style: {
248
286
  display: 'flex',
249
287
  flexDirection: 'column',
250
288
  gap: '6px',
@@ -264,14 +302,19 @@ const TMMergeToPdfForm = ({ mode, selectedDcmtInfos, onClose, showTMRelationView
264
302
  borderBottom: '1px solid #e0e0e0',
265
303
  paddingBottom: '6px',
266
304
  marginBottom: '2px'
267
- }, children: SDKUI_Localizator.NotesAndWarnings }), _jsxs("ul", { style: { margin: 0, paddingLeft: '18px', display: 'flex', flexDirection: 'column', gap: '4px' }, children: [hasConvertibleSelected && (_jsx("li", { style: { color: '#00527a' }, children: (() => {
268
- const extSet = Array.from(new Set(convertibleSelectedItems.map(i => (i.ext ?? '').toString().trim().toLowerCase().replace(/^\./, '')).filter(e => e.length > 0)));
305
+ }, children: SDKUI_Localizator.NotesAndWarnings }), _jsxs("ul", { style: { margin: 0, paddingLeft: '18px', display: 'flex', flexDirection: 'column', gap: '4px' }, children: [(hasConvertibleSelected || hasMetadataOnlySelected) && (_jsx("li", { style: { color: '#00527a' }, children: (() => {
306
+ // Combina convertibili + metadataOnly
307
+ const allConvertible = [...convertibleSelectedItems, ...metadataOnlySelectedItems];
308
+ const extSet = Array.from(new Set([
309
+ ...convertibleSelectedItems.map(i => (i.ext ?? '').toString().trim().toLowerCase().replace(/^\./, '')).filter(e => e.length > 0),
310
+ ...(hasMetadataOnlySelected ? ['metadati'] : [])
311
+ ]));
269
312
  const extLabel = extSet.length > 0
270
313
  ? ` (${extSet.map(e => '.' + e).join(', ')})`
271
314
  : '';
272
- return convertibleSelectedItems.length === 1
315
+ return allConvertible.length === 1
273
316
  ? SDKUI_Localizator.DocumentWillBeConvertedDuringMerge.replaceParams(extLabel)
274
- : SDKUI_Localizator.DocumentsWillBeConvertedDuringMerge.replaceParams(convertibleSelectedItems.length.toString(), extLabel);
317
+ : SDKUI_Localizator.DocumentsWillBeConvertedDuringMerge.replaceParams(allConvertible.length.toString(), extLabel);
275
318
  })() })), !hasEnoughPdfForMerge && (_jsx("li", { style: { color: '#7a5d00' }, children: mergeableSelectedCount === 0
276
319
  ? SDKUI_Localizator.NoPdfOrConvertibleFilesSelected_Param.replaceParams(MIN_PDF_FOR_MERGE.toString())
277
320
  : SDKUI_Localizator.OnlyOnePdfOrConvertibleFileSelected.replaceParams(MIN_PDF_FOR_MERGE.toString()) })), hasNonPdfSelected && (_jsx("li", { style: { color: '#7a5d00' }, children: (() => {
@@ -0,0 +1,52 @@
1
+ import { DcmtInfo } from '../../../ts';
2
+ import { IRelatedDcmt } from './TMMasterDetailDcmts';
3
+ /** Tipo per gli elementi con chiave e estensione */
4
+ export interface ISelectedItemInfo {
5
+ key: string;
6
+ ext: string | undefined;
7
+ }
8
+ /** Risultato delle statistiche di selezione per il merge PDF */
9
+ export interface IMergePdfSelectionStats {
10
+ /** Elementi con estensioni convertibili in PDF (non nativamente PDF) */
11
+ convertibleSelectedItems: Array<ISelectedItemInfo>;
12
+ /** True se ci sono elementi convertibili selezionati */
13
+ hasConvertibleSelected: boolean;
14
+ /** Elementi che NON sono PDF e NON sono convertibili */
15
+ nonPdfSelectedItems: Array<ISelectedItemInfo>;
16
+ /** True se ci sono elementi non-PDF selezionati */
17
+ hasNonPdfSelected: boolean;
18
+ /** Documenti di soli metadati (senza file allegato) */
19
+ metadataOnlySelectedItems: Array<ISelectedItemInfo>;
20
+ /** True se ci sono documenti di soli metadati selezionati */
21
+ hasMetadataOnlySelected: boolean;
22
+ /** Numero totale di file unibili (PDF nativi + convertibili) */
23
+ mergeableSelectedCount: number;
24
+ /** True se ci sono abbastanza file per il merge (>= MIN_PDF_FOR_MERGE) */
25
+ hasEnoughPdfForMerge: boolean;
26
+ }
27
+ /** Documento di soli metadati: FILEEXT null/undefined oppure FILECOUNT 0 (verrà convertito frontend) */
28
+ export declare const isMetadataOnlyDcmt: (fileExt: string | null | undefined, fileCount: string | number | null | undefined) => boolean;
29
+ /**
30
+ * Calcola tutte le statistiche di selezione necessarie per il merge PDF.
31
+ * Restituisce info su file convertibili, non-PDF e conteggio file unibili.
32
+ *
33
+ * @param selectedDcmtInfos - Lista dei DcmtInfo selezionati (usata se showTMRelationViewer è false)
34
+ * @param selectedItemsFull - Lista completa con FILEEXT e FILECOUNT per check documenti di soli metadati
35
+ * @param selectedItemsRelationViewer - Lista degli IRelatedDcmt selezionati nel RelationViewer
36
+ * @param showTMRelationViewer - Se true, usa selectedItemsRelationViewer, altrimenti selectedDcmtInfos
37
+ */
38
+ export declare const getMergePdfSelectionStats: (selectedDcmtInfos: Array<DcmtInfo>, selectedItemsFull: Array<any>, selectedItemsRelationViewer: Array<IRelatedDcmt>, showTMRelationViewer: boolean) => IMergePdfSelectionStats;
39
+ export interface IMetadataKeyValue {
40
+ label: string;
41
+ value: string;
42
+ }
43
+ export interface IMetadataPdfResult {
44
+ file: File;
45
+ blob: Blob;
46
+ bytes: Uint8Array;
47
+ }
48
+ /** Crea PDF con metadati documento (header + lista label=valore). Supporta wrapping e paginazione.
49
+ * @param dcmtInfo - Informazioni del documento
50
+ * @param showSystemMetadata - Se true, include i metadati di sistema (mid < 100). Default: false
51
+ */
52
+ export declare const createMetadataPdfFromDocument: (dcmtInfo: DcmtInfo, showSystemMetadata?: boolean) => Promise<IMetadataPdfResult | null>;
@@ -0,0 +1,268 @@
1
+ import { DcmtTypeListCacheService, LayoutModes, MetadataDataTypes, SDK_Globals } from '@topconsultnpm/sdk-ts';
2
+ import { Globalization, isConvertibleToPdfExt, searchResultToMetadataValues } from '../../../helper';
3
+ import { isPdfExt, MIN_PDF_FOR_MERGE } from './copyAndMergeDcmtsShared';
4
+ /** Documento di soli metadati: FILEEXT null/undefined oppure FILECOUNT 0 (verrà convertito frontend) */
5
+ export const isMetadataOnlyDcmt = (fileExt, fileCount) => {
6
+ if (!fileExt)
7
+ return true;
8
+ return Number(fileCount) === 0;
9
+ };
10
+ /**
11
+ * Calcola tutte le statistiche di selezione necessarie per il merge PDF.
12
+ * Restituisce info su file convertibili, non-PDF e conteggio file unibili.
13
+ *
14
+ * @param selectedDcmtInfos - Lista dei DcmtInfo selezionati (usata se showTMRelationViewer è false)
15
+ * @param selectedItemsFull - Lista completa con FILEEXT e FILECOUNT per check documenti di soli metadati
16
+ * @param selectedItemsRelationViewer - Lista degli IRelatedDcmt selezionati nel RelationViewer
17
+ * @param showTMRelationViewer - Se true, usa selectedItemsRelationViewer, altrimenti selectedDcmtInfos
18
+ */
19
+ export const getMergePdfSelectionStats = (selectedDcmtInfos, selectedItemsFull, selectedItemsRelationViewer, showTMRelationViewer) => {
20
+ // Mappa per lookup veloce di FILECOUNT da selectedItemsFull (usato solo per isMetadataOnly)
21
+ const fileCountMap = new Map(selectedItemsFull.map(d => [`${d.TID}_${d.DID}`, d.FILECOUNT]));
22
+ // Pre-calcolo delle informazioni sui documenti selezionati
23
+ const docs = showTMRelationViewer
24
+ ? selectedItemsRelationViewer
25
+ .filter(i => i.isDcmt)
26
+ .map(i => {
27
+ const ext = i.fileExt ?? undefined;
28
+ return {
29
+ key: `${i.tid}_${i.did}`,
30
+ ext,
31
+ isPdf: isPdfExt(ext),
32
+ isConvertible: isConvertibleToPdfExt(ext),
33
+ isMetadataOnly: isMetadataOnlyDcmt(ext, null)
34
+ };
35
+ })
36
+ : selectedDcmtInfos.map(d => {
37
+ const ext = d.FILEEXT ?? undefined;
38
+ const key = `${d.TID}_${d.DID}`;
39
+ const fileCount = fileCountMap.get(key);
40
+ return {
41
+ key,
42
+ ext,
43
+ isPdf: isPdfExt(ext),
44
+ isConvertible: isConvertibleToPdfExt(ext),
45
+ isMetadataOnly: isMetadataOnlyDcmt(ext, fileCount)
46
+ };
47
+ });
48
+ // Elementi convertibili: file con estensione convertibile (esclusi documenti di soli metadati)
49
+ const convertibleSelectedItems = docs
50
+ .filter(i => i.isConvertible && !i.isMetadataOnly)
51
+ .map(i => ({ key: i.key, ext: i.ext }));
52
+ // Elementi NON PDF e NON convertibili (esclusi documenti di soli metadati)
53
+ const nonPdfSelectedItems = docs
54
+ .filter(i => !i.isPdf && !i.isConvertible && !i.isMetadataOnly)
55
+ .map(i => ({ key: i.key, ext: i.ext }));
56
+ // Documenti di soli metadati (senza file allegato)
57
+ const metadataOnlySelectedItems = docs
58
+ .filter(i => i.isMetadataOnly)
59
+ .map(i => ({ key: i.key, ext: i.ext }));
60
+ // Conteggio file unibili: PDF nativi + convertibili + documenti di soli metadati
61
+ const mergeableSelectedCount = docs
62
+ .filter(i => i.isPdf || i.isConvertible || i.isMetadataOnly).length;
63
+ return {
64
+ convertibleSelectedItems,
65
+ hasConvertibleSelected: convertibleSelectedItems.length > 0,
66
+ nonPdfSelectedItems,
67
+ hasNonPdfSelected: nonPdfSelectedItems.length > 0,
68
+ metadataOnlySelectedItems,
69
+ hasMetadataOnlySelected: metadataOnlySelectedItems.length > 0,
70
+ mergeableSelectedCount,
71
+ hasEnoughPdfForMerge: mergeableSelectedCount >= MIN_PDF_FOR_MERGE,
72
+ };
73
+ };
74
+ // LAZY LOADING pdf-lib
75
+ let pdfLibModule = null;
76
+ const loadPdfLib = async () => {
77
+ if (!pdfLibModule) {
78
+ pdfLibModule = await import('pdf-lib');
79
+ }
80
+ return pdfLibModule;
81
+ };
82
+ // HELPER FUNCTIONS
83
+ /** Ottiene label localizzata del metadato (customName > nameLoc > name) */
84
+ const getMetadataLabel = (md, customName) => {
85
+ if (customName)
86
+ return customName;
87
+ if (!md)
88
+ return '';
89
+ return md.nameLoc || md.name || '';
90
+ };
91
+ /** Formatta valore metadato per PDF (null/undefined -> stringa vuota, datetime -> dd/MM/yyyy HH:mm) */
92
+ const formatMetadataValueForPdf = (value, md) => {
93
+ if (value === undefined || value === null)
94
+ return '';
95
+ // Se il metadato è di tipo DateTime, formatta in modo leggibile usando Globalization
96
+ if (md?.dataType === MetadataDataTypes.DateTime) {
97
+ return Globalization.getDateTimeDisplayValueCompact(value, undefined, md.format?.format);
98
+ }
99
+ return value.toString().trim();
100
+ };
101
+ // ============================================================================
102
+ // CREAZIONE PDF DA METADATI DOCUMENTO
103
+ // ============================================================================
104
+ /** Crea PDF con metadati documento (header + lista label=valore). Supporta wrapping e paginazione.
105
+ * @param dcmtInfo - Informazioni del documento
106
+ * @param showSystemMetadata - Se true, include i metadati di sistema (mid < 100). Default: false
107
+ */
108
+ export const createMetadataPdfFromDocument = async (dcmtInfo, showSystemMetadata = false) => {
109
+ // Configurazione ottimale per PDF A4
110
+ const pageWidth = 595; // A4 width in points
111
+ const pageHeight = 842; // A4 height in points
112
+ const margin = 50; // Margine confortevole
113
+ const titleFontSize = 14; // Titolo leggibile ma non troppo grande
114
+ const contentFontSize = 10; // Font contenuto ottimale per leggibilità
115
+ try {
116
+ // Recupera i metadati del documento
117
+ const metadata = await SDK_Globals.tmSession?.NewSearchEngine().GetMetadataAsync(dcmtInfo.TID, dcmtInfo.DID, true);
118
+ if (!metadata) {
119
+ console.warn('createMetadataPdfFromDocument: impossibile recuperare i metadati');
120
+ return null;
121
+ }
122
+ const dtdResult = metadata.dtdResult;
123
+ const rows = dtdResult?.rows?.[0] ?? [];
124
+ const mids = metadata.selectMIDs;
125
+ // Recupera il tipo documento con metadati
126
+ const dtdWithMetadata = await DcmtTypeListCacheService.GetWithNotGrantedAsync(dcmtInfo.TID, dcmtInfo.DID, metadata);
127
+ if (!dtdWithMetadata) {
128
+ console.warn('createMetadataPdfFromDocument: impossibile recuperare il tipo documento');
129
+ return null;
130
+ }
131
+ const mdList = dtdWithMetadata.metadata ?? [];
132
+ // Converte i metadati in formato leggibile
133
+ const metadataList = searchResultToMetadataValues(dcmtInfo.TID, dtdResult, rows, mids, mdList, LayoutModes.Update);
134
+ // Ottiene il nome del tipo documento (localizzato se abilitato)
135
+ const dcmtTypeName = dtdWithMetadata.nameLoc || dtdWithMetadata.name || 'Documento';
136
+ // Prepara la lista di metadati chiave-valore
137
+ const metadataKeyValues = metadataList
138
+ .filter(mvd => mvd.md && mvd.mid && mvd.mid > 0) // Filtra solo metadati validi
139
+ .filter(mvd => showSystemMetadata || (mvd.mid && mvd.mid >= 100)) // Filtra metadati di sistema se showSystemMetadata è false
140
+ .map(mvd => ({
141
+ label: getMetadataLabel(mvd.md, mvd.customName),
142
+ value: formatMetadataValueForPdf(mvd.value, mvd.md)
143
+ }))
144
+ .filter(kv => kv.label.length > 0); // Rimuovi metadati senza label
145
+ // Carica pdf-lib on-demand
146
+ const { PDFDocument, StandardFonts, rgb } = await loadPdfLib();
147
+ // Crea il documento PDF
148
+ const pdfDoc = await PDFDocument.create();
149
+ const helveticaFont = await pdfDoc.embedFont(StandardFonts.Helvetica);
150
+ const helveticaBoldFont = await pdfDoc.embedFont(StandardFonts.HelveticaBold);
151
+ // Calcola dimensioni
152
+ const contentWidth = pageWidth - (margin * 2);
153
+ const lineHeight = contentFontSize + 5;
154
+ const titleLineHeight = titleFontSize + 6;
155
+ // Funzione per aggiungere una nuova pagina
156
+ const addPage = () => {
157
+ const page = pdfDoc.addPage([pageWidth, pageHeight]);
158
+ return { page, yPosition: pageHeight - margin };
159
+ };
160
+ // Funzione per calcolare la larghezza del testo
161
+ const getTextWidth = (text, font, size) => {
162
+ try {
163
+ return font.widthOfTextAtSize(text, size);
164
+ }
165
+ catch {
166
+ // Fallback per caratteri non supportati
167
+ return text.length * size * 0.5;
168
+ }
169
+ };
170
+ // Funzione per spezzare il testo in righe che entrano nella larghezza disponibile
171
+ const wrapText = (text, font, fontSize, maxWidth) => {
172
+ const lines = [];
173
+ const words = text.split(' ');
174
+ let currentLine = '';
175
+ for (const word of words) {
176
+ const testLine = currentLine ? `${currentLine} ${word}` : word;
177
+ const testWidth = getTextWidth(testLine, font, fontSize);
178
+ if (testWidth > maxWidth && currentLine) {
179
+ lines.push(currentLine);
180
+ currentLine = word;
181
+ }
182
+ else {
183
+ currentLine = testLine;
184
+ }
185
+ }
186
+ if (currentLine) {
187
+ lines.push(currentLine);
188
+ }
189
+ // Se una singola parola è troppo lunga, spezzala in più righe (carattere per carattere)
190
+ const result = [];
191
+ for (const line of lines) {
192
+ if (getTextWidth(line, font, fontSize) <= maxWidth) {
193
+ result.push(line);
194
+ }
195
+ else {
196
+ // Spezza la parola/linea troppo lunga carattere per carattere
197
+ let chunk = '';
198
+ for (const char of line) {
199
+ const testChunk = chunk + char;
200
+ if (getTextWidth(testChunk, font, fontSize) > maxWidth && chunk) {
201
+ result.push(chunk);
202
+ chunk = char;
203
+ }
204
+ else {
205
+ chunk = testChunk;
206
+ }
207
+ }
208
+ if (chunk) {
209
+ result.push(chunk);
210
+ }
211
+ }
212
+ }
213
+ return result;
214
+ };
215
+ // Sanitizza testo per Helvetica (solo ASCII)
216
+ const sanitize = (text) => text.replace(/[^\x20-\x7E]/g, '_');
217
+ let { page, yPosition } = addPage();
218
+ // Header (titolo centrato)
219
+ const titleText = sanitize(`${dcmtTypeName}`);
220
+ const titleWidth = getTextWidth(titleText, helveticaBoldFont, titleFontSize);
221
+ const titleX = Math.max(margin, margin + (contentWidth - titleWidth) / 2);
222
+ page.drawText(titleText, { x: titleX, y: yPosition, size: titleFontSize, font: helveticaBoldFont, color: rgb(0, 0, 0) });
223
+ yPosition -= titleLineHeight + 8;
224
+ // Metadati (label in grassetto, valore normale)
225
+ for (const kv of metadataKeyValues) {
226
+ const labelText = sanitize(kv.label) + ': ';
227
+ const labelWidth = getTextWidth(labelText, helveticaBoldFont, contentFontSize);
228
+ const valueMaxWidth = contentWidth - labelWidth;
229
+ const continuationIndent = labelWidth; // Indentazione per righe successive
230
+ // Wrappa il valore
231
+ const valueText = sanitize(kv.value);
232
+ const valueLines = wrapText(valueText, helveticaFont, contentFontSize, valueMaxWidth);
233
+ // Prima riga: label (bold) + primo pezzo di valore
234
+ if (yPosition < margin + lineHeight) {
235
+ ({ page, yPosition } = addPage());
236
+ }
237
+ page.drawText(labelText, { x: margin, y: yPosition, size: contentFontSize, font: helveticaBoldFont, color: rgb(0, 0, 0) });
238
+ if (valueLines.length > 0) {
239
+ page.drawText(valueLines[0], { x: margin + labelWidth, y: yPosition, size: contentFontSize, font: helveticaFont, color: rgb(0, 0, 0) });
240
+ }
241
+ yPosition -= lineHeight;
242
+ // Righe successive del valore (indentate)
243
+ for (let i = 1; i < valueLines.length; i++) {
244
+ if (yPosition < margin + lineHeight) {
245
+ ({ page, yPosition } = addPage());
246
+ }
247
+ page.drawText(valueLines[i], { x: margin + continuationIndent, y: yPosition, size: contentFontSize, font: helveticaFont, color: rgb(0, 0, 0) });
248
+ yPosition -= lineHeight;
249
+ }
250
+ }
251
+ // Salva il PDF
252
+ const pdfBytes = await pdfDoc.save();
253
+ // Converte in Blob e File
254
+ const pdfBuffer = pdfBytes.buffer.slice(pdfBytes.byteOffset, pdfBytes.byteOffset + pdfBytes.byteLength);
255
+ const blob = new Blob([pdfBuffer], { type: 'application/pdf' });
256
+ const finalFileName = `${dcmtTypeName}_${dcmtInfo.DID}.pdf`;
257
+ const file = new File([blob], finalFileName, { type: 'application/pdf' });
258
+ return {
259
+ file,
260
+ blob,
261
+ bytes: pdfBytes
262
+ };
263
+ }
264
+ catch (error) {
265
+ console.error('createMetadataPdfFromDocument: errore durante la creazione del PDF', error);
266
+ return null;
267
+ }
268
+ };
@@ -25,6 +25,6 @@ interface ITMSearchQueryPanelProps {
25
25
  declare const TMSearchQueryPanel: React.FunctionComponent<ITMSearchQueryPanelProps>;
26
26
  export default TMSearchQueryPanel;
27
27
  export declare const refreshLastSearch: (qd: QueryDescriptor | undefined) => Promise<SearchResultDescriptor[] | undefined>;
28
- export declare const StyledToppyTextContainer: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>> & string;
29
- export declare const StyledToppyText: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>, never>> & string;
30
- export declare const StyledToppyImage: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<React.DetailedHTMLProps<React.ImgHTMLAttributes<HTMLImageElement>, HTMLImageElement>, never>> & string;
28
+ export declare const StyledToppyTextContainer: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never> & Partial<Pick<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>>> & string;
29
+ export declare const StyledToppyText: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>, never> & Partial<Pick<React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>, never>>> & string;
30
+ export declare const StyledToppyImage: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<React.DetailedHTMLProps<React.ImgHTMLAttributes<HTMLImageElement>, HTMLImageElement>, never> & Partial<Pick<React.DetailedHTMLProps<React.ImgHTMLAttributes<HTMLImageElement>, HTMLImageElement>, never>>> & string;
@@ -1,7 +1,7 @@
1
1
  import React from 'react';
2
2
  import { QueryDescriptor } from '@topconsultnpm/sdk-ts';
3
3
  import { WorkItemActor } from './RecipientList';
4
- export declare const RecipientsContainer: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>> & string;
4
+ export declare const RecipientsContainer: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never> & Partial<Pick<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>>> & string;
5
5
  export declare const tosToActors: (tosString: string) => {
6
6
  andRecipients: WorkItemActor[];
7
7
  orRecipients: WorkItemActor[];
@@ -11,4 +11,4 @@ interface ChangePasswordInputsProps {
11
11
  }
12
12
  declare const ChangePasswordInputs: React.FC<ChangePasswordInputsProps>;
13
13
  export default ChangePasswordInputs;
14
- export declare const Divider: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>> & string;
14
+ export declare const Divider: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never> & Partial<Pick<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>>> & string;
@@ -1,9 +1,12 @@
1
1
  import React from 'react';
2
2
  import { TMPanelDefinition } from './types';
3
- export declare const StyledToolbarButton: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components/dist/types").Substitute<React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, {
3
+ export declare const StyledToolbarButton: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("styled-components").FastOmit<React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, "$isDisabled" | "$isActive"> & {
4
4
  $isActive?: boolean;
5
5
  $isDisabled?: boolean;
6
- }>> & string;
6
+ }, never> & Partial<Pick<import("styled-components").FastOmit<React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, "$isDisabled" | "$isActive"> & {
7
+ $isActive?: boolean;
8
+ $isDisabled?: boolean;
9
+ }, never>>> & string;
7
10
  interface TMPanelManagerToolbarProps {
8
11
  panels: Array<TMPanelDefinition>;
9
12
  }
@@ -2,14 +2,18 @@ import React from 'react';
2
2
  import { QueryDescriptor, ValidationItem } from '@topconsultnpm/sdk-ts';
3
3
  import { ITMApplyFormProps, FormModes } from '../../ts';
4
4
  import type { TMContextMenuItemProps } from '../NewComponents/ContextMenu/types';
5
- export declare const StyledRowItem: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>> & string;
6
- export declare const StyledItemWrapper: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components/dist/types").Substitute<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, {
5
+ export declare const StyledRowItem: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never> & Partial<Pick<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>>> & string;
6
+ export declare const StyledItemWrapper: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("styled-components").FastOmit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "$borderRadius"> & {
7
7
  $borderRadius?: string;
8
- }>> & string;
9
- export declare const StyledAccordionItemContainer: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>> & string;
10
- export declare const StyledAccordionItemContent: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components/dist/types").Substitute<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, {
8
+ }, never> & Partial<Pick<import("styled-components").FastOmit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "$borderRadius"> & {
9
+ $borderRadius?: string;
10
+ }, never>>> & string;
11
+ export declare const StyledAccordionItemContainer: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never> & Partial<Pick<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>>> & string;
12
+ export declare const StyledAccordionItemContent: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("styled-components").FastOmit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "$disabled"> & {
13
+ $disabled?: boolean;
14
+ }, never> & Partial<Pick<import("styled-components").FastOmit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "$disabled"> & {
11
15
  $disabled?: boolean;
12
- }>> & string;
16
+ }, never>>> & string;
13
17
  export declare const colorValue = "rgba(224,224,224,.5)";
14
18
  export declare const colorBrackets = "rgba(209,52,56,.3)";
15
19
  export declare const colorOperator = "rgba(16,124,16,.3)";