@topconsultnpm/sdkui-react 6.21.0-dev2.32 → 6.21.0-dev2.34

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.
Files changed (32) hide show
  1. package/lib/components/editors/TMTextBox.d.ts +2 -0
  2. package/lib/components/editors/TMTextBox.js +3 -3
  3. package/lib/components/features/documents/TMCopyToFolderForm.d.ts +16 -0
  4. package/lib/components/features/documents/TMCopyToFolderForm.js +306 -0
  5. package/lib/components/features/documents/TMDownloadRelationViewerSection.d.ts +15 -0
  6. package/lib/components/features/documents/TMDownloadRelationViewerSection.js +155 -0
  7. package/lib/components/features/documents/TMMasterDetailDcmts.d.ts +4 -0
  8. package/lib/components/features/documents/TMMasterDetailDcmts.js +6 -5
  9. package/lib/components/features/documents/TMMergeToPdfForm.d.ts +18 -0
  10. package/lib/components/features/documents/TMMergeToPdfForm.js +164 -0
  11. package/lib/components/features/documents/TMRelationViewer.d.ts +13 -0
  12. package/lib/components/features/documents/TMRelationViewer.js +75 -6
  13. package/lib/components/features/documents/copyAndMergeDcmtsShared.d.ts +53 -0
  14. package/lib/components/features/documents/copyAndMergeDcmtsShared.js +263 -0
  15. package/lib/components/features/search/TMSearch.d.ts +2 -0
  16. package/lib/components/features/search/TMSearch.js +2 -2
  17. package/lib/components/features/search/TMSearchResult.d.ts +2 -0
  18. package/lib/components/features/search/TMSearchResult.js +3 -2
  19. package/lib/components/forms/Login/TMLoginForm.d.ts +9 -0
  20. package/lib/components/forms/Login/TMLoginForm.js +44 -0
  21. package/lib/helper/SDKUI_Globals.d.ts +16 -0
  22. package/lib/helper/SDKUI_Globals.js +16 -1
  23. package/lib/helper/TMUtils.d.ts +19 -0
  24. package/lib/helper/ZipManager.d.ts +56 -0
  25. package/lib/helper/ZipManager.js +104 -0
  26. package/lib/helper/index.d.ts +1 -0
  27. package/lib/helper/index.js +1 -0
  28. package/lib/hooks/useDcmtOperations.d.ts +8 -2
  29. package/lib/hooks/useDcmtOperations.js +29 -20
  30. package/lib/hooks/useDocumentOperations.d.ts +4 -0
  31. package/lib/hooks/useDocumentOperations.js +75 -7
  32. package/package.json +2 -1
@@ -0,0 +1,263 @@
1
+ import { DcmtTypeListCacheService, LayoutModes, SDK_Globals } from '@topconsultnpm/sdk-ts';
2
+ import { searchResultToMetadataValues, DocumentDownloadSettings } from '../../../helper';
3
+ import { TMColors } from '../../../utils/theme';
4
+ // ============================================================
5
+ // Costanti condivise tra TMCopyToFolderForm e TMMergeToPdfForm
6
+ // ============================================================
7
+ export const SPLITTER_MIN = ['50', '50'];
8
+ export const SPLITTER_START_HALF = ['50%', '50%'];
9
+ export const SPLITTER_START_65_35 = ['65%', '35%'];
10
+ /** Numero minimo di file PDF necessari per poterli unire in un unico documento. */
11
+ export const MIN_PDF_FOR_MERGE = 2;
12
+ // ============================================================
13
+ // Stili comuni
14
+ // ============================================================
15
+ /** Stile per le etichette "floating" posizionate sopra i bordi dei container */
16
+ export const getFloatingLabelStyle = () => ({
17
+ position: 'absolute',
18
+ top: '-10px',
19
+ left: '12px',
20
+ maxWidth: 'calc(100% - 24px)',
21
+ fontSize: '0.85rem',
22
+ fontWeight: 600,
23
+ color: TMColors.primary,
24
+ padding: '0 8px',
25
+ backgroundColor: 'white',
26
+ zIndex: 1,
27
+ whiteSpace: 'nowrap',
28
+ overflow: 'hidden',
29
+ textOverflow: 'ellipsis',
30
+ display: 'inline-block',
31
+ width: 'fit-content',
32
+ lineHeight: '20px',
33
+ letterSpacing: '0.2px',
34
+ boxSizing: 'border-box'
35
+ });
36
+ // ============================================================
37
+ // Helper su IRelatedDcmt
38
+ // ============================================================
39
+ /** Chiave univoca per un IRelatedDcmt basata sulla coppia tid+did */
40
+ export const getDcmtKey = (item) => `${item.tid ?? ''}_${item.did ?? ''}`;
41
+ /** Deduplica gli IRelatedDcmt in base alla coppia tid+did (mantiene la prima occorrenza) */
42
+ export const dedupeByTidDid = (items) => {
43
+ const map = new Map();
44
+ for (const item of items) {
45
+ const key = getDcmtKey(item);
46
+ if (!map.has(key))
47
+ map.set(key, item);
48
+ }
49
+ return Array.from(map.values());
50
+ };
51
+ // ============================================================
52
+ // Feature detection / Estensioni file
53
+ // ============================================================
54
+ /** Verifica se showDirectoryPicker è supportato (Chrome, Edge) */
55
+ export const isDirectoryPickerSupported = () => {
56
+ return 'showDirectoryPicker' in window;
57
+ };
58
+ /**
59
+ * Verifica se un'estensione (con o senza punto iniziale, case-insensitive) è "pdf".
60
+ */
61
+ export const isPdfExt = (ext) => {
62
+ if (!ext)
63
+ return false;
64
+ const normalized = ext.trim().toLowerCase().replace(/^\./, '');
65
+ return normalized === 'pdf';
66
+ };
67
+ // ============================================================
68
+ // Sanitizzazione e gestione nomi file
69
+ // ============================================================
70
+ /** Sanitizza il nome file per Windows (rimuove caratteri illegali e limita la lunghezza) */
71
+ export const sanitizeFileName = (fileName, fallbackName, maxLength = 255) => {
72
+ try {
73
+ const illegalCharsRegex = /[<>:"/\\|?*]/g;
74
+ const controlCharsRegex = /[\x00-\x1F\x7F]/g;
75
+ let sanitized = fileName
76
+ .replace(illegalCharsRegex, '_')
77
+ .replace(controlCharsRegex, '')
78
+ .trim();
79
+ sanitized = sanitized.replace(/[. ]+$/, '');
80
+ if (sanitized.length > maxLength) {
81
+ const lastDotIndex = sanitized.lastIndexOf('.');
82
+ if (lastDotIndex > 0) {
83
+ const extension = sanitized.substring(lastDotIndex);
84
+ const baseName = sanitized.substring(0, lastDotIndex);
85
+ const maxBaseLength = maxLength - extension.length;
86
+ sanitized = baseName.substring(0, maxBaseLength) + extension;
87
+ }
88
+ else {
89
+ sanitized = sanitized.substring(0, maxLength);
90
+ }
91
+ }
92
+ return sanitized || fallbackName;
93
+ }
94
+ catch {
95
+ return fallbackName;
96
+ }
97
+ };
98
+ /** Verifica se un file esiste nella cartella (FileSystemDirectoryHandle) */
99
+ export const fileExists = async (dirHandle, fileName) => {
100
+ try {
101
+ await dirHandle.getFileHandle(fileName, { create: false });
102
+ return true;
103
+ }
104
+ catch {
105
+ return false;
106
+ }
107
+ };
108
+ /** Genera un nome file univoco aggiungendo un suffisso numerico se già esistente */
109
+ export const generateUniqueFileName = async (dirHandle, originalName) => {
110
+ const splitName = (name) => {
111
+ const lastDotIndex = name.lastIndexOf('.');
112
+ const baseName = lastDotIndex > 0 ? name.substring(0, lastDotIndex) : name;
113
+ const extension = lastDotIndex > 0 ? name.substring(lastDotIndex) : '';
114
+ return { baseName, extension };
115
+ };
116
+ try {
117
+ const { baseName, extension } = splitName(originalName);
118
+ const MAX_ATTEMPTS = 1000;
119
+ for (let counter = 1; counter <= MAX_ATTEMPTS; counter++) {
120
+ const candidateName = counter === 1 ? originalName : `${baseName} (${counter - 1})${extension}`;
121
+ try {
122
+ await dirHandle.getFileHandle(candidateName, { create: false });
123
+ }
124
+ catch {
125
+ return candidateName;
126
+ }
127
+ }
128
+ return `${baseName}_${Date.now()}${extension}`;
129
+ }
130
+ catch {
131
+ const { baseName, extension } = splitName(originalName);
132
+ return `${baseName}_${Date.now()}${extension}`;
133
+ }
134
+ };
135
+ // ============================================================
136
+ // Helper per la generazione del nome di destinazione del file
137
+ // (in base alle impostazioni utente: tipo, metadati, separatore...)
138
+ // ============================================================
139
+ /** Recupera il nome leggibile del tipo documento (cache) */
140
+ export const getTypeName = async (tid) => {
141
+ const typeList = await DcmtTypeListCacheService.GetAllWithoutMetadataAsync();
142
+ const foundDtd = typeList.find(dtd => dtd.id?.toString() === tid?.toString());
143
+ return foundDtd?.name ?? String(tid);
144
+ };
145
+ /** Formatta un valore convertendo le date in formato dd-MM-yyyy [HH-mm-ss] */
146
+ export const formatMetadataValue = (value) => {
147
+ const date = new Date(value);
148
+ if (!isNaN(date.getTime())) {
149
+ const day = date.getDate().toString().padStart(2, '0');
150
+ const month = (date.getMonth() + 1).toString().padStart(2, '0');
151
+ const year = date.getFullYear();
152
+ const hours = date.getHours();
153
+ const minutes = date.getMinutes();
154
+ const seconds = date.getSeconds();
155
+ if (hours !== 0 || minutes !== 0 || seconds !== 0) {
156
+ const hh = hours.toString().padStart(2, '0');
157
+ const mm = minutes.toString().padStart(2, '0');
158
+ const ss = seconds.toString().padStart(2, '0');
159
+ return `${day}-${month}-${year} ${hh}-${mm}-${ss}`;
160
+ }
161
+ return `${day}-${month}-${year}`;
162
+ }
163
+ return value;
164
+ };
165
+ /** Recupera i metadati filtrati (primi 5 con mid > 100 e valore presente), concatenati con separatorChar */
166
+ export const getFilteredMetadata = async (tid, did, separatorChar) => {
167
+ const metadata = await SDK_Globals.tmSession?.NewSearchEngine().GetMetadataAsync(tid, did, true);
168
+ if (!metadata)
169
+ return null;
170
+ const dtdResult = metadata.dtdResult;
171
+ const rows = dtdResult?.rows?.[0] ?? [];
172
+ const mids = metadata.selectMIDs;
173
+ const dtdWithMetadata = await DcmtTypeListCacheService.GetWithNotGrantedAsync(tid, did, metadata);
174
+ const mdList = dtdWithMetadata?.metadata ?? [];
175
+ const metadataList = searchResultToMetadataValues(tid, dtdResult, rows, mids, mdList, LayoutModes.Update);
176
+ return metadataList
177
+ .reduce((acc, md) => acc.length < 5 && md.mid && md.mid > 100 && md.value ? [...acc, formatMetadataValue(md.value)] : acc, [])
178
+ .join(separatorChar);
179
+ };
180
+ /** Genera il nome file di destinazione in base alle impostazioni di naming */
181
+ export const generateTargetFileName = async (file, dcmtInfo, options) => {
182
+ const { fileNamingMode, separatorChar } = options;
183
+ const lastDotIndex = file.name.lastIndexOf('.');
184
+ const fileExtension = lastDotIndex > 0 ? file.name.substring(lastDotIndex) : '';
185
+ const fallbackName = dcmtInfo.DID + fileExtension;
186
+ let targetFileName = file.name;
187
+ switch (fileNamingMode) {
188
+ case 'onlyDid':
189
+ targetFileName = fallbackName;
190
+ break;
191
+ case 'documentTypeAndDid': {
192
+ const typeName = await getTypeName(dcmtInfo.TID);
193
+ targetFileName = `${typeName}${separatorChar}${dcmtInfo.DID}${fileExtension}`;
194
+ break;
195
+ }
196
+ case 'documentTypeAndCustomMetadata':
197
+ case 'onlyCustomMetadata': {
198
+ try {
199
+ const filteredMetadata = await getFilteredMetadata(dcmtInfo.TID, dcmtInfo.DID, separatorChar);
200
+ if (filteredMetadata) {
201
+ if (fileNamingMode === 'documentTypeAndCustomMetadata') {
202
+ const typeName = await getTypeName(dcmtInfo.TID);
203
+ targetFileName = `${typeName}${separatorChar}${filteredMetadata}${fileExtension}`;
204
+ }
205
+ else {
206
+ targetFileName = `${filteredMetadata}${fileExtension}`;
207
+ }
208
+ }
209
+ else {
210
+ targetFileName = fallbackName;
211
+ }
212
+ }
213
+ catch {
214
+ targetFileName = fallbackName;
215
+ }
216
+ break;
217
+ }
218
+ default:
219
+ break;
220
+ }
221
+ return sanitizeFileName(targetFileName, fallbackName);
222
+ };
223
+ // ============================================================
224
+ // Build della lista di DcmtInfo effettivi da scaricare
225
+ // ============================================================
226
+ /**
227
+ * Restituisce i documenti da scaricare:
228
+ * - se è visibile il TMRelationViewer, usa gli isDcmt selezionati al suo interno
229
+ * - altrimenti restituisce i selectedDcmtInfos passati come prop
230
+ */
231
+ export const getDcmtInfosToDownload = (selectedDcmtInfos, selectedItemsRelationViewer, showTMRelationViewer) => {
232
+ if (showTMRelationViewer) {
233
+ return selectedItemsRelationViewer
234
+ .filter(i => i.isDcmt && i.tid != null && i.did != null)
235
+ .map(i => ({ TID: i.tid, DID: i.did }));
236
+ }
237
+ return selectedDcmtInfos;
238
+ };
239
+ // ============================================================
240
+ // Stato iniziale settings (con valori salvati o default)
241
+ // ============================================================
242
+ /**
243
+ * Costruisce lo stato iniziale di DocumentDownloadSettings combinando:
244
+ * - i valori salvati in userSettings (se presenti)
245
+ * - i default della classe
246
+ * Nota: destinationFolder e zipPassword vengono sempre resettati per sicurezza.
247
+ */
248
+ export const buildInitialDownloadSettings = (saved, defaultInvoiceFormat, defaultOrderFormat) => {
249
+ const defaults = new DocumentDownloadSettings();
250
+ return {
251
+ exportMode: saved?.exportMode ?? defaults.exportMode,
252
+ destinationFolder: 'Download',
253
+ zipFileName: saved?.zipFileName ?? defaults.zipFileName,
254
+ zipPassword: '',
255
+ fileNamingMode: saved?.fileNamingMode ?? defaults.fileNamingMode,
256
+ separatorChar: saved?.separatorChar ?? defaults.separatorChar,
257
+ invoiceFormat: saved?.invoiceFormat ?? defaultInvoiceFormat,
258
+ orderFormat: saved?.orderFormat ?? defaultOrderFormat,
259
+ fileExistsMode: saved?.fileExistsMode ?? defaults.fileExistsMode,
260
+ removeSignature: saved?.removeSignature ?? defaults.removeSignature,
261
+ pdfFileName: saved?.pdfFileName ?? defaults.pdfFileName,
262
+ };
263
+ };
@@ -1,5 +1,6 @@
1
1
  import React from 'react';
2
2
  import { SavedQueryDescriptor, DcmtTypeDescriptor, TaskDescriptor, ObjectRef, HomeBlogPost } from '@topconsultnpm/sdk-ts';
3
+ import { MergePdfManagerType } from '../../../helper';
3
4
  import { DcmtInfo, TaskContext } from '../../../ts';
4
5
  import { TMSearchResultFloatingActionConfig } from './TMSearchResultFloatingActionButton';
5
6
  interface ITMSearchProps {
@@ -40,6 +41,7 @@ interface ITMSearchProps {
40
41
  onCurrentTIDChangedCallback?: (tid: number | undefined) => void;
41
42
  onlyShowSearchQueryPanel?: boolean;
42
43
  onReferenceClick?: (ref: ObjectRef) => void;
44
+ mergePdfManager?: MergePdfManagerType;
43
45
  }
44
46
  declare const TMSearch: React.FunctionComponent<ITMSearchProps>;
45
47
  export default TMSearch;
@@ -20,7 +20,7 @@ var TMSearchViews;
20
20
  TMSearchViews[TMSearchViews["Search"] = 0] = "Search";
21
21
  TMSearchViews[TMSearchViews["Result"] = 1] = "Result";
22
22
  })(TMSearchViews || (TMSearchViews = {}));
23
- const TMSearch = ({ allTasks = [], getAllTasks, deleteTaskByIdsCallback, addTaskCallback, editTaskCallback, handleNavigateToWGs, handleNavigateToDossiers, openInOffice, isVisible, inputTID, inputSqdID, inputMids, isExpertMode = SDKUI_Globals.userSettings.advancedSettings.expertMode === 1, floatingActionConfig, onFileOpened, onRefreshAfterAddDcmtToFavs, onTaskCreateRequest, openWGsCopyMoveForm, editPdfForm = false, openS4TViewer, onOpenS4TViewerRequest, onOpenPdfEditorRequest, openFileUploaderPdfEditor, showTodoDcmtForm, showToppyDraggableHelpCenter = true, toppyHelpCenterUsePortal = false, passToArchiveCallback, onCurrentTIDChangedCallback, onlyShowSearchQueryPanel, onReferenceClick, refreshFavoriteSavedQueries }) => {
23
+ const TMSearch = ({ allTasks = [], getAllTasks, deleteTaskByIdsCallback, addTaskCallback, editTaskCallback, handleNavigateToWGs, handleNavigateToDossiers, openInOffice, isVisible, inputTID, inputSqdID, inputMids, isExpertMode = SDKUI_Globals.userSettings.advancedSettings.expertMode === 1, floatingActionConfig, onFileOpened, onRefreshAfterAddDcmtToFavs, onTaskCreateRequest, openWGsCopyMoveForm, editPdfForm = false, openS4TViewer, onOpenS4TViewerRequest, onOpenPdfEditorRequest, openFileUploaderPdfEditor, showTodoDcmtForm, showToppyDraggableHelpCenter = true, toppyHelpCenterUsePortal = false, passToArchiveCallback, onCurrentTIDChangedCallback, onlyShowSearchQueryPanel, onReferenceClick, refreshFavoriteSavedQueries, mergePdfManager }) => {
24
24
  const [allSQDs, setAllSQDs] = useState([]);
25
25
  const [filteredByTIDSQDs, setFilteredByTIDSQDs] = useState([]);
26
26
  const [currentSQD, setCurrentSQD] = useState();
@@ -266,7 +266,7 @@ const TMSearch = ({ allTasks = [], getAllTasks, deleteTaskByIdsCallback, addTask
266
266
  toolbarOptions: { icon: _jsx(IconSavedQuery, { fontSize: 24 }), visible: true, orderNumber: 4, isActive: allInitialPanelVisibility['TMSavedQuerySelector'] }
267
267
  }
268
268
  ], [tmTreeSelectorElement, showSearchResults, tmRecentsManagerElement, tmSearchQueryPanelElement, tmSavedQuerySelectorElement, fromDTD, mruTIDs]);
269
- return (_jsxs(_Fragment, { children: [showSearchResults ? _jsx(StyledMultiViewPanel, { "$isVisible": currentSearchView === TMSearchViews.Search, children: _jsx(TMPanelManagerWithPersistenceProvider, { panels: initialPanels, initialVisibility: allInitialPanelVisibility, defaultDimensions: initialPanelDimensions, initialDimensions: initialPanelDimensions, initialMobilePanelId: 'TMRecentsManager', isPersistenceEnabled: !isMobile ? hasSavedLayout() : false, persistPanelStates: !isMobile ? (state) => persistPanelStates(state) : undefined, persistedPanelStates: getPersistedPanelStates(), children: _jsx(TMPanelManagerContainer, { panels: initialPanels, direction: "horizontal", showToolbar: true, minPanelSizePx: !isMobile ? 250 : 150 }) }) }) : tmSearchQueryPanelElement, showSearchResults && _jsx(TMSearchResult, { isVisible: isVisible && currentSearchView === TMSearchViews.Result, context: SearchResultContext.METADATA_SEARCH, searchResults: searchResult, floatingActionConfig: floatingActionConfig, onRefreshAfterAddDcmtToFavs: onRefreshAfterAddDcmtToFavs, openInOffice: openInOffice, onRefreshSearchAsyncDatagrid: onRefreshSearchAsyncDatagrid, onClose: () => { onlyShowSearchQueryPanel ? setShowSearchResults(false) : setCurrentSearchView(TMSearchViews.Search); }, onFileOpened: onFileOpened, onTaskCreateRequest: onTaskCreateRequest, openWGsCopyMoveForm: openWGsCopyMoveForm, editPdfForm: editPdfForm, onOpenPdfEditorRequest: onOpenPdfEditorRequest, openS4TViewer: openS4TViewer, onOpenS4TViewerRequest: onOpenS4TViewerRequest, openFileUploaderPdfEditor: openFileUploaderPdfEditor, passToArchiveCallback: passToArchiveCallback, onSelectedTIDChanged: onCurrentTIDChangedCallback, showTodoDcmtForm: showTodoDcmtForm, showToppyDraggableHelpCenter: showToppyDraggableHelpCenter, toppyHelpCenterUsePortal: toppyHelpCenterUsePortal, onReferenceClick: onReferenceClick, allTasks: allTasks, getAllTasks: getAllTasks, deleteTaskByIdsCallback: deleteTaskByIdsCallback, addTaskCallback: addTaskCallback, editTaskCallback: editTaskCallback, handleNavigateToWGs: handleNavigateToWGs, handleNavigateToDossiers: handleNavigateToDossiers })] }));
269
+ return (_jsxs(_Fragment, { children: [showSearchResults ? _jsx(StyledMultiViewPanel, { "$isVisible": currentSearchView === TMSearchViews.Search, children: _jsx(TMPanelManagerWithPersistenceProvider, { panels: initialPanels, initialVisibility: allInitialPanelVisibility, defaultDimensions: initialPanelDimensions, initialDimensions: initialPanelDimensions, initialMobilePanelId: 'TMRecentsManager', isPersistenceEnabled: !isMobile ? hasSavedLayout() : false, persistPanelStates: !isMobile ? (state) => persistPanelStates(state) : undefined, persistedPanelStates: getPersistedPanelStates(), children: _jsx(TMPanelManagerContainer, { panels: initialPanels, direction: "horizontal", showToolbar: true, minPanelSizePx: !isMobile ? 250 : 150 }) }) }) : tmSearchQueryPanelElement, showSearchResults && _jsx(TMSearchResult, { isVisible: isVisible && currentSearchView === TMSearchViews.Result, context: SearchResultContext.METADATA_SEARCH, searchResults: searchResult, floatingActionConfig: floatingActionConfig, onRefreshAfterAddDcmtToFavs: onRefreshAfterAddDcmtToFavs, openInOffice: openInOffice, onRefreshSearchAsyncDatagrid: onRefreshSearchAsyncDatagrid, onClose: () => { onlyShowSearchQueryPanel ? setShowSearchResults(false) : setCurrentSearchView(TMSearchViews.Search); }, onFileOpened: onFileOpened, onTaskCreateRequest: onTaskCreateRequest, openWGsCopyMoveForm: openWGsCopyMoveForm, editPdfForm: editPdfForm, onOpenPdfEditorRequest: onOpenPdfEditorRequest, openS4TViewer: openS4TViewer, onOpenS4TViewerRequest: onOpenS4TViewerRequest, openFileUploaderPdfEditor: openFileUploaderPdfEditor, passToArchiveCallback: passToArchiveCallback, onSelectedTIDChanged: onCurrentTIDChangedCallback, showTodoDcmtForm: showTodoDcmtForm, showToppyDraggableHelpCenter: showToppyDraggableHelpCenter, toppyHelpCenterUsePortal: toppyHelpCenterUsePortal, onReferenceClick: onReferenceClick, allTasks: allTasks, getAllTasks: getAllTasks, deleteTaskByIdsCallback: deleteTaskByIdsCallback, addTaskCallback: addTaskCallback, editTaskCallback: editTaskCallback, handleNavigateToWGs: handleNavigateToWGs, handleNavigateToDossiers: handleNavigateToDossiers, mergePdfManager: mergePdfManager })] }));
270
270
  };
271
271
  export default TMSearch;
272
272
  const TMTreeSelectorWrapper = ({ isMobile, onSelectedTIDChanged }) => {
@@ -1,5 +1,6 @@
1
1
  import React from 'react';
2
2
  import { DcmtTypeDescriptor, HomeBlogPost, ObjectRef, SearchResultDescriptor, TaskDescriptor, WorkingGroupDescriptor } from '@topconsultnpm/sdk-ts';
3
+ import { MergePdfManagerType } from '../../../helper';
3
4
  import { DcmtInfo, SearchResultContext, TaskContext } from '../../../ts';
4
5
  import { TMSearchResultFloatingActionConfig } from './TMSearchResultFloatingActionButton';
5
6
  export declare const getSearchResultCountersSingleCategory: (searchResults: SearchResultDescriptor[]) => string;
@@ -29,6 +30,7 @@ interface ITMSearchResultProps {
29
30
  showToppyDraggableHelpCenter?: boolean;
30
31
  toppyHelpCenterUsePortal?: boolean;
31
32
  showNoDcmtFoundMessage?: boolean;
33
+ mergePdfManager?: MergePdfManagerType;
32
34
  onClose?: () => void;
33
35
  openInOffice?: (selectedDcmtsOrFocused: Array<DcmtInfo>) => Promise<void>;
34
36
  onWFOperationCompleted?: () => Promise<void>;
@@ -54,7 +54,7 @@ const TMSearchResult = ({
54
54
  // Data
55
55
  groupId, searchResults = [], context = SearchResultContext.METADATA_SEARCH, title, selectedSearchResultTID, floatingActionConfig, workingGroupContext = undefined,
56
56
  // Boolean flags to enable/disable features
57
- isVisible = true, allowRelations = true, openDcmtFormAsModal = false, showSearchResultSidebar = true, showDcmtFormSidebar = true, showSelector = false, isClosable = false, allowFloatingBar = true, showToolbarHeader = true, showBackButton = true, disableAccordionIfSingleCategory = false, editPdfForm = false, openS4TViewer = false, showTodoDcmtForm = false, showToppyDraggableHelpCenter = true, toppyHelpCenterUsePortal = false, showNoDcmtFoundMessage = true, enablePinIcons = true,
57
+ isVisible = true, allowRelations = true, openDcmtFormAsModal = false, showSearchResultSidebar = true, showDcmtFormSidebar = true, showSelector = false, isClosable = false, allowFloatingBar = true, showToolbarHeader = true, showBackButton = true, disableAccordionIfSingleCategory = false, editPdfForm = false, openS4TViewer = false, showTodoDcmtForm = false, showToppyDraggableHelpCenter = true, toppyHelpCenterUsePortal = false, showNoDcmtFoundMessage = true, enablePinIcons = true, mergePdfManager,
58
58
  // Callbacks (optional)
59
59
  openInOffice, onRefreshAfterAddDcmtToFavs, onRefreshSearchAsyncDatagrid, onSelectedTIDChanged, onWFOperationCompleted, onClose, onFileOpened, onTaskCreateRequest, openWGsCopyMoveForm, openCommentFormCallback, openAddDocumentForm, onOpenS4TViewerRequest, onOpenPdfEditorRequest, openFileUploaderPdfEditor, passToArchiveCallback, onReferenceClick,
60
60
  // Tasks
@@ -291,7 +291,7 @@ handleNavigateToWGs, handleNavigateToDossiers, }) => {
291
291
  createTaskFromDocumentOrWorkItem();
292
292
  }
293
293
  };
294
- const { operationItems, renderFloatingBar, renderDcmtOperations, features } = useDocumentOperations({
294
+ const { operationItems, renderFloatingBar, renderDcmtOperations, features, } = useDocumentOperations({
295
295
  context: context,
296
296
  documentData: {
297
297
  dtd: fromDTD,
@@ -361,6 +361,7 @@ handleNavigateToWGs, handleNavigateToDossiers, }) => {
361
361
  onTaskCreateRequest,
362
362
  openTaskFormHandler,
363
363
  },
364
+ mergePdfManager,
364
365
  });
365
366
  const { isOpenDcmtForm, openFormHandler, dcmtFormLayoutMode, onDcmtFormOpenChange, showSearchTMDatagrid, showExportForm, isOpenBatchUpdate, isModifiedBatchUpdate, updateBatchUpdateForm, closeDcmtFormHandler, handleSignApprove, checkoutInfo: { showCicoWaitPanel, cicoWaitPanelTitle, showCicoPrimaryProgress, cicoPrimaryProgressText, cicoPrimaryProgressValue, cicoPrimaryProgressMax, }, dcmtOperations: { abortController, showWaitPanel, showPrimary, waitPanelTitle, waitPanelTextPrimary, waitPanelValuePrimary, waitPanelMaxValuePrimary, showSecondary, waitPanelTextSecondary, waitPanelValueSecondary, waitPanelMaxValueSecondary, downloadDcmtsAsync, runOperationAsync, }, relatedDocumentsInfo: { isOpenDetails, isOpenMaster, checkRelatedDcmtsArchiveCapability, checkManyToManyCapability, }, toppyOperations: { showApprovePopup, showRejectPopup, showReAssignPopup, showMoreInfoPopup, updateShowApprovePopup, updateShowRejectPopup, updateShowReAssignPopup, updateShowMoreInfoPopup } } = features;
366
367
  const deviceType = useDeviceType();
@@ -31,6 +31,14 @@ export declare const cultureIDsDataSource: {
31
31
  value: CultureIDs;
32
32
  display: string;
33
33
  }[];
34
+ export interface ITMLoginDefaultValues {
35
+ endpoint: string;
36
+ archiveId?: string;
37
+ authenticationMode?: AuthenticationModes;
38
+ username?: string;
39
+ behalfUsername?: string;
40
+ domain?: string;
41
+ }
34
42
  interface ITMLoginFormProps {
35
43
  isConnector?: boolean;
36
44
  sdInput?: SessionDescriptor;
@@ -39,6 +47,7 @@ interface ITMLoginFormProps {
39
47
  onLogged: (tmSession: ITopMediaSession) => void;
40
48
  onChangeLanguage?: (e: CultureIDs) => void;
41
49
  cultureID?: CultureIDs;
50
+ defaultLoginValues?: ITMLoginDefaultValues;
42
51
  }
43
52
  declare const TMLoginForm: React.FunctionComponent<ITMLoginFormProps>;
44
53
  export default TMLoginForm;
@@ -106,6 +106,7 @@ const TMLoginForm = (props) => {
106
106
  const passwordRef = useRef(null);
107
107
  const usernameOnBehalfOfRef = useRef(null);
108
108
  const passwordOnBehalfOfRRef = useRef(null);
109
+ const defaultLoginAppliedRef = useRef(false);
109
110
  const [loginStep, setLoginStep] = useState(1);
110
111
  const [tmServer, setTmServer] = useState();
111
112
  const [tmSession, setTmSession] = useState();
@@ -201,6 +202,23 @@ const TMLoginForm = (props) => {
201
202
  setUsername(props.sdInput.userName);
202
203
  }
203
204
  }, []);
205
+ useEffect(() => {
206
+ if (!props.defaultLoginValues)
207
+ return;
208
+ const matchingEndpoint = props.endpoints.find(ep => ep.URL === props.defaultLoginValues.endpoint);
209
+ if (!matchingEndpoint)
210
+ return;
211
+ setEndpoint(matchingEndpoint);
212
+ if (props.defaultLoginValues.archiveId)
213
+ setManualArchiveID(props.defaultLoginValues.archiveId);
214
+ setAuthMode(props.defaultLoginValues.authenticationMode ?? AuthenticationModes.TopMedia);
215
+ if (props.defaultLoginValues.username)
216
+ setUsername(props.defaultLoginValues.username);
217
+ if (props.defaultLoginValues.domain)
218
+ setAuthDomain(props.defaultLoginValues.domain);
219
+ if (props.defaultLoginValues.behalfUsername)
220
+ setUsernameOnBehalf(props.defaultLoginValues.behalfUsername);
221
+ }, [props.defaultLoginValues, props.endpoints]);
204
222
  useEffect(() => {
205
223
  if (!hasSingleOption)
206
224
  return;
@@ -216,6 +234,9 @@ const TMLoginForm = (props) => {
216
234
  setDcmtArchive(undefined);
217
235
  }, [isSuccess]);
218
236
  useEffect(() => {
237
+ // Skip default endpoint
238
+ if (props.defaultLoginValues && props.endpoints.some(ep => ep.URL === props.defaultLoginValues.endpoint))
239
+ return;
219
240
  let preferredRapidAccess = localRa?.find(ar => ar.preferred === true);
220
241
  if (preferredRapidAccess) {
221
242
  handleRapidAccessSelection(preferredRapidAccess);
@@ -240,6 +261,29 @@ const TMLoginForm = (props) => {
240
261
  useEffect(() => {
241
262
  getArchivesAsync();
242
263
  }, [tmSession]);
264
+ // Default values and step management
265
+ useEffect(() => {
266
+ if (!props.defaultLoginValues || !props.defaultLoginValues.archiveId || defaultLoginAppliedRef.current || !tmSession || !tmSession.TopMediaServer?.BaseAddress)
267
+ return;
268
+ defaultLoginAppliedRef.current = true;
269
+ const archiveId = props.defaultLoginValues.archiveId;
270
+ const validateAndAdvance = async () => {
271
+ try {
272
+ TMSpinner.show({ description: '' });
273
+ const archiveEngine = tmSession.NewArchiveEngine();
274
+ const result = await archiveEngine.RetrieveAsync(archiveId);
275
+ setDcmtArchive(result);
276
+ setLoginStep(2);
277
+ }
278
+ catch (e) {
279
+ TMExceptionBoxManager.show({ exception: e });
280
+ }
281
+ finally {
282
+ TMSpinner.hide();
283
+ }
284
+ };
285
+ validateAndAdvance();
286
+ }, [tmSession, props.defaultLoginValues]);
243
287
  useEffect(() => {
244
288
  if (!saveLoginEnable || !dcmtArchive)
245
289
  return;
@@ -1,5 +1,20 @@
1
1
  import { InvoiceRetrieveFormats, ObjectClasses, OrderRetrieveFormats } from "@topconsultnpm/sdk-ts";
2
2
  import { CheckoutInfo } from "./checkinCheckoutManager";
3
+ export type FileNamingMode = 'onlyDid' | 'documentTypeAndDid' | 'documentTypeAndCustomMetadata' | 'onlyCustomMetadata';
4
+ export type FileExistsMode = 'overwrite' | 'skip' | 'rename';
5
+ export declare class DocumentDownloadSettings {
6
+ exportMode: 'copy' | 'zip';
7
+ destinationFolder: string;
8
+ zipFileName: string;
9
+ zipPassword: string;
10
+ fileNamingMode: FileNamingMode;
11
+ separatorChar: string;
12
+ invoiceFormat: InvoiceRetrieveFormats;
13
+ orderFormat: OrderRetrieveFormats;
14
+ fileExistsMode: FileExistsMode;
15
+ removeSignature: boolean;
16
+ pdfFileName: string;
17
+ }
3
18
  export declare const dcmtsFileCacheDownload: Map<string, File>;
4
19
  export declare const dcmtsFileCachePreview: Map<string, File>;
5
20
  export declare const CACHE_SIZE_LIMIT = 10;
@@ -22,6 +37,7 @@ export declare class UserSettings {
22
37
  wgDraftCheckoutInfo: CheckoutInfo[];
23
38
  dcmtCheckoutInfo: CheckoutInfo[];
24
39
  defaultCheckInOutFolder: string;
40
+ documentDownloadSettings: DocumentDownloadSettings;
25
41
  constructor(skipCssUpdate?: boolean);
26
42
  /** Load settings from local storage or other sources */
27
43
  static LoadSettings(userID: number | undefined, archiveID: string | undefined): UserSettings;
@@ -1,6 +1,20 @@
1
1
  import { InvoiceRetrieveFormats, LocalStorageService, OrderRetrieveFormats } from "@topconsultnpm/sdk-ts";
2
2
  import { FontSize } from "../utils/theme";
3
- // import { LandingPages } from "./helpers";
3
+ export class DocumentDownloadSettings {
4
+ constructor() {
5
+ this.exportMode = 'copy';
6
+ this.destinationFolder = 'Download';
7
+ this.zipFileName = '';
8
+ this.zipPassword = '';
9
+ this.fileNamingMode = 'documentTypeAndDid';
10
+ this.separatorChar = '_';
11
+ this.invoiceFormat = InvoiceRetrieveFormats.ASW_HTML;
12
+ this.orderFormat = OrderRetrieveFormats.NSO_HTML;
13
+ this.fileExistsMode = 'overwrite';
14
+ this.removeSignature = false;
15
+ this.pdfFileName = '';
16
+ }
17
+ }
4
18
  export const dcmtsFileCacheDownload = new Map();
5
19
  export const dcmtsFileCachePreview = new Map();
6
20
  export const CACHE_SIZE_LIMIT = 10;
@@ -24,6 +38,7 @@ export class UserSettings {
24
38
  this.wgDraftCheckoutInfo = [];
25
39
  this.dcmtCheckoutInfo = [];
26
40
  this.defaultCheckInOutFolder = DEFAULT_CHECK_IN_OUT_FOLDER;
41
+ this.documentDownloadSettings = new DocumentDownloadSettings();
27
42
  this.themeSettings = new ThemeSettings(skipCssUpdate);
28
43
  }
29
44
  /** Load settings from local storage or other sources */
@@ -53,4 +53,23 @@ type DcmtFormToolbarVisibility = {
53
53
  tmDcmtTasks: boolean;
54
54
  };
55
55
  export declare const getDcmtFormToolbarVisibility: (appModuleID: AppModules) => DcmtFormToolbarVisibility;
56
+ export type MergePdfManagerType = {
57
+ merge: (files: File[], options?: {
58
+ onProgress?: (current: number, total: number, file: File) => void;
59
+ }) => Promise<{
60
+ bytes: Uint8Array;
61
+ blob: Blob;
62
+ pageCount: number;
63
+ }>;
64
+ mergeToFile: (files: File[], fileName?: string, options?: {
65
+ onProgress?: (current: number, total: number, file: File) => void;
66
+ }) => Promise<File>;
67
+ mergeAndDownload: (files: File[], fileName?: string, options?: {
68
+ onProgress?: (current: number, total: number, file: File) => void;
69
+ }) => Promise<{
70
+ bytes: Uint8Array;
71
+ blob: Blob;
72
+ pageCount: number;
73
+ }>;
74
+ };
56
75
  export {};
@@ -0,0 +1,56 @@
1
+ /** File da aggiungere allo ZIP */
2
+ export interface ZipFileEntry {
3
+ /** Nome/percorso nello ZIP (es. "docs/file.txt") */
4
+ filename: string;
5
+ /** Contenuto: string, File, Blob, ArrayBuffer o Uint8Array */
6
+ data: File | Blob | string | ArrayBuffer | Uint8Array;
7
+ /** Commento opzionale */
8
+ comment?: string;
9
+ /** Data modifica (default: ora) */
10
+ lastModDate?: Date;
11
+ }
12
+ /** Opzioni creazione ZIP */
13
+ export interface ZipCreateOptions {
14
+ /** Password AES (opzionale) */
15
+ password?: string;
16
+ /** Compressione 0-9 (default: 6) */
17
+ compressionLevel?: number;
18
+ /** Cifratura: 1=128bit, 2=192bit, 3=256bit (default: 3) */
19
+ encryptionStrength?: 1 | 2 | 3;
20
+ /** Commento globale ZIP */
21
+ comment?: string;
22
+ /** Callback progresso: (indice, totale, nomeFile) */
23
+ onProgress?: (index: number, total: number, filename: string) => void;
24
+ /** Callback progresso singolo file: (bytesElaborati, bytesTotali) */
25
+ onEntryProgress?: (progress: number, total: number) => void;
26
+ /** AbortSignal per annullare */
27
+ signal?: AbortSignal;
28
+ }
29
+ /**
30
+ * Utility per creare file ZIP con password opzionale (AES-256).
31
+ * Tutti i metodi sono statici.
32
+ */
33
+ export declare class ZipManager {
34
+ /** Configura zip.js (useWebWorkers, maxWorkers) */
35
+ static configure(options: {
36
+ useWebWorkers?: boolean;
37
+ maxWorkers?: number;
38
+ }): void;
39
+ /** Crea ZIP da array di file */
40
+ static createZip(files: ZipFileEntry[], options?: ZipCreateOptions): Promise<Blob>;
41
+ /** Crea ZIP da singolo file */
42
+ static createZipFromFile(filename: string, data: File | Blob | string | ArrayBuffer | Uint8Array, options?: ZipCreateOptions): Promise<Blob>;
43
+ /** Crea ZIP da oggetto { nomeFile: contenuto } */
44
+ static createZipFromMap(filesMap: Record<string, File | Blob | string | ArrayBuffer | Uint8Array>, options?: ZipCreateOptions): Promise<Blob>;
45
+ /** Crea ZIP e avvia download */
46
+ static createAndDownload(files: ZipFileEntry[], downloadFilename: string, options?: ZipCreateOptions): Promise<void>;
47
+ /** Scarica un Blob come file */
48
+ static downloadBlob(blob: Blob, filename: string): void;
49
+ /** Crea URL temporaneo per Blob (ricordarsi revokeObjectURL!) */
50
+ static createObjectURL(blob: Blob): string;
51
+ /** Rilascia URL creato con createObjectURL */
52
+ static revokeObjectURL(url: string): void;
53
+ /** Converte data nel reader appropriato per zip.js */
54
+ private static createReader;
55
+ }
56
+ export default ZipManager;