@topconsultnpm/sdkui-react 6.22.0-dev2.37 → 6.22.0-dev2.39

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.
@@ -18,6 +18,13 @@ interface ITMModal {
18
18
  recenterOnResize?: boolean;
19
19
  /** Contenuto aggiuntivo nella barra del titolo, prima del pulsante di chiusura (es. un menu di opzioni) */
20
20
  titleActions?: React.ReactNode;
21
+ /** Dimensioni in px al termine di un ridimensionamento manuale */
22
+ onResized?: (size: {
23
+ width: number;
24
+ height: number;
25
+ }) => void;
26
+ /** A ogni cambio di valore le dimensioni tornano a `width`/`height`, anche se invariate */
27
+ sizeResetToken?: number | string;
21
28
  }
22
29
  declare const TMModal: React.FC<ITMModal>;
23
30
  export default TMModal;
@@ -42,7 +42,7 @@ const StyledModalContext = styled.div `
42
42
  overflow: auto;
43
43
  height: 100%;
44
44
  `;
45
- const TMModal = ({ resizable = true, expandable = false, isModal = true, title = '', toolbar, onClose, children, width = '100%', height = '100%', fontSize = FontSize.defaultFontSize, hidePopup = true, askClosingConfirm = false, showCloseButton = true, showHeader = true, recenterOnResize = false, titleActions }) => {
45
+ const TMModal = ({ resizable = true, expandable = false, isModal = true, title = '', toolbar, onClose, children, width = '100%', height = '100%', fontSize = FontSize.defaultFontSize, hidePopup = true, askClosingConfirm = false, showCloseButton = true, showHeader = true, recenterOnResize = false, titleActions, onResized, sizeResetToken }) => {
46
46
  const popupRef = useRef(null);
47
47
  const [initialWidth, setInitialWidth] = useState(width);
48
48
  const [initialHeight, setInitialHeight] = useState(height);
@@ -116,7 +116,8 @@ const TMModal = ({ resizable = true, expandable = false, isModal = true, title =
116
116
  // annullando l'eventuale spostamento manuale precedente
117
117
  if (recenterOnResize)
118
118
  setPosition({ my: 'center', at: 'center', of: window });
119
- }, [width, height, recenterOnResize]);
119
+ // Il token annulla un ridimensionamento manuale, che non cambia width/height
120
+ }, [width, height, recenterOnResize, sizeResetToken]);
120
121
  // Dimensioni, testata e schermo pieno fanno rigenerare geometria ed elementi del popup:
121
122
  // il layout flex del contenuto va riapplicato dopo il ridisegno (vedi applyContentFillLayout)
122
123
  useEffect(() => {
@@ -128,8 +129,10 @@ const TMModal = ({ resizable = true, expandable = false, isModal = true, title =
128
129
  };
129
130
  const handleResizeEnd = (e) => {
130
131
  setIsResizing(false);
131
- setInitialWidth(e.width.toString());
132
- setInitialHeight(e.height.toString());
132
+ // Le dimensioni arrivano in px: senza unità non sarebbero un valore CSS valido
133
+ setInitialWidth(`${e.width}px`);
134
+ setInitialHeight(`${e.height}px`);
135
+ onResized?.({ width: e.width, height: e.height });
133
136
  };
134
137
  const onHiding = (e) => {
135
138
  if (askClosingConfirm)
@@ -3,7 +3,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3
3
  import { LayoutModes } from "@topconsultnpm/sdk-ts";
4
4
  import { LoadIndicator } from "devextreme-react/load-indicator";
5
5
  import { SearchResultContext } from "../../../ts";
6
- import { getFullFileExtension, getMultiDetailPanelStates, hasMultiDetailPanelStates, hoverStyle, IconArchive, IconDelete, IconDuplicate, IconEdit, IconEraser, IconInfo, IconMenuVertical, IconRefresh, IconSearch, IconSearchCheck, saveMultiDetailPanelStates, SDKUI_Localizator } from "../../../helper";
6
+ import { getFullFileExtension, getMultiDetailModalSize, getMultiDetailPanelStates, hasMultiDetailPanelStates, hoverStyle, IconArchive, IconDelete, IconDuplicate, IconEdit, IconEraser, IconInfo, IconMenuVertical, IconRefresh, IconSearch, IconSearchCheck, saveMultiDetailModalSize, saveMultiDetailPanelStates, SDKUI_Localizator } from "../../../helper";
7
7
  import { TMColors } from "../../../utils/theme";
8
8
  import { DeviceType, useDeviceType } from "../../base/TMDeviceProvider";
9
9
  import { TMLayoutWaitingContainer } from "../../base/TMWaitPanel";
@@ -46,7 +46,7 @@ const styles = {
46
46
  const MOBILE_SECTION = 'archiveList';
47
47
  const TMMultiMasterDetailDcmtsForm = (props) => {
48
48
  const { dtd, masterDcmt, onClose, allTasks, getAllTasks, deleteTaskByIdsCallback, addTaskCallback, editTaskCallback, handleNavigateToWGs, handleNavigateToDossiers, showDcmtFormSidebar, showTodoDcmtForm, openFileUploaderPdfEditor, graphometricManager, editPdfForm, openS4TViewer, showToppyDraggableHelpCenter, toppyHelpCenterUsePortal, fetchRemoteCertificates, onOpenS4TViewerRequest, onOpenPdfEditorRequest, onReferenceClick, onTaskCreateRequest, onRefreshAfterAddDcmtToFavs, onFileOpened, passToArchiveCallback, openWGsCopyMoveForm } = props;
49
- const { isLoading, isSummaryReady, phase, setPhase, detailRelationItems, selectedRelation, selectedItem, hasMultipleTypes, selectRelation, backToTypeSelection, isBackLocked, closeModal, sectionsOpen, setSectionOpen, openAllSections, clearLayout, layoutResetToken, hasSavedLayout, setHasSavedLayout, canUpdateMaster, masterFieldsCount, getMasterMetadataAsync, masterInfoReloadToken, isOpenMasterForm, setIsOpenMasterForm, handleMasterSavedAsync, archivedDetailResults, archivedDetailsCount, isLoadingArchivedDetails, refreshArchivedDetailsAsync, detailColumns, detailSupportsFile, archiveRows, currentRows, focusedRowKey, setFocusedRowKey, selectedRowKeys, setSelectedRowKeys, isOpenAddForm, editingRowId, editingRow, formInputMids, openAddForm, openEditForm, closeAddForm, deleteRow, duplicateRow, upsertRow, updateRowValues, stopOnFirstError, setStopOnFirstError, isArchiving, archiveAllAsync, waitPanel, abortController, } = useMultiMasterDetailDcmts({ dtd, masterDcmt, onClose });
49
+ const { isLoading, isSummaryReady, phase, setPhase, detailRelationItems, selectedRelation, selectedItem, hasMultipleTypes, selectRelation, backToTypeSelection, isBackLocked, closeModal, sectionsOpen, setSectionOpen, openAllSections, clearLayout, layoutResetToken, hasSavedLayout, setHasSavedLayout, canUpdateMaster, masterFieldsCount, getMasterMetadataAsync, masterInfoReloadToken, isOpenMasterForm, setIsOpenMasterForm, handleMasterSavedAsync, archivedDetailResults, archivedDetailsCount, isLoadingArchivedDetails, refreshArchivedDetailsAsync, detailColumns, detailSupportsFile, archiveRows, currentRows, focusedRowKey, setFocusedRowKey, selectedRowKeys, setSelectedRowKeys, isOpenAddForm, editingRowId, editingRow, formInputMids, openAddForm, openEditForm, closeAddForm, deleteRow, duplicateRow, upsertRow, updateRowValues, copyArchivedDetailToRows, stopOnFirstError, setStopOnFirstError, isArchiving, archiveAllAsync, waitPanel, abortController, } = useMultiMasterDetailDcmts({ dtd, masterDcmt, onClose });
50
50
  const deviceType = useDeviceType();
51
51
  // Su mobile il panel manager mostra un pannello alla volta: le sezioni si raggiungono dalla sua barra
52
52
  const isMobile = useMemo(() => deviceType === DeviceType.MOBILE, [deviceType]);
@@ -75,6 +75,15 @@ const TMMultiMasterDetailDcmtsForm = (props) => {
75
75
  onClick: () => setShowGridSearch(prev => !prev),
76
76
  };
77
77
  const isSummaryPhase = phase === MultiDetailArchivePhase.ArchiveSummary;
78
+ // Dimensioni del modale, ricordate per passo. Si salvano senza aggiornare lo stato: il cambio di
79
+ // dimensioni ricentrerebbe il popup appena ridimensionato dall'utente
80
+ const modalStep = isSummaryPhase ? 'archiveSummary' : 'selectDcmtType';
81
+ const defaultModalSize = isSummaryPhase ? { width: '95%', height: '95%' } : { width: '720px', height: 'auto' };
82
+ const modalSize = useMemo(() => getMultiDetailModalSize(modalStep) ?? defaultModalSize, [modalStep, layoutResetToken]);
83
+ const persistModalSize = useCallback((size) => {
84
+ saveMultiDetailModalSize(modalStep, { width: `${size.width}px`, height: `${size.height}px` });
85
+ setHasSavedLayout(true);
86
+ }, [modalStep, setHasSavedLayout]);
78
87
  const baseTitle = SDKUI_Localizator.ArchiveMultipleDetailDocuments;
79
88
  // Con un solo tipo il nome è mostrato direttamente nel titolo del modale
80
89
  const modalTitle = !hasMultipleTypes && selectedItem ? `${baseTitle}: ${getDtdDisplayName(selectedItem.detailDtd)}` : baseTitle;
@@ -114,7 +123,7 @@ const TMMultiMasterDetailDcmtsForm = (props) => {
114
123
  allowSorting: false, allowFiltering: false, allowHeaderFiltering: false, allowResizing: false,
115
124
  allowReordering: false, allowHiding: false, showInColumnChooser: false, allowEditing: false,
116
125
  cssClass: 'tm-row-actions',
117
- cellRender: (cell) => (_jsxs("div", { style: styles.actionsCell, children: [_jsx(TMTooltip, { content: SDKUI_Localizator.Update, children: _jsx("span", { style: styles.actionIcon, onClick: () => openEditForm(cell.data), children: _jsx(IconEdit, { fontSize: 18, color: TMColors.primary }) }) }), _jsx(TMTooltip, { content: SDKUI_Localizator.Duplicate, children: _jsx("span", { style: styles.actionIcon, onClick: () => duplicateRow(cell.data), children: _jsx(IconDuplicate, { fontSize: 18, color: TMColors.tertiary }) }) }), _jsx(TMTooltip, { content: SDKUI_Localizator.Deletion, children: _jsx("span", { style: styles.actionIcon, onClick: () => deleteRow(cell.data), children: _jsx(IconDelete, { fontSize: 18, color: TMColors.error }) }) })] })),
126
+ cellRender: (cell) => (_jsxs("div", { style: styles.actionsCell, children: [_jsx(TMTooltip, { content: SDKUI_Localizator.Update, children: _jsx("span", { style: styles.actionIcon, onClick: () => openEditForm(cell.data), children: _jsx(IconEdit, { fontSize: 18, color: TMColors.primary }) }) }), _jsx(TMTooltip, { content: SDKUI_Localizator.Duplicate, children: _jsx("span", { style: styles.actionIcon, onClick: () => duplicateRow(cell.data), children: _jsx(IconDuplicate, { fontSize: 18, color: TMColors.tertiary }) }) }), _jsx(TMTooltip, { content: SDKUI_Localizator.Remove, children: _jsx("span", { style: styles.actionIcon, onClick: () => deleteRow(cell.data), children: _jsx(IconDelete, { fontSize: 18, color: TMColors.error }) }) })] })),
118
127
  };
119
128
  return [actionsColumn, iconColumn, ...detailColumns, ...(detailSupportsFile ? [fileColumn] : [])];
120
129
  }, [detailColumns, detailSupportsFile]);
@@ -151,13 +160,13 @@ const TMMultiMasterDetailDcmtsForm = (props) => {
151
160
  const contextMenuItems = useMemo(() => [
152
161
  { name: SDKUI_Localizator.Update, icon: _jsx(IconEdit, { fontSize: 18, color: TMColors.primary }), operationType: 'singleRow', onClick: (id) => openEditForm(archiveRows.find(r => r.id === id)) },
153
162
  { name: SDKUI_Localizator.Duplicate, icon: _jsx(IconDuplicate, { fontSize: 18, color: TMColors.tertiary }), operationType: 'singleRow', onClick: (id) => duplicateRow(archiveRows.find(r => r.id === id)) },
154
- { name: SDKUI_Localizator.Deletion, icon: _jsx(IconDelete, { fontSize: 18, color: TMColors.error }), operationType: 'singleRow', beginGroup: true, onClick: (id) => deleteRow(archiveRows.find(r => r.id === id)) },
163
+ { name: SDKUI_Localizator.Remove, icon: _jsx(IconDelete, { fontSize: 18, color: TMColors.error }), operationType: 'singleRow', beginGroup: true, onClick: (id) => deleteRow(archiveRows.find(r => r.id === id)) },
155
164
  toggleGridSearchItem,
156
165
  ], [archiveRows, showGridSearch]);
157
166
  // Menu delle opzioni del modale, aperto dal pulsante a tre punti nella barra del titolo.
158
167
  // Con il layout di default non c'è niente da cancellare, quindi la voce non compare
159
168
  const optionsMenuItems = useMemo(() => hasSavedLayout
160
- ? [{ name: 'Cancella layout', icon: _jsx(IconEraser, { fontSize: 18, color: TMColors.error }), onClick: clearLayout }]
169
+ ? [{ name: SDKUI_Localizator.RemoveLayout, icon: _jsx(IconEraser, { fontSize: 18, color: TMColors.error }), onClick: clearLayout }]
161
170
  : [], [clearLayout, hasSavedLayout]);
162
171
  // Le opzioni riguardano i pannelli del riepilogo: fuori da quella fase non hanno effetto, e su mobile
163
172
  // non c'è un layout da cancellare (i pannelli si vedono uno alla volta, a tutta altezza)
@@ -202,7 +211,7 @@ const TMMultiMasterDetailDcmtsForm = (props) => {
202
211
  }), isOpen && (_jsx("div", { style: { ...accordionStyles.accordionListBody, ...(panel.isMaximized ? { borderTop: 'none' } : {}) }, children: isLoadingArchivedDetails
203
212
  ? _jsxs("div", { style: accordionStyles.accordionListPlaceholder, children: [_jsx(LoadIndicator, { height: 30, width: 30 }), SDKUI_Localizator.Loading] })
204
213
  : (hasResults
205
- ? _jsx(TMSearchResult, { showToolbarHeader: false, showCounterConfig: false, showActionsColumn: true, compactPager: !panel.isMaximized, context: SearchResultContext.METADATA_SEARCH, searchResults: archivedDetailResults, onClose: () => setSectionOpen('archivedDetails', false), showBackButton: false, showToolbarCloseButton: false, allowFloatingBar: false, allowRelations: false, openDcmtFormAsModal: true, showSearchResultSidebar: false, showDcmtFormSidebar: true, autoFocusFirstRow: false, onRefreshSearchAsyncDatagrid: refreshArchivedDetailsAsync, allTasks: allTasks, getAllTasks: getAllTasks, deleteTaskByIdsCallback: deleteTaskByIdsCallback, addTaskCallback: addTaskCallback, editTaskCallback: editTaskCallback, handleNavigateToWGs: handleNavigateToWGs, handleNavigateToDossiers: handleNavigateToDossiers, showTodoDcmtForm: showTodoDcmtForm, openFileUploaderPdfEditor: openFileUploaderPdfEditor, graphometricManager: graphometricManager, editPdfForm: editPdfForm, openS4TViewer: openS4TViewer, showToppyDraggableHelpCenter: showToppyDraggableHelpCenter, toppyHelpCenterUsePortal: toppyHelpCenterUsePortal, fetchRemoteCertificates: fetchRemoteCertificates, onOpenS4TViewerRequest: onOpenS4TViewerRequest, onOpenPdfEditorRequest: onOpenPdfEditorRequest, onReferenceClick: onReferenceClick, onTaskCreateRequest: onTaskCreateRequest, onRefreshAfterAddDcmtToFavs: onRefreshAfterAddDcmtToFavs, onFileOpened: onFileOpened, passToArchiveCallback: passToArchiveCallback, openWGsCopyMoveForm: openWGsCopyMoveForm })
214
+ ? _jsx(TMSearchResult, { showToolbarHeader: false, showCounterConfig: false, showActionsColumn: true, compactPager: !panel.isMaximized, context: SearchResultContext.METADATA_SEARCH, searchResults: archivedDetailResults, onClose: () => setSectionOpen('archivedDetails', false), showBackButton: false, showToolbarCloseButton: false, allowFloatingBar: false, allowRelations: false, openDcmtFormAsModal: true, showSearchResultSidebar: false, showDcmtFormSidebar: true, autoFocusFirstRow: false, onRefreshSearchAsyncDatagrid: refreshArchivedDetailsAsync, onRowDuplicateRequest: copyArchivedDetailToRows, allTasks: allTasks, getAllTasks: getAllTasks, deleteTaskByIdsCallback: deleteTaskByIdsCallback, addTaskCallback: addTaskCallback, editTaskCallback: editTaskCallback, handleNavigateToWGs: handleNavigateToWGs, handleNavigateToDossiers: handleNavigateToDossiers, showTodoDcmtForm: showTodoDcmtForm, openFileUploaderPdfEditor: openFileUploaderPdfEditor, graphometricManager: graphometricManager, editPdfForm: editPdfForm, openS4TViewer: openS4TViewer, showToppyDraggableHelpCenter: showToppyDraggableHelpCenter, toppyHelpCenterUsePortal: toppyHelpCenterUsePortal, fetchRemoteCertificates: fetchRemoteCertificates, onOpenS4TViewerRequest: onOpenS4TViewerRequest, onOpenPdfEditorRequest: onOpenPdfEditorRequest, onReferenceClick: onReferenceClick, onTaskCreateRequest: onTaskCreateRequest, onRefreshAfterAddDcmtToFavs: onRefreshAfterAddDcmtToFavs, onFileOpened: onFileOpened, passToArchiveCallback: passToArchiveCallback, openWGsCopyMoveForm: openWGsCopyMoveForm })
206
215
  : _jsx("div", { style: accordionStyles.accordionListPlaceholder, children: SDKUI_Localizator.NoDataToDisplay })) }))] }));
207
216
  };
208
217
  // ----- Sezione: dettagli da archiviare ------------------------------------------------------
@@ -276,7 +285,9 @@ const TMMultiMasterDetailDcmtsForm = (props) => {
276
285
  // ----- Render principale --------------------------------------------------------------------
277
286
  if (isLoading)
278
287
  return null;
279
- return _jsxs(_Fragment, { children: [_jsx(TMLayoutWaitingContainer, { direction: 'vertical', ...waitPanel, isCancelable: true, abortController: abortController, usePortal: true, children: _jsx(TMModal, { width: isSummaryPhase ? '95%' : '720px', height: isSummaryPhase ? '95%' : 'auto', onClose: closeModal, title: modalTitle, hidePopup: false, askClosingConfirm: archiveRows.length > 0, recenterOnResize: true, titleActions: renderOptionsMenu(), children: _jsxs("div", { style: styles.content, children: [hasMultipleTypes && _jsx("div", { style: styles.header, children: renderStepper({
288
+ return _jsxs(_Fragment, { children: [_jsx(TMLayoutWaitingContainer, { direction: 'vertical', ...waitPanel, isCancelable: true, abortController: abortController, usePortal: true, children: _jsx(TMModal, { width: modalSize.width, height: modalSize.height, onClose: closeModal, onResized: persistModalSize,
289
+ // Cancellato il layout il popup torna alle dimensioni di default
290
+ sizeResetToken: layoutResetToken, title: modalTitle, hidePopup: false, askClosingConfirm: archiveRows.length > 0, recenterOnResize: true, titleActions: renderOptionsMenu(), children: _jsxs("div", { style: styles.content, children: [hasMultipleTypes && _jsx("div", { style: styles.header, children: renderStepper({
280
291
  steps,
281
292
  phase,
282
293
  hasMultipleTypes,
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { DataListViewModes, DcmtTypeDescriptor, MetadataDescriptor, RelationDescriptor } from '@topconsultnpm/sdk-ts';
2
+ import { DataListViewModes, DcmtTypeDescriptor, MetadataDataTypes, MetadataDescriptor, RelationDescriptor } from '@topconsultnpm/sdk-ts';
3
3
  import { DataGridTypes, IColumnProps } from 'devextreme-react/data-grid';
4
4
  import { TMPanelDimensionsMap } from '../../layout/panelManager/types';
5
5
  import { DcmtInfo, MetadataValueDescriptorEx } from '../../../ts';
@@ -80,6 +80,13 @@ export declare const isInlineEditableMetadata: (md: MetadataDescriptor) => boole
80
80
  * la riga porta in `__values` per l'archiviazione. Vuoto o non numerico: cella vuota
81
81
  */
82
82
  export declare const toGridNumber: (value: unknown) => number | undefined;
83
+ /**
84
+ * Valore di un metadato letto da un documento archiviato, nel formato in cui il form della riga
85
+ * produce i suoi valori: stringa vuota se assente, data serializzata come la restituisce l'editor
86
+ * ('yyyy-MM-ddTHH:mm:ss', vedi TMDateBox), numero con il punto decimale. Solo così la riga copiata
87
+ * si rilegge nel form, si mostra in griglia e si archivia come una riga compilata a mano
88
+ */
89
+ export declare const toArchiveRowValue: (value: MetadataScalarValue, dataType: MetadataDataTypes | undefined) => string;
83
90
  /** Colonne della griglia di riepilogo dai metadati (utente) del tipo documento */
84
91
  export declare const buildColumnsFromDtd: (dtd: DcmtTypeDescriptor | undefined, renderDataListCell: (value: string | Date | number | undefined, dataListID: number, viewMode: DataListViewModes) => React.ReactElement) => Array<IColumnProps>;
85
92
  export declare const getDetailDtdAsync: (detailTID: number | undefined) => Promise<DcmtTypeDescriptor | undefined>;
@@ -47,6 +47,30 @@ export const toGridNumber = (value) => {
47
47
  const parsed = typeof value === 'number' ? value : Number(value);
48
48
  return Number.isFinite(parsed) ? parsed : undefined;
49
49
  };
50
+ /**
51
+ * Valore di un metadato letto da un documento archiviato, nel formato in cui il form della riga
52
+ * produce i suoi valori: stringa vuota se assente, data serializzata come la restituisce l'editor
53
+ * ('yyyy-MM-ddTHH:mm:ss', vedi TMDateBox), numero con il punto decimale. Solo così la riga copiata
54
+ * si rilegge nel form, si mostra in griglia e si archivia come una riga compilata a mano
55
+ */
56
+ export const toArchiveRowValue = (value, dataType) => {
57
+ if (value === undefined || value === null || value === '')
58
+ return '';
59
+ if (dataType === MetadataDataTypes.DateTime) {
60
+ const date = value instanceof Date ? value : new Date(String(value));
61
+ // Data non interpretabile: meglio la cella vuota di un valore che il form non saprebbe rileggere
62
+ if (Number.isNaN(date.getTime()))
63
+ return '';
64
+ const pad = (part) => String(part).padStart(2, '0');
65
+ return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
66
+ }
67
+ if (dataType === MetadataDataTypes.Number) {
68
+ // L'archiviazione fa parseFloat del valore: serve il punto come separatore decimale
69
+ const parsed = typeof value === 'number' ? value : Number(String(value).trim());
70
+ return Number.isFinite(parsed) ? String(parsed) : '';
71
+ }
72
+ return String(value);
73
+ };
50
74
  /** Colonne della griglia di riepilogo dai metadati (utente) del tipo documento */
51
75
  export const buildColumnsFromDtd = (dtd, renderDataListCell) => getUserMetadataSorted(dtd).map(md => {
52
76
  const dataListID = md.dataListID ?? 0;
@@ -40,6 +40,12 @@ interface ITMSearchResultProps {
40
40
  showActionsColumn?: boolean;
41
41
  /** Rende compatto il paginatore della griglia: utile quando il risultato è dentro pannelli bassi (es. accordion) */
42
42
  compactPager?: boolean;
43
+ /** Sostituisce l'azione "duplica" della colonna di riga, che passa qui la riga cliccata: il menu contestuale resta invariato. I valori di riga della griglia sono stringhe */
44
+ onRowDuplicateRequest?: (rowData: {
45
+ TID?: number | string;
46
+ DID?: number | string;
47
+ FILEEXT?: string;
48
+ }) => void;
43
49
  counterConfig?: ITMCounterContainerProps;
44
50
  onClose?: () => void;
45
51
  openInOffice?: (selectedDcmtsOrFocused: Array<DcmtInfo>) => Promise<void>;
@@ -66,7 +66,7 @@ const TMSearchResult = ({
66
66
  // Data
67
67
  groupId, searchResults = [], context = SearchResultContext.METADATA_SEARCH, title, selectedSearchResultTID, floatingActionConfig, workingGroupContext = undefined, inputDID,
68
68
  // Boolean flags to enable/disable features
69
- autoFocusFirstRow = true, formAutoOpen, isVisible = true, allowRelations = true, openDcmtFormAsModal = false, showSearchResultSidebar = true, showDcmtFormSidebar = true, showSelector = false, isClosable = false, allowFloatingBar = true, showToolbarHeader = true, showBackButton = true, showToolbarCloseButton = false, disableAccordionIfSingleCategory = false, editPdfForm = false, openS4TViewer = false, showTodoDcmtForm = false, showToppyDraggableHelpCenter = true, toppyHelpCenterUsePortal = false, showNoDcmtFoundMessage = true, enablePinIcons = true, showCounterConfig = true, showActionsColumn = false, compactPager = false,
69
+ autoFocusFirstRow = true, formAutoOpen, isVisible = true, allowRelations = true, openDcmtFormAsModal = false, showSearchResultSidebar = true, showDcmtFormSidebar = true, showSelector = false, isClosable = false, allowFloatingBar = true, showToolbarHeader = true, showBackButton = true, showToolbarCloseButton = false, disableAccordionIfSingleCategory = false, editPdfForm = false, openS4TViewer = false, showTodoDcmtForm = false, showToppyDraggableHelpCenter = true, toppyHelpCenterUsePortal = false, showNoDcmtFoundMessage = true, enablePinIcons = true, showCounterConfig = true, showActionsColumn = false, compactPager = false, onRowDuplicateRequest,
70
70
  // Counters
71
71
  counterConfig,
72
72
  // Callbacks (optional)
@@ -396,7 +396,7 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
396
396
  openTaskFormHandler,
397
397
  },
398
398
  });
399
- const { isOpenDcmtForm, openFormHandler, dcmtFormLayoutMode, onDcmtFormOpenChange, showSearchTMDatagrid, showDcmtTypesSelection, showExportForm, 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;
399
+ const { isOpenDcmtForm, isDcmtFormModal, openFormHandler, dcmtFormLayoutMode, onDcmtFormOpenChange, showSearchTMDatagrid, showDcmtTypesSelection, showExportForm, 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;
400
400
  // ----- Azioni della colonna di riga (showActionsColumn) -----------------------------------------
401
401
  // Le voci di operationItems agiscono sui documenti selezionati o, in mancanza, su quello a fuoco:
402
402
  // non accettano una riga come argomento. Il click sull'icona quindi azzera la selezione e porta il
@@ -404,6 +404,10 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
404
404
  // l'hook ricalcola in un effect (un render più tardi): lanciare l'operazione prima significherebbe
405
405
  // eseguirla sulla riga precedente.
406
406
  const pendingRowActionRef = useRef(undefined);
407
+ // La callback è letta da una ref: dipendere dal prop renderebbe nuova requestRowAction, e con essa
408
+ // la colonna delle azioni, a ogni render del padre (vedi actionsColumn in TMSearchResultGrid)
409
+ const onRowDuplicateRequestRef = useRef(onRowDuplicateRequest);
410
+ onRowDuplicateRequestRef.current = onRowDuplicateRequest;
407
411
  const requestRowAction = useCallback((operationId, rowData) => {
408
412
  if (!showActionsColumn)
409
413
  return;
@@ -411,6 +415,11 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
411
415
  ShowAlert({ message: SDKUI_Localizator.InvalidDcmt, mode: "warning", duration: 3000 });
412
416
  return;
413
417
  }
418
+ // Duplica ridefinita dal chiamante: agisce sulla riga cliccata, senza toccare fuoco e selezione
419
+ if (operationId === 'dup' && onRowDuplicateRequestRef.current) {
420
+ onRowDuplicateRequestRef.current(rowData);
421
+ return;
422
+ }
414
423
  pendingRowActionRef.current = { operationId, TID: rowData.TID, DID: rowData.DID };
415
424
  isFirstAutoFocusRef.current = false;
416
425
  setSelectedItems([]);
@@ -969,7 +978,7 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
969
978
  ], [tmSearchResult, tmBlog, tmSysMetadata, tmDcmtPreview, tmFullTextSearch, showToolbarHeader, context, isMobile, backHandler, backHandlerSecondary, isClosable, onBack]);
970
979
  /** Contenuto del panel manager: con groupId sta nel provider del contenitore, altrimenti in quello qui sotto */
971
980
  const panelManagerContent = (_jsxs(_Fragment, { children: [_jsx(PanelDisabledStateHandler, { isBoardDisabled: isBoardDisabled, savedBlogVisible: savedBlogVisible }), isLayoutPersisted && _jsx(SavedLayoutSyncHandler, { disabledPanelIds: disabledLayoutPanelIds }), _jsx(ShowMainPanelBridge, { showMainPanelRef: showMainPanelRef }), _jsx(TMPanelManagerContainer, { panels: initialPanels, direction: "horizontal", parentId: groupId, showToolbar: showSearchResultSidebar })] }));
972
- return (_jsxs(StyledMultiViewPanel, { "$isVisible": isVisible, children: [_jsx(StyledMultiViewPanel, { "$isVisible": !isOpenDcmtForm && !isOpenDetails && !isOpenMaster, style: {
981
+ return (_jsxs(StyledMultiViewPanel, { "$isVisible": isVisible, children: [_jsx(StyledMultiViewPanel, { "$isVisible": (!isOpenDcmtForm || isDcmtFormModal) && !isOpenDetails && !isOpenMaster, style: {
973
982
  display: 'flex',
974
983
  flexDirection: isMobile ? 'column' : 'row',
975
984
  justifyContent: 'space-between',
@@ -1002,7 +1011,7 @@ const findOperationItemById = (items, id) => {
1002
1011
  const rowActions = [
1003
1012
  { operationId: 'open-form', icon: _jsx(IconEdit, { fontSize: 18, color: TMColors.primary }) },
1004
1013
  { operationId: 'dup', icon: _jsx(IconDuplicate, { fontSize: 18, color: TMColors.tertiary }) },
1005
- { operationId: 'del-log', icon: _jsx(IconDelete, { fontSize: 18, color: TMColors.error }) },
1014
+ { operationId: 'del-log', icon: _jsx(IconDelete, { fontSize: 18, color: TMColors.error }), isVisible: (rowData) => rowData?.ISLOGDEL != 1 },
1006
1015
  ];
1007
1016
  const rowActionsStyles = {
1008
1017
  cell: { display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '14px' },
@@ -1449,6 +1458,8 @@ const TMSearchResultGrid = ({ openInOffice, fromDTD, operationItems, allUsers, i
1449
1458
  const operation = findOperationItemById(operationItemsRef.current, action.operationId);
1450
1459
  if (!operation || operation.visible === false)
1451
1460
  return null;
1461
+ if (action.isVisible && !action.isVisible(cellData.data))
1462
+ return null;
1452
1463
  return (_jsx("span", { style: rowActionsStyles.icon, title: operation.name, onClick: () => onRowActionRequest?.(action.operationId, cellData.data), children: action.icon }, action.operationId));
1453
1464
  }) })),
1454
1465
  }), [onRowActionRequest]);
@@ -1499,9 +1510,9 @@ const TMSearchResultGrid = ({ openInOffice, fromDTD, operationItems, allUsers, i
1499
1510
  return _jsxs("div", { style: { width: "100%", height: "100%" }, children: [!isDataGridReady && (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', height: '100%', width: '100%', gap: '10px' }, children: [_jsx(LoadIndicator, { height: 60, width: 60 }), _jsx("div", { children: SDKUI_Localizator.Loading })] })), isDataGridReady && _jsx(TMDataGrid, { ref: dataGridRef, id: "tm-search-result", keyExpr: "rowIndex", dataColumns: dataColumns, dataSource: dataSource, customizeColumns: customizeColumns, repaintChangesOnly: true, selectedRowKeys: selectedRowKeys, focusedRowKey: disableAutoFocus ? undefined : Number(focusedItem?.rowIndex ?? 0), showSearchPanel: showSearchTMDatagrid, showFilterPanel: true, sorting: { mode: "multiple" }, selection: { mode: allowMultipleSelection ? 'multiple' : 'single' }, pageSize: pageSize, compactPager: compactPager, onSelectionChanged: handleSelectionChange, onFocusedRowChanged: handleFocusedRowChange, onRowDblClick: onRowDblClick, onContentReady: onContentReady, showHeaderColumnChooser: true, onKeyDown: onKeyDown, customContextMenuItems: operationItems, counterConfig: gridCounterConfig })] });
1500
1511
  };
1501
1512
  //#region TMSearchResultSelector
1502
- const StyledItemTemplate = styled.div `
1503
- background: ${(props) => props.$isSelected ? 'oklch(from var(--dx-color-primary) l c h / .2) !important' : 'transparent'};
1504
- cursor: pointer;
1513
+ const StyledItemTemplate = styled.div `
1514
+ background: ${(props) => props.$isSelected ? 'oklch(from var(--dx-color-primary) l c h / .2) !important' : 'transparent'};
1515
+ cursor: pointer;
1505
1516
  `;
1506
1517
  const MemoizedStyledItemTemplate = React.memo(StyledItemTemplate);
1507
1518
  const TMSearchResultSelector = ({ searchResults = [], disableAccordionIfSingleCategory = false, selectedTID, selectedSearchResult, autoSelectFirst = true, onSelectionChanged }) => {
@@ -1708,85 +1719,85 @@ const TMDcmtPreviewWrapper = ({ refreshPreviewTrigger, currentDcmt, isVisible, i
1708
1719
  return (_jsx(TMDcmtPreview, { dcmtData: currentDcmt, onClosePanel: (!isMobile && countVisibleLeafPanels() > 1) ? () => setPanelVisibilityById('tmDcmtPreview', false) : undefined, onBack: onBack, allowMaximize: !isMobile && countVisibleLeafPanels() > 1, onMaximizePanel: (!isMobile && countVisibleLeafPanels() > 1) ? () => toggleMaximize("tmDcmtPreview") : undefined, isResizingActive: isResizingActive, isVisible: isVisible && isPageVisible }, refreshPreviewTrigger));
1709
1720
  };
1710
1721
  // Styled Components
1711
- const StyledPlaceholder = styled.div `
1712
- padding: 20px;
1713
- text-align: center;
1714
- color: #888;
1722
+ const StyledPlaceholder = styled.div `
1723
+ padding: 20px;
1724
+ text-align: center;
1725
+ color: #888;
1715
1726
  `;
1716
- const StyledIndexingInfoSection = styled.div `
1717
- padding: 15px;
1718
- border-top: 1px solid #e0e0e0;
1719
- background: linear-gradient(to bottom, #f9f9f9, #f5f5f5);
1720
- display: flex;
1721
- flex-direction: column;
1722
- gap: 10px;
1727
+ const StyledIndexingInfoSection = styled.div `
1728
+ padding: 15px;
1729
+ border-top: 1px solid #e0e0e0;
1730
+ background: linear-gradient(to bottom, #f9f9f9, #f5f5f5);
1731
+ display: flex;
1732
+ flex-direction: column;
1733
+ gap: 10px;
1723
1734
  `;
1724
- const StyledIndexingToggle = styled.button `
1725
- display: flex;
1726
- align-items: center;
1727
- justify-content: space-between;
1728
- width: 100%;
1729
- padding: 10px 16px;
1730
- background: white;
1731
- border: 1px solid #d0d0d0;
1732
- border-radius: 6px;
1733
- cursor: ${props => props.disabled ? 'not-allowed' : 'pointer'};
1734
- transition: all 0.2s ease;
1735
- font-size: 14px;
1736
- font-weight: 500;
1737
- color: #333;
1738
- opacity: ${props => props.disabled ? 0.6 : 1};
1739
- box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
1740
-
1741
- &:hover:not(:disabled) {
1742
- background: #f8f8f8;
1743
- border-color: #2196F3;
1744
- box-shadow: 0 2px 4px rgba(33, 150, 243, 0.2);
1745
- }
1746
-
1747
- &:active:not(:disabled) {
1748
- transform: translateY(1px);
1749
- box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
1750
- }
1735
+ const StyledIndexingToggle = styled.button `
1736
+ display: flex;
1737
+ align-items: center;
1738
+ justify-content: space-between;
1739
+ width: 100%;
1740
+ padding: 10px 16px;
1741
+ background: white;
1742
+ border: 1px solid #d0d0d0;
1743
+ border-radius: 6px;
1744
+ cursor: ${props => props.disabled ? 'not-allowed' : 'pointer'};
1745
+ transition: all 0.2s ease;
1746
+ font-size: 14px;
1747
+ font-weight: 500;
1748
+ color: #333;
1749
+ opacity: ${props => props.disabled ? 0.6 : 1};
1750
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
1751
+
1752
+ &:hover:not(:disabled) {
1753
+ background: #f8f8f8;
1754
+ border-color: #2196F3;
1755
+ box-shadow: 0 2px 4px rgba(33, 150, 243, 0.2);
1756
+ }
1757
+
1758
+ &:active:not(:disabled) {
1759
+ transform: translateY(1px);
1760
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
1761
+ }
1751
1762
  `;
1752
- const StyledLeftContent = styled.div `
1753
- display: flex;
1754
- align-items: center;
1755
- gap: 8px;
1763
+ const StyledLeftContent = styled.div `
1764
+ display: flex;
1765
+ align-items: center;
1766
+ gap: 8px;
1756
1767
  `;
1757
- const StyledRightContent = styled.div `
1758
- display: flex;
1759
- align-items: center;
1760
- gap: 8px;
1768
+ const StyledRightContent = styled.div `
1769
+ display: flex;
1770
+ align-items: center;
1771
+ gap: 8px;
1761
1772
  `;
1762
- const StyledChevron = styled.span `
1763
- transition: transform 0.2s ease;
1764
- transform: ${props => props.$isOpen ? 'rotate(180deg)' : 'rotate(0deg)'};
1765
- color: #666;
1766
- font-size: 12px;
1773
+ const StyledChevron = styled.span `
1774
+ transition: transform 0.2s ease;
1775
+ transform: ${props => props.$isOpen ? 'rotate(180deg)' : 'rotate(0deg)'};
1776
+ color: #666;
1777
+ font-size: 12px;
1767
1778
  `;
1768
- const StyledIndexingInfoBox = styled.div `
1769
- position: relative;
1770
- background: white;
1771
- border: 1px solid #e0e0e0;
1772
- border-radius: 6px;
1773
- padding: 12px;
1774
- max-height: 200px;
1775
- overflow: auto;
1776
- font-family: 'Courier New', monospace;
1777
- font-size: 12px;
1778
- color: #333;
1779
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
1780
- animation: slideDown 0.2s ease;
1781
-
1782
- @keyframes slideDown {
1783
- from {
1784
- opacity: 0;
1785
- transform: translateY(-10px);
1786
- }
1787
- to {
1788
- opacity: 1;
1789
- transform: translateY(0);
1790
- }
1791
- }
1779
+ const StyledIndexingInfoBox = styled.div `
1780
+ position: relative;
1781
+ background: white;
1782
+ border: 1px solid #e0e0e0;
1783
+ border-radius: 6px;
1784
+ padding: 12px;
1785
+ max-height: 200px;
1786
+ overflow: auto;
1787
+ font-family: 'Courier New', monospace;
1788
+ font-size: 12px;
1789
+ color: #333;
1790
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
1791
+ animation: slideDown 0.2s ease;
1792
+
1793
+ @keyframes slideDown {
1794
+ from {
1795
+ opacity: 0;
1796
+ transform: translateY(-10px);
1797
+ }
1798
+ to {
1799
+ opacity: 1;
1800
+ transform: translateY(0);
1801
+ }
1802
+ }
1792
1803
  `;
@@ -1 +1 @@
1
- export declare const licenseKey = "LCPv1(%@j2<F<eQy:2N+f,7R@!i)BcQ,N374Qe%m;9etySj@gGntp77@@:<VJ77@j!_t<%yd-Siei,7FYV%!G>7a(cj%z3Ltv!ot1SeaicLr[3o,6>H:1(e,jSQt$3bREp%tv%H+4InRJ3J88Gn+aIidEee--SQVb,yVH7QRgSpm:2be9I%,;Z+Qmk%!<9jyG6%!6pLdQ6Lt<!N-Q%iR)6%fBIR+:2<)<!Qr->%X<2p@62%!F2bR62<-$2i-)2i-FI%d@G<-@!jy$2iRQ6b-v!jyvIg!:ajzvaj@Fpg!$2b-FIp9;9K-:>Q4:G_@@9Qdv6<!F6<dFI7yFI7yFI7Ll";
1
+ export declare const licenseKey = "LCPv1>7eY(j4LeYSi9_V6!J4He7,1:JfoSYV:%QDL7+aJZee3%oR>!N,(7<,!cyaX>Qta3%e7(nrB7+a42R-8pJ@(>Qm<IJ+(Sn,e3J+:VN-;(y,$Zj4yZ74[S<FZ:ee49ntV(%Xn3Lr19Y9[7iR3:74X7RV(6ye(Iea1cNSS3gFf!N91%R-ppQQmk79-6%)<SQf-I_@F6_y6SjzB6_!Q2bREIgF:aQdE!KF6>%X<2p@62%!F2bR62<-$2i-)2i-FI%d@G<-@!jy$2iRQ6b-v!jyvIg!:ajzvaj@Fpg!$2b-FIp9;9K-:>Q4:G_@@9Qdv6<!F6<dFI7yFI7yFI7Ll";
@@ -1,3 +1,3 @@
1
1
  // Auto-generated by devextreme-license.
2
2
  // Do not commit this file to source control.
3
- export const licenseKey = 'LCPv1(%@j2<F<eQy:2N+f,7R@!i)BcQ,N374Qe%m;9etySj@gGntp77@@:<VJ77@j!_t<%yd-Siei,7FYV%!G>7a(cj%z3Ltv!ot1SeaicLr[3o,6>H:1(e,jSQt$3bREp%tv%H+4InRJ3J88Gn+aIidEee--SQVb,yVH7QRgSpm:2be9I%,;Z+Qmk%!<9jyG6%!6pLdQ6Lt<!N-Q%iR)6%fBIR+:2<)<!Qr->%X<2p@62%!F2bR62<-$2i-)2i-FI%d@G<-@!jy$2iRQ6b-v!jyvIg!:ajzvaj@Fpg!$2b-FIp9;9K-:>Q4:G_@@9Qdv6<!F6<dFI7yFI7yFI7Ll';
3
+ export const licenseKey = 'LCPv1>7eY(j4LeYSi9_V6!J4He7,1:JfoSYV:%QDL7+aJZee3%oR>!N,(7<,!cyaX>Qta3%e7(nrB7+a42R-8pJ@(>Qm<IJ+(Sn,e3J+:VN-;(y,$Zj4yZ74[S<FZ:ee49ntV(%Xn3Lr19Y9[7iR3:74X7RV(6ye(Iea1cNSS3gFf!N91%R-ppQQmk79-6%)<SQf-I_@F6_y6SjzB6_!Q2bREIgF:aQdE!KF6>%X<2p@62%!F2bR62<-$2i-)2i-FI%d@G<-@!jy$2iRQ6b-v!jyvIg!:ajzvaj@Fpg!$2b-FIp9;9K-:>Q4:G_@@9Qdv6<!F6<dFI7yFI7yFI7Ll';
@@ -152,6 +152,15 @@ export type MultiDetailPanelState = {
152
152
  };
153
153
  /** Stato dei pannelli del riepilogo, indicizzato per id pannello */
154
154
  export type MultiDetailPanelStates = Record<string, MultiDetailPanelState>;
155
+ /** Passi dell'archiviazione multipla di cui si ricordano le dimensioni del modale */
156
+ export type MultiDetailModalStep = 'selectDcmtType' | 'archiveSummary';
157
+ /** Dimensioni salvate del modale, in formato CSS (es. '1200px') */
158
+ export type MultiDetailModalSize = {
159
+ width: string;
160
+ height: string;
161
+ };
162
+ /** Dimensioni del modale indicizzate per passo */
163
+ export type MultiDetailModalSizes = Partial<Record<MultiDetailModalStep, MultiDetailModalSize>>;
155
164
  export declare class ArchivingSettings {
156
165
  mruTIDs: number[];
157
166
  defaultTree: number;
@@ -165,6 +174,11 @@ export declare class ArchivingSettings {
165
174
  * sezioni, che è in multiDetailSectionsOpen: assente finché l'utente non sposta un separatore.
166
175
  */
167
176
  multiDetailPanelStates?: MultiDetailPanelStates;
177
+ /**
178
+ * Dimensioni del modale scelte dall'utente, distinte per passo (scelta del tipo e riepilogo partono
179
+ * da dimensioni molto diverse). Assenti finché non ridimensiona il modale in quel passo.
180
+ */
181
+ multiDetailModalSizes?: MultiDetailModalSizes;
168
182
  }
169
183
  /** Stato di apertura salvato delle sezioni: i default coprono l'impostazione mai modificata dall'utente */
170
184
  export declare const getMultiDetailSectionsOpen: () => MultiDetailSectionsOpen;
@@ -174,11 +188,16 @@ export declare const getMultiDetailPanelStates: () => MultiDetailPanelStates;
174
188
  /** Il panel manager applica lo stato salvato solo se esiste: altrimenti riparte dalle quote di default */
175
189
  export declare const hasMultiDetailPanelStates: () => boolean;
176
190
  export declare const saveMultiDetailPanelStates: (panelStates: MultiDetailPanelStates) => void;
177
- /** C'è un layout dell'utente da cancellare: nel local storage le due impostazioni ci sono solo se diverse dai default */
191
+ /** Assenti se l'utente non ha mai ridimensionato il modale in quel passo */
192
+ export declare const getMultiDetailModalSize: (step: MultiDetailModalStep) => MultiDetailModalSize | undefined;
193
+ export declare const hasMultiDetailModalSizes: () => boolean;
194
+ /** Salva le dimensioni del passo indicato, lasciando invariate quelle degli altri */
195
+ export declare const saveMultiDetailModalSize: (step: MultiDetailModalStep, size: MultiDetailModalSize) => void;
196
+ /** C'è un layout dell'utente da cancellare: nel local storage le impostazioni ci sono solo se diverse dai default */
178
197
  export declare const hasMultiDetailLayout: () => boolean;
179
198
  /**
180
- * Elimina dal local storage il layout configurato dall'utente per il riepilogo: altezze dei pannelli
181
- * e sezioni collassate. Alla riapertura le sezioni sono tutte espanse con le quote di default.
199
+ * Elimina dal local storage il layout configurato dall'utente: altezze dei pannelli, sezioni
200
+ * collassate e dimensioni del modale. Tutto torna ai default: modale, quote e sezioni espanse.
182
201
  */
183
202
  export declare const clearMultiDetailLayout: () => void;
184
203
  export declare class FullTextSettings {
@@ -260,17 +260,31 @@ export const saveMultiDetailPanelStates = (panelStates) => {
260
260
  // Il proxy persiste solo intercettando l'assegnazione di una proprietà: la mappa va riassegnata per intero
261
261
  SDKUI_Globals.userSettings.archivingSettings.multiDetailPanelStates = { ...panelStates };
262
262
  };
263
- /** C'è un layout dell'utente da cancellare: nel local storage le due impostazioni ci sono solo se diverse dai default */
264
- export const hasMultiDetailLayout = () => hasMultiDetailPanelStates() || SDKUI_Globals.userSettings.archivingSettings?.multiDetailSectionsOpen !== undefined;
263
+ /** Assenti se l'utente non ha mai ridimensionato il modale in quel passo */
264
+ export const getMultiDetailModalSize = (step) => SDKUI_Globals.userSettings.archivingSettings?.multiDetailModalSizes?.[step];
265
+ export const hasMultiDetailModalSizes = () => Object.keys(SDKUI_Globals.userSettings.archivingSettings?.multiDetailModalSizes ?? {}).length > 0;
266
+ /** Salva le dimensioni del passo indicato, lasciando invariate quelle degli altri */
267
+ export const saveMultiDetailModalSize = (step, size) => {
268
+ if (!size?.width || !size?.height)
269
+ return;
270
+ // Il proxy persiste solo intercettando l'assegnazione di una proprietà: la mappa va riassegnata per intero
271
+ SDKUI_Globals.userSettings.archivingSettings.multiDetailModalSizes = {
272
+ ...SDKUI_Globals.userSettings.archivingSettings?.multiDetailModalSizes,
273
+ [step]: { ...size },
274
+ };
275
+ };
276
+ /** C'è un layout dell'utente da cancellare: nel local storage le impostazioni ci sono solo se diverse dai default */
277
+ export const hasMultiDetailLayout = () => hasMultiDetailPanelStates() || hasMultiDetailModalSizes() || SDKUI_Globals.userSettings.archivingSettings?.multiDetailSectionsOpen !== undefined;
265
278
  /**
266
- * Elimina dal local storage il layout configurato dall'utente per il riepilogo: altezze dei pannelli
267
- * e sezioni collassate. Alla riapertura le sezioni sono tutte espanse con le quote di default.
279
+ * Elimina dal local storage il layout configurato dall'utente: altezze dei pannelli, sezioni
280
+ * collassate e dimensioni del modale. Tutto torna ai default: modale, quote e sezioni espanse.
268
281
  */
269
282
  export const clearMultiDetailLayout = () => {
270
- // Il proxy persiste solo intercettando l'assegnazione di una proprietà: entrambi i campi vanno riassegnati.
283
+ // Il proxy persiste solo intercettando l'assegnazione di una proprietà: tutti i campi vanno riassegnati.
271
284
  // Senza valore spariscono dal local storage e alla riapertura tornano validi i default
272
285
  SDKUI_Globals.userSettings.archivingSettings.multiDetailPanelStates = undefined;
273
286
  SDKUI_Globals.userSettings.archivingSettings.multiDetailSectionsOpen = undefined;
287
+ SDKUI_Globals.userSettings.archivingSettings.multiDetailModalSizes = undefined;
274
288
  };
275
289
  export class FullTextSettings {
276
290
  constructor() {
@@ -172,6 +172,12 @@ export declare class SDKUI_Localizator {
172
172
  static get DcmtTypeSelectOrQuickSearch(): "Wählen Sie einen Dokumenttyp oder eine Schnellsuche aus" | "Select a document type or quick search" | "Seleccione un tipo de documento o búsqueda rápida" | "Sélectionnez un type de document ou une recherche rapide" | "Selecione um tipo de documento ou pesquisa rápida" | "Selezionare un tipo documento o ricerca rapida";
173
173
  static get Default(): "Standard" | "Default" | "Predeterminado" | "Defaultão";
174
174
  static get DefaultFolder(): string;
175
+ static get DcmtTypeStructureNotRetrieved(): "Die Struktur des Dokumenttyps konnte nicht abgerufen werden." | "Unable to retrieve the document type structure." | "No se ha podido recuperar la estructura del tipo de documento." | "Impossible de récupérer la structure du type de document." | "Não foi possível recuperar a estrutura do tipo de documento." | "Non è stato possibile recuperare la struttura del tipo documento.";
176
+ static get DetailCopiedToArchiveList(): "Detail zu den zu archivierenden Details kopiert" | "Detail copied to the details to archive" | "Detalle copiado a los detalles a archivar" | "Détail copié dans les détails à archiver" | "Detalhe copiado para os detalhes a arquivar" | "Dettaglio copiato tra i dettagli da archiviare";
177
+ static get DetailFileNotRetrieved(): "Datei des Details nicht abgerufen" | "Detail file not retrieved" | "Archivo del detalle no recuperado" | "Fichier du détail non récupéré" | "Ficheiro do detalhe não recuperado" | "File del dettaglio non recuperato";
178
+ static get DetailFileToAddFromRowForm(): "Der Dokumenttyp erfordert eine Datei: Fügen Sie sie über das Formular der Zeile hinzu." | "The document type requires a file: add it from the row form." | "El tipo de documento requiere un archivo: añádelo desde el formulario de la fila." | "Le type de document exige un fichier : ajoutez-le depuis le formulaire de la ligne." | "O tipo de documento exige um ficheiro: adicione-o a partir do formulário da linha." | "Il tipo documento richiede un file: aggiungilo dal form della riga.";
179
+ static get DetailMetadataNotRetrieved(): "Die Metadaten des Details konnten nicht abgerufen werden." | "Unable to retrieve the detail metadata." | "No se han podido recuperar los metadatos del detalle." | "Impossible de récupérer les métadonnées du détail." | "Não foi possível recuperar os metadados do detalhe." | "Non è stato possibile recuperare i metadati del dettaglio.";
180
+ static get DetailNotOfSelectedDcmtType(): "Das Detail gehört nicht zum ausgewählten Dokumenttyp." | "The detail does not belong to the selected document type." | "El detalle no pertenece al tipo de documento seleccionado." | "Le détail n'appartient pas au type de document sélectionné." | "O detalhe não pertence ao tipo de documento selecionado." | "Il dettaglio non è del tipo documento selezionato.";
175
181
  static get Details(): "Einzelheiten" | "Details" | "Detalles" | "Détails" | "detalhes" | "Dettagli";
176
182
  static get DetailsToArchive(): string;
177
183
  static get Delete(): "Löschen" | "Delete" | "Borrar" | "Supprimer" | "Excluir" | "Elimina";
@@ -454,6 +460,7 @@ export declare class SDKUI_Localizator {
454
460
  static get LastUpdateTime(): "Letzte Änderung" | "Last update Time" | "Última modificación" | "Dernière modifie" | "Última modificação" | "Ultima modifica";
455
461
  static get LastVersion(): string;
456
462
  static get Latest(): string;
463
+ static get LayoutRemoved(): string;
457
464
  static get LexProt(): "Lex-Schutz" | "Lex protection" | "Protección Lex" | "Protection Lex" | "Proteção Lex" | "Protezione Lex";
458
465
  static get Line(): "Linie" | "Line" | "Línea" | "Ligne" | "Linha" | "Linea";
459
466
  static get List(): "Liste" | "List" | "Lista";
@@ -690,6 +697,7 @@ export declare class SDKUI_Localizator {
690
697
  static get RemoveFromWorkgroup(): string;
691
698
  static get RemoveNamedPreferredCredentials(): "Möchten Sie die Anmeldedaten '{{0}}' entfernen, die als bevorzugt festgelegt wurden?" | "Do you want to remove the '{{0}}' credentials set as preferred?" | "¿Quieres eliminar las credenciales '{{0}}' configuradas como preferidas?" | "Voulez-vous supprimer les identifiants '{{0}}' définis comme préférés ?" | "Deseja remover as credenciais '{{0}}' definidas como preferidas?" | "Vuoi rimuovere le credenziali '{{0}}' impostate come preferite?";
692
699
  static get RemoveLayout(): string;
700
+ static get RemoveLayout_Confirm(): string;
693
701
  static get RemovingFromList(): string;
694
702
  static get RemoveSignatureIfPresent(): string;
695
703
  static get RememberCredentials(): "Anmeldedaten merken" | "Remember credentials" | "Recordar credenciales" | "Se souvenir des identifiants" | "Lembrar credenciais" | "Ricorda credenziali";
@@ -970,6 +978,7 @@ export declare class SDKUI_Localizator {
970
978
  static get WrittenOn(): "Geschrieben am" | "Written on" | "Escrito el" | "Écrit le" | "Escrito em" | "Scritto il";
971
979
  static get YouDoNotHavePermissionsToArchiveDetailDocumentsOfThisType(): "Sie haben keine Berechtigung, Detaildokumente dieses Typs zu archivieren." | "You do not have permissions to archive detail documents of this type." | "No tienes permisos para archivar documentos de detalle de este tipo." | "Vous n'avez pas les permissions pour archiver les documents détail de ce type." | "Você não tem permissões para arquivar documentos de detalhe deste tipo." | "Non hai i permessi per archiviare documenti di dettaglio di questo tipo.";
972
980
  static get YouDoNotHavePermissionsToArchiveMasterDocumentsOfThisType(): "Sie haben keine Berechtigung, Master-Dokumente dieses Typs zu archivieren." | "You do not have permissions to archive master documents of this type." | "No tienes permisos para archivar documentos maestros de este tipo." | "Vous n'avez pas les permissions pour archiver les documents maîtres de ce type." | "Você não tem permissões para arquivar documentos mestres deste tipo." | "Non hai i permessi per archiviare documenti master di questo tipo.";
981
+ static get YouDoNotHavePermissionsToRetrieveTheFile(): "Sie haben keine Berechtigung, die Datei abzurufen." | "You do not have permissions to retrieve the file." | "No tienes permisos para recuperar el archivo." | "Vous n'avez pas les permissions pour récupérer le fichier." | "Você não tem permissões para recuperar o ficheiro." | "Non hai i permessi per recuperare il file.";
973
982
  static get Yes(): "Ja" | "Yes" | "Sí" | "Oui" | "Sim" | "Sì";
974
983
  static get ZipFileName(): string;
975
984
  static get ZipCreatedSavedInFolder(): string;
@@ -1712,6 +1712,66 @@ export class SDKUI_Localizator {
1712
1712
  return "Cartella di default";
1713
1713
  }
1714
1714
  }
1715
+ static get DcmtTypeStructureNotRetrieved() {
1716
+ switch (this._cultureID) {
1717
+ case CultureIDs.De_DE: return "Die Struktur des Dokumenttyps konnte nicht abgerufen werden.";
1718
+ case CultureIDs.En_US: return "Unable to retrieve the document type structure.";
1719
+ case CultureIDs.Es_ES: return "No se ha podido recuperar la estructura del tipo de documento.";
1720
+ case CultureIDs.Fr_FR: return "Impossible de récupérer la structure du type de document.";
1721
+ case CultureIDs.Pt_PT: return "Não foi possível recuperar a estrutura do tipo de documento.";
1722
+ default: return "Non è stato possibile recuperare la struttura del tipo documento.";
1723
+ }
1724
+ }
1725
+ static get DetailCopiedToArchiveList() {
1726
+ switch (this._cultureID) {
1727
+ case CultureIDs.De_DE: return "Detail zu den zu archivierenden Details kopiert";
1728
+ case CultureIDs.En_US: return "Detail copied to the details to archive";
1729
+ case CultureIDs.Es_ES: return "Detalle copiado a los detalles a archivar";
1730
+ case CultureIDs.Fr_FR: return "Détail copié dans les détails à archiver";
1731
+ case CultureIDs.Pt_PT: return "Detalhe copiado para os detalhes a arquivar";
1732
+ default: return "Dettaglio copiato tra i dettagli da archiviare";
1733
+ }
1734
+ }
1735
+ static get DetailFileNotRetrieved() {
1736
+ switch (this._cultureID) {
1737
+ case CultureIDs.De_DE: return "Datei des Details nicht abgerufen";
1738
+ case CultureIDs.En_US: return "Detail file not retrieved";
1739
+ case CultureIDs.Es_ES: return "Archivo del detalle no recuperado";
1740
+ case CultureIDs.Fr_FR: return "Fichier du détail non récupéré";
1741
+ case CultureIDs.Pt_PT: return "Ficheiro do detalhe não recuperado";
1742
+ default: return "File del dettaglio non recuperato";
1743
+ }
1744
+ }
1745
+ static get DetailFileToAddFromRowForm() {
1746
+ switch (this._cultureID) {
1747
+ case CultureIDs.De_DE: return "Der Dokumenttyp erfordert eine Datei: Fügen Sie sie über das Formular der Zeile hinzu.";
1748
+ case CultureIDs.En_US: return "The document type requires a file: add it from the row form.";
1749
+ case CultureIDs.Es_ES: return "El tipo de documento requiere un archivo: añádelo desde el formulario de la fila.";
1750
+ case CultureIDs.Fr_FR: return "Le type de document exige un fichier : ajoutez-le depuis le formulaire de la ligne.";
1751
+ case CultureIDs.Pt_PT: return "O tipo de documento exige um ficheiro: adicione-o a partir do formulário da linha.";
1752
+ default: return "Il tipo documento richiede un file: aggiungilo dal form della riga.";
1753
+ }
1754
+ }
1755
+ static get DetailMetadataNotRetrieved() {
1756
+ switch (this._cultureID) {
1757
+ case CultureIDs.De_DE: return "Die Metadaten des Details konnten nicht abgerufen werden.";
1758
+ case CultureIDs.En_US: return "Unable to retrieve the detail metadata.";
1759
+ case CultureIDs.Es_ES: return "No se han podido recuperar los metadatos del detalle.";
1760
+ case CultureIDs.Fr_FR: return "Impossible de récupérer les métadonnées du détail.";
1761
+ case CultureIDs.Pt_PT: return "Não foi possível recuperar os metadados do detalhe.";
1762
+ default: return "Non è stato possibile recuperare i metadati del dettaglio.";
1763
+ }
1764
+ }
1765
+ static get DetailNotOfSelectedDcmtType() {
1766
+ switch (this._cultureID) {
1767
+ case CultureIDs.De_DE: return "Das Detail gehört nicht zum ausgewählten Dokumenttyp.";
1768
+ case CultureIDs.En_US: return "The detail does not belong to the selected document type.";
1769
+ case CultureIDs.Es_ES: return "El detalle no pertenece al tipo de documento seleccionado.";
1770
+ case CultureIDs.Fr_FR: return "Le détail n'appartient pas au type de document sélectionné.";
1771
+ case CultureIDs.Pt_PT: return "O detalhe não pertence ao tipo de documento selecionado.";
1772
+ default: return "Il dettaglio non è del tipo documento selezionato.";
1773
+ }
1774
+ }
1715
1775
  static get Details() {
1716
1776
  switch (this._cultureID) {
1717
1777
  case CultureIDs.De_DE: return "Einzelheiten";
@@ -4508,6 +4568,16 @@ export class SDKUI_Localizator {
4508
4568
  default: return "Ultimi";
4509
4569
  }
4510
4570
  }
4571
+ static get LayoutRemoved() {
4572
+ switch (this._cultureID) {
4573
+ case CultureIDs.De_DE: return "Layout entfernt";
4574
+ case CultureIDs.En_US: return "Layout removed";
4575
+ case CultureIDs.Es_ES: return "Diseño eliminado";
4576
+ case CultureIDs.Fr_FR: return "Mise en page supprimée";
4577
+ case CultureIDs.Pt_PT: return "Layout removido";
4578
+ default: return "Layout rimosso";
4579
+ }
4580
+ }
4511
4581
  static get LexProt() {
4512
4582
  switch (this._cultureID) {
4513
4583
  case CultureIDs.De_DE: return "Lex-Schutz";
@@ -6879,6 +6949,16 @@ export class SDKUI_Localizator {
6879
6949
  default: return "Rimuovi Layout";
6880
6950
  }
6881
6951
  }
6952
+ static get RemoveLayout_Confirm() {
6953
+ switch (this._cultureID) {
6954
+ case CultureIDs.De_DE: return "Möchten Sie das Layout entfernen?";
6955
+ case CultureIDs.En_US: return "Do you want to remove the layout?";
6956
+ case CultureIDs.Es_ES: return "¿Desea eliminar el diseño?";
6957
+ case CultureIDs.Fr_FR: return "Voulez-vous supprimer la mise en page ?";
6958
+ case CultureIDs.Pt_PT: return "Deseja remover o layout?";
6959
+ default: return "Vuoi rimuovere il layout?";
6960
+ }
6961
+ }
6882
6962
  static get RemovingFromList() {
6883
6963
  switch (this._cultureID) {
6884
6964
  case CultureIDs.De_DE: return "Wird aus der Liste entfernt";
@@ -9692,6 +9772,16 @@ export class SDKUI_Localizator {
9692
9772
  default: return "Non hai i permessi per archiviare documenti master di questo tipo.";
9693
9773
  }
9694
9774
  }
9775
+ static get YouDoNotHavePermissionsToRetrieveTheFile() {
9776
+ switch (this._cultureID) {
9777
+ case CultureIDs.De_DE: return "Sie haben keine Berechtigung, die Datei abzurufen.";
9778
+ case CultureIDs.En_US: return "You do not have permissions to retrieve the file.";
9779
+ case CultureIDs.Es_ES: return "No tienes permisos para recuperar el archivo.";
9780
+ case CultureIDs.Fr_FR: return "Vous n'avez pas les permissions pour récupérer le fichier.";
9781
+ case CultureIDs.Pt_PT: return "Você não tem permissões para recuperar o ficheiro.";
9782
+ default: return "Non hai i permessi per recuperare il file.";
9783
+ }
9784
+ }
9695
9785
  static get Yes() {
9696
9786
  switch (this._cultureID) {
9697
9787
  case CultureIDs.De_DE: return "Ja";
@@ -110,6 +110,8 @@ export interface UseDocumentOperationsResult {
110
110
  renderDcmtOperations: React.ReactNode;
111
111
  features: {
112
112
  isOpenDcmtForm: boolean;
113
+ /** Il form del documento è aperto come popup sopra il contenuto, che resta visibile sotto */
114
+ isDcmtFormModal: boolean;
113
115
  openFormHandler: (layoutMode: LayoutModes) => void;
114
116
  dcmtFormLayoutMode: LayoutModes;
115
117
  onDcmtFormOpenChange: (isOpen: boolean, layoutMode: LayoutModes) => void;
@@ -150,6 +150,12 @@ export const useDocumentOperations = (props) => {
150
150
  const [showFloatingBar, setShowFloatingBar] = useState(allowFloatingBar);
151
151
  const [isOpenDcmtForm, setIsOpenDcmtForm] = useState(false);
152
152
  const [dcmtFormLayoutMode, setDcmtFormLayoutMode] = useState(LayoutModes.Update);
153
+ /**
154
+ * Il form del documento è a popup: si sovrappone a chi lo ha aperto, che resta visibile sotto e non
155
+ * va nascosto. Oltre ai contesti che lo chiedono (openDcmtFormAsModal) è il caso della duplicazione,
156
+ * che parte da un documento a video (LayoutModes.Ark con un DID) per proporne uno nuovo da archiviare.
157
+ */
158
+ const isDcmtFormModal = openDcmtFormAsModal || (dcmtFormLayoutMode === LayoutModes.Ark && focusedItem?.DID !== undefined);
153
159
  const [currentCustomButton, setCurrentCustomButton] = useState();
154
160
  // State to control the visibility of the details form
155
161
  const [showSearchTMDatagrid, setShowSearchTMDatagrid] = useState(false);
@@ -1393,8 +1399,8 @@ export const useDocumentOperations = (props) => {
1393
1399
  dcmtUtility,
1394
1400
  fetchRemoteCertificates
1395
1401
  };
1396
- const renderDcmtOperations = (_jsxs(_Fragment, { children: [(showExportForm && searchResult && dataColumns && dataSource && selectedRowKeys) && (_jsx(TMDataGridExportForm, { dataColumns: dataColumns, dataSource: dataSource, selectedRowKeys: selectedRowKeys, onCloseExportForm: () => setShowExportForm(false), searchResult: searchResult })), _jsx(StyledMultiViewPanel, { "$isVisible": isOpenDcmtForm, children: ((isOpenDcmtForm && focusedItem?.TID !== undefined && focusedItem?.DID !== undefined) &&
1397
- _jsx(TMDcmtForm, { isModal: openDcmtFormAsModal || (dcmtFormLayoutMode === LayoutModes.Ark && focusedItem?.DID !== undefined), titleModal: dtd?.name ?? '', TID: focusedItem.TID, DID: focusedItem.DID, allowButtonsRefs: true, showTodoDcmtForm: showTodoDcmtForm, layoutMode: dcmtFormLayoutMode, count: visibleItems?.length, itemIndex: visibleItems ? visibleItems.findIndex(o => o.rowIndex === focusedItem?.rowIndex) + 1 : undefined, canNext: canNavigateHandler ? canNavigateHandler('next') : false, canPrev: canNavigateHandler ? canNavigateHandler('prev') : false, onNext: () => onNavigateHandler && onNavigateHandler('next'), onPrev: () => onNavigateHandler && onNavigateHandler('prev'), onClose: () => { (false); onDcmtFormOpenChange(false, LayoutModes.Update); }, onWFOperationCompleted: onWFOperationCompleted, onTaskCreateRequest: onTaskCreateRequest, onSavedAsyncCallback: onSavedAsyncCallback, openS4TViewer: openS4TViewer, onOpenS4TViewerRequest: onOpenS4TViewerRequest, editPdfForm: editPdfForm, onOpenPdfEditorRequest: onOpenPdfEditorRequest, openFileUploaderPdfEditor: openFileUploaderPdfEditor, onReferenceClick: onReferenceClick, allTasks: allTasks, getAllTasks: getAllTasks, deleteTaskByIdsCallback: deleteTaskByIdsCallback, addTaskCallback: addTaskCallback, editTaskCallback: editTaskCallback, handleNavigateToWGs: handleNavigateToWGs, handleNavigateToDossiers: handleNavigateToDossiers, moreInfoTasks: getMoreInfoTasksForDocument(allTasks, focusedItem?.TID, focusedItem?.DID), showDcmtFormSidebar: showDcmtFormSidebar, datagridUtility: {
1402
+ const renderDcmtOperations = (_jsxs(_Fragment, { children: [(showExportForm && searchResult && dataColumns && dataSource && selectedRowKeys) && (_jsx(TMDataGridExportForm, { dataColumns: dataColumns, dataSource: dataSource, selectedRowKeys: selectedRowKeys, onCloseExportForm: () => setShowExportForm(false), searchResult: searchResult })), _jsx(StyledMultiViewPanel, { "$isVisible": isOpenDcmtForm && !isDcmtFormModal, children: ((isOpenDcmtForm && focusedItem?.TID !== undefined && focusedItem?.DID !== undefined) &&
1403
+ _jsx(TMDcmtForm, { isModal: isDcmtFormModal, titleModal: dtd?.name ?? '', TID: focusedItem.TID, DID: focusedItem.DID, allowButtonsRefs: true, showTodoDcmtForm: showTodoDcmtForm, layoutMode: dcmtFormLayoutMode, count: visibleItems?.length, itemIndex: visibleItems ? visibleItems.findIndex(o => o.rowIndex === focusedItem?.rowIndex) + 1 : undefined, canNext: canNavigateHandler ? canNavigateHandler('next') : false, canPrev: canNavigateHandler ? canNavigateHandler('prev') : false, onNext: () => onNavigateHandler && onNavigateHandler('next'), onPrev: () => onNavigateHandler && onNavigateHandler('prev'), onClose: () => { (false); onDcmtFormOpenChange(false, LayoutModes.Update); }, onWFOperationCompleted: onWFOperationCompleted, onTaskCreateRequest: onTaskCreateRequest, onSavedAsyncCallback: onSavedAsyncCallback, openS4TViewer: openS4TViewer, onOpenS4TViewerRequest: onOpenS4TViewerRequest, editPdfForm: editPdfForm, onOpenPdfEditorRequest: onOpenPdfEditorRequest, openFileUploaderPdfEditor: openFileUploaderPdfEditor, onReferenceClick: onReferenceClick, allTasks: allTasks, getAllTasks: getAllTasks, deleteTaskByIdsCallback: deleteTaskByIdsCallback, addTaskCallback: addTaskCallback, editTaskCallback: editTaskCallback, handleNavigateToWGs: handleNavigateToWGs, handleNavigateToDossiers: handleNavigateToDossiers, moreInfoTasks: getMoreInfoTasksForDocument(allTasks, focusedItem?.TID, focusedItem?.DID), showDcmtFormSidebar: showDcmtFormSidebar, datagridUtility: {
1398
1404
  onRefreshSearchAsyncDatagrid,
1399
1405
  onRefreshDataRowsAsync,
1400
1406
  refreshFocusedDataRowAsync,
@@ -1472,6 +1478,7 @@ export const useDocumentOperations = (props) => {
1472
1478
  renderDcmtOperations,
1473
1479
  features: {
1474
1480
  isOpenDcmtForm,
1481
+ isDcmtFormModal,
1475
1482
  openFormHandler,
1476
1483
  dcmtFormLayoutMode,
1477
1484
  onDcmtFormOpenChange,
@@ -91,6 +91,11 @@ export declare const useMultiMasterDetailDcmts: ({ dtd, masterDcmt, onClose }: U
91
91
  duplicateRow: (row: ArchiveRow | undefined) => void;
92
92
  upsertRow: (values: Array<MetadataValueDescriptorEx>, file: File | undefined) => void;
93
93
  updateRowValues: (rowId: number, changes: ArchiveRowChanges) => void;
94
+ copyArchivedDetailToRows: (dcmt: {
95
+ TID?: number | string;
96
+ DID?: number | string;
97
+ FILEEXT?: string;
98
+ } | undefined) => Promise<void>;
94
99
  stopOnFirstError: boolean;
95
100
  setStopOnFirstError: import("react").Dispatch<import("react").SetStateAction<boolean>>;
96
101
  isArchiving: boolean;
@@ -1,14 +1,14 @@
1
1
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
- import { ArchiveConstraints, ArchiveEngineByID, MetadataDataDomains, MetadataDataTypes, RelationCacheService, RelationTypes, ResultTypes, SDK_Globals } from '@topconsultnpm/sdk-ts';
2
+ import { AccessLevels, ArchiveConstraints, ArchiveEngineByID, DcmtOpers, GeneralRetrieveFormats, MetadataDataDomains, MetadataDataTypes, RelationCacheService, RelationTypes, ResultTypes, RetrieveFileOptions, SDK_Globals, SystemMIDsAsNumber } from '@topconsultnpm/sdk-ts';
3
3
  import { MetadataValueDescriptorEx } from '../ts';
4
- import { clearMultiDetailLayout, Globalization, getExceptionMessage, getMultiDetailSectionsOpen, hasMultiDetailLayout, saveMultiDetailSectionsOpen, SDKUI_Localizator } from '../helper';
4
+ import { clearMultiDetailLayout, Globalization, getExceptionMessage, getMultiDetailSectionsOpen, handleArchiveVisibility, hasMultiDetailLayout, saveMultiDetailSectionsOpen, SDKUI_Localizator } from '../helper';
5
5
  import { getDcmtMetadataByMid, getUserMetadataSorted, mapAssociationsFromMetadata } from '../helper/dcmtsHelper';
6
6
  import { ButtonNames, TMExceptionBoxManager, TMMessageBoxManager } from '../components/base/TMPopUp';
7
7
  import TMSpinner from '../components/base/TMSpinner';
8
8
  import ShowAlert from '../components/base/TMAlert';
9
9
  import { TMResultManager } from '../components/forms/TMResultDialog';
10
10
  import { useDataListItem } from './useDataListItem';
11
- import { buildColumnsFromDtd, canArchive, canUpdateMasterCallback, getDetailDtdAsync, toGridNumber } from '../components/features/documents/TMMultiMasterDetailDcmtsUtils';
11
+ import { buildColumnsFromDtd, canArchive, canUpdateMasterCallback, getDetailDtdAsync, toArchiveRowValue, toGridNumber } from '../components/features/documents/TMMultiMasterDetailDcmtsUtils';
12
12
  /** Fasi del flusso di archiviazione multipla dei dettagli */
13
13
  export var MultiDetailArchivePhase;
14
14
  (function (MultiDetailArchivePhase) {
@@ -65,6 +65,8 @@ export const useMultiMasterDetailDcmts = ({ dtd, masterDcmt, onClose }) => {
65
65
  const archiveRowsRef = useRef(archiveRows);
66
66
  archiveRowsRef.current = archiveRows;
67
67
  const rowIdRef = useRef(0);
68
+ // Copia di un dettaglio archiviato in corso: i clic successivi sull'icona vanno ignorati (vedi copyArchivedDetailToRows)
69
+ const isCopyingArchivedDetailRef = useRef(false);
68
70
  const [focusedRowKey, setFocusedRowKey] = useState(undefined);
69
71
  const [selectedRowKeys, setSelectedRowKeys] = useState([]);
70
72
  // Form di inserimento/modifica di una riga
@@ -213,11 +215,12 @@ export const useMultiMasterDetailDcmts = ({ dtd, masterDcmt, onClose }) => {
213
215
  setIsLoadingArchivedDetails(false);
214
216
  }
215
217
  };
216
- // I risultati seguono il tipo documento selezionato
218
+ // I risultati seguono il tipo documento selezionato e si caricano anche a sezione chiusa: il contatore
219
+ // nella testata e nella barra dei pannelli deve essere corretto senza dover aprire la sezione
217
220
  useEffect(() => {
218
221
  setArchivedDetailResults([]);
219
222
  areArchivedDetailsLoadedRef.current = false;
220
- if (selectedRelation?.detailTID !== undefined && sectionsOpen.archivedDetails)
223
+ if (selectedRelation?.detailTID !== undefined)
221
224
  refreshArchivedDetailsAsync();
222
225
  }, [selectedRelation?.detailTID]);
223
226
  // =============================================================================================
@@ -229,7 +232,7 @@ export const useMultiMasterDetailDcmts = ({ dtd, masterDcmt, onClose }) => {
229
232
  saveMultiDetailSectionsOpen(next);
230
233
  // Riaperte tutte le sezioni il collasso torna ai default: senza altezze salvate non c'è più layout
231
234
  setHasSavedLayout(hasMultiDetailLayout());
232
- // I dettagli già archiviati si caricano alla prima apertura della sezione
235
+ // I dettagli già archiviati sono caricati insieme al tipo di dettaglio: qui si riprova solo se quel caricamento è fallito
233
236
  if (section === 'archivedDetails' && isOpen && !areArchivedDetailsLoadedRef.current && !isLoadingArchivedDetails)
234
237
  refreshArchivedDetailsAsync();
235
238
  };
@@ -241,7 +244,7 @@ export const useMultiMasterDetailDcmts = ({ dtd, masterDcmt, onClose }) => {
241
244
  */
242
245
  const openAllSections = () => {
243
246
  setSectionsOpen(prev => Object.values(prev).every(Boolean) ? prev : { masterInfo: true, archivedDetails: true, archiveList: true });
244
- // I dettagli già archiviati si caricano come alla prima apertura della sezione
247
+ // I dettagli già archiviati sono caricati insieme al tipo di dettaglio: qui si riprova solo se quel caricamento è fallito
245
248
  if (!areArchivedDetailsLoadedRef.current && !isLoadingArchivedDetails)
246
249
  refreshArchivedDetailsAsync();
247
250
  };
@@ -251,8 +254,8 @@ export const useMultiMasterDetailDcmts = ({ dtd, masterDcmt, onClose }) => {
251
254
  */
252
255
  const clearLayout = () => {
253
256
  TMMessageBoxManager.show({
254
- title: 'Cancella layout',
255
- message: 'Vuoi cancellare il layout?',
257
+ title: SDKUI_Localizator.RemoveLayout,
258
+ message: SDKUI_Localizator.RemoveLayout_Confirm,
256
259
  buttons: [ButtonNames.YES, ButtonNames.NO],
257
260
  onButtonClick: (button) => {
258
261
  if (button !== ButtonNames.YES)
@@ -262,10 +265,10 @@ export const useMultiMasterDetailDcmts = ({ dtd, masterDcmt, onClose }) => {
262
265
  setSectionsOpen(restored);
263
266
  setLayoutResetToken(prev => prev + 1);
264
267
  setHasSavedLayout(false);
265
- // Le sezioni riaperte dal ripristino caricano i dettagli già archiviati come alla prima apertura
266
- if (restored.archivedDetails && !areArchivedDetailsLoadedRef.current && !isLoadingArchivedDetails)
268
+ // I dettagli già archiviati sono caricati insieme al tipo di dettaglio: qui si riprova solo se quel caricamento è fallito
269
+ if (!areArchivedDetailsLoadedRef.current && !isLoadingArchivedDetails)
267
270
  refreshArchivedDetailsAsync();
268
- ShowAlert({ message: 'Layout cancellato', mode: 'success', title: 'Cancella layout', duration: 3000 });
271
+ ShowAlert({ message: SDKUI_Localizator.LayoutRemoved, mode: 'success', title: SDKUI_Localizator.RemoveLayout, duration: 3000 });
269
272
  },
270
273
  });
271
274
  };
@@ -394,6 +397,128 @@ export const useMultiMasterDetailDcmts = ({ dtd, masterDcmt, onClose }) => {
394
397
  rowIdRef.current += 1;
395
398
  setArchiveRows(prev => [...prev, buildArchiveRow(rowIdRef.current, row.__tid, row.__values, row.__file)]);
396
399
  }, [buildArchiveRow]);
400
+ /**
401
+ * Copia un dettaglio già archiviato tra le righe da archiviare: i suoi metadati (e il file, se il
402
+ * tipo lo prevede e il documento ne ha uno) diventano una nuova riga del riepilogo. Il documento
403
+ * archiviato non è toccato: quello che si ottiene è un nuovo dettaglio, ancora da archiviare.
404
+ * I numeratori non si copiano, il loro valore lo assegna il sistema all'archiviazione.
405
+ * Ogni motivo che impedisce la copia, o che la rende parziale, arriva all'utente con un avviso:
406
+ * la riga entra nel riepilogo solo se i metadati del dettaglio sono stati letti davvero.
407
+ */
408
+ const copyArchivedDetailToRows = async (dcmt) => {
409
+ // Clic ripetuti sull'icona: una copia alla volta, altrimenti spinner e righe si sovrappongono
410
+ if (isCopyingArchivedDetailRef.current)
411
+ return;
412
+ try {
413
+ const detailTID = selectedRelation?.detailTID;
414
+ // Senza tipo selezionato il riepilogo non ha colonne in cui copiare il dettaglio
415
+ if (detailTID === undefined) {
416
+ ShowAlert({ message: SDKUI_Localizator.SelectDcmtTypeForDetailArchiving, mode: 'warning', title: SDKUI_Localizator.Duplicate, duration: 3000 });
417
+ return;
418
+ }
419
+ // La griglia dei risultati porta i valori di riga come stringhe: TID e DID vanno usati come numeri
420
+ const archivedDcmt = { TID: Number(dcmt?.TID), DID: Number(dcmt?.DID) };
421
+ if (!Number.isFinite(archivedDcmt.TID) || !Number.isFinite(archivedDcmt.DID) || archivedDcmt.TID <= 0 || archivedDcmt.DID <= 0) {
422
+ ShowAlert({ message: SDKUI_Localizator.InvalidDcmt, mode: 'warning', title: SDKUI_Localizator.Duplicate, duration: 3000 });
423
+ return;
424
+ }
425
+ // Le righe del riepilogo sono del tipo selezionato: un dettaglio di altro tipo non ne ha le colonne
426
+ if (archivedDcmt.TID !== detailTID) {
427
+ ShowAlert({ message: SDKUI_Localizator.DetailNotOfSelectedDcmtType, mode: 'warning', title: SDKUI_Localizator.Duplicate, duration: 3000 });
428
+ return;
429
+ }
430
+ isCopyingArchivedDetailRef.current = true;
431
+ TMSpinner.show({ description: SDKUI_Localizator.Loading });
432
+ const [detailDtd, metadataByMid] = await Promise.all([getDetailDtdAsync(detailTID), getDcmtMetadataByMid(archivedDcmt)]);
433
+ // Senza la struttura del tipo non si sa quali metadati copiare: la riga nascerebbe vuota
434
+ if (!detailDtd) {
435
+ ShowAlert({ message: SDKUI_Localizator.DcmtTypeStructureNotRetrieved, mode: 'error', title: SDKUI_Localizator.Duplicate, duration: 5000 });
436
+ return;
437
+ }
438
+ // Il permesso serve comunque all'archiviazione: dirlo qui evita di compilare righe inutili
439
+ if (!canArchive(detailDtd)) {
440
+ ShowAlert({ message: SDKUI_Localizator.YouDoNotHavePermissionsToArchiveDetailDocumentsOfThisType, mode: 'warning', title: SDKUI_Localizator.Duplicate, duration: 5000 });
441
+ return;
442
+ }
443
+ // Metadati non letti (sessione non valida, documento non più visibile): una riga di valori vuoti non serve
444
+ if (metadataByMid.size === 0) {
445
+ ShowAlert({ message: SDKUI_Localizator.DetailMetadataNotRetrieved, mode: 'error', title: SDKUI_Localizator.Duplicate, duration: 5000 });
446
+ return;
447
+ }
448
+ const userMetadata = getUserMetadataSorted(detailDtd);
449
+ const archivableMetadata = userMetadata.filter(md => md.dataDomain !== MetadataDataDomains.Numerator && handleArchiveVisibility(md));
450
+ // Niente da copiare: il tipo non ha metadati utente, o nessuno di questi è archiviabile
451
+ if (archivableMetadata.length === 0) {
452
+ const message = userMetadata.length > 0
453
+ ? SDKUI_Localizator.YouDoNotHavePermissionsToArchiveDetailDocumentsOfThisType
454
+ : SDKUI_Localizator.DetailMetadataNotRetrieved;
455
+ ShowAlert({ message, mode: 'warning', title: SDKUI_Localizator.Duplicate, duration: 5000 });
456
+ return;
457
+ }
458
+ // Gli stessi metadati che compila il form della riga: valori come stringhe, mancanti a vuoto
459
+ const values = archivableMetadata.map(md => {
460
+ const mvd = new MetadataValueDescriptorEx();
461
+ mvd.tid = detailTID;
462
+ mvd.mid = md.id;
463
+ mvd.md = md;
464
+ // Date e numeri nel formato del form: la riga copiata si rilegge e si modifica come le altre
465
+ mvd.value = toArchiveRowValue(metadataByMid.get(md.id), md.dataType);
466
+ mvd.isRequired = md.isRequired?.toString();
467
+ return mvd;
468
+ });
469
+ // Il file segue la riga solo se il tipo lo prevede e il documento ne ha uno; se non si
470
+ // recupera la riga si inserisce comunque, il file si aggiunge dal form della riga
471
+ let file = undefined;
472
+ // Il file c'è se lo dice il metadato di sistema del documento: la colonna della griglia
473
+ // dei risultati è solo un ripiego, la ricerca potrebbe non averla tra i suoi valori
474
+ const fileExt = String(metadataByMid.get(SystemMIDsAsNumber.FileExt) ?? dcmt?.FILEEXT ?? '');
475
+ const hasFile = fileExt !== '';
476
+ const supportsFile = detailDtd.archiveConstraint !== ArchiveConstraints.OnlyMetadata;
477
+ if (hasFile && supportsFile) {
478
+ if (detailDtd.perm?.canRetrieveFile !== AccessLevels.Yes) {
479
+ ShowAlert({ message: `${SDKUI_Localizator.DetailFileNotRetrieved}: ${SDKUI_Localizator.YouDoNotHavePermissionsToRetrieveTheFile}`, mode: 'warning', title: SDKUI_Localizator.Duplicate, duration: 5000 });
480
+ }
481
+ else {
482
+ try {
483
+ const rfo = new RetrieveFileOptions();
484
+ rfo.retrieveReason = DcmtOpers.None;
485
+ rfo.generalRetrieveFormat = GeneralRetrieveFormats.OriginalUnsigned;
486
+ file = await SDK_Globals.tmSession?.NewSearchEngine().RetrieveFileAsync(archivedDcmt.TID, archivedDcmt.DID, rfo);
487
+ // Nessun errore ma nemmeno il file (sessione non valida): l'utente deve saperlo
488
+ if (!file)
489
+ ShowAlert({ message: SDKUI_Localizator.DetailFileNotRetrieved, mode: 'warning', title: SDKUI_Localizator.Duplicate, duration: 5000 });
490
+ }
491
+ catch (error) {
492
+ ShowAlert({ message: `${SDKUI_Localizator.DetailFileNotRetrieved}: ${getExceptionMessage(error)}`, mode: 'warning', title: SDKUI_Localizator.Duplicate, duration: 5000 });
493
+ }
494
+ }
495
+ }
496
+ rowIdRef.current += 1;
497
+ const newRowId = rowIdRef.current;
498
+ setArchiveRows(prev => [...prev, buildArchiveRow(newRowId, detailTID, values, file)]);
499
+ // La riga copiata è quella su cui si lavora: fuoco e selezione la seguono
500
+ setFocusedRowKey(newRowId);
501
+ setSelectedRowKeys([newRowId]);
502
+ // Con la sezione chiusa la riga appena copiata non si vedrebbe
503
+ if (!sectionsOpen.archiveList)
504
+ setSectionOpen('archiveList', true);
505
+ ShowAlert({ message: SDKUI_Localizator.DetailCopiedToArchiveList, mode: 'success', title: SDKUI_Localizator.Duplicate, duration: 3000 });
506
+ // Il tipo pretende il file: senza, la riga non passerebbe l'archiviazione
507
+ if (!file && detailDtd.archiveConstraint === ArchiveConstraints.ContentCompulsory) {
508
+ ShowAlert({ message: SDKUI_Localizator.DetailFileToAddFromRowForm, mode: 'warning', title: SDKUI_Localizator.Duplicate, duration: 5000 });
509
+ }
510
+ }
511
+ catch (error) {
512
+ TMExceptionBoxManager.show({ exception: error });
513
+ }
514
+ finally {
515
+ // Spinner e blocco partono insieme: se la copia non è nemmeno iniziata non c'è niente da chiudere
516
+ if (isCopyingArchivedDetailRef.current) {
517
+ isCopyingArchivedDetailRef.current = false;
518
+ TMSpinner.hide();
519
+ }
520
+ }
521
+ };
397
522
  // All'apertura del form pre-compila i MID di collegamento valorizzati dal master
398
523
  useEffect(() => {
399
524
  if (!isOpenAddForm || !selectedRelation) {
@@ -493,8 +618,7 @@ export const useMultiMasterDetailDcmts = ({ dtd, masterDcmt, onClose }) => {
493
618
  setArchiveRows(prev => prev.filter(r => !archivedRowIds.has(r.id)));
494
619
  setSelectedRowKeys([]);
495
620
  setFocusedRowKey(undefined);
496
- if (areArchivedDetailsLoadedRef.current)
497
- refreshArchivedDetailsAsync();
621
+ refreshArchivedDetailsAsync();
498
622
  }
499
623
  const successCount = result.filter(o => o.resultType === ResultTypes.SUCCESS).length;
500
624
  const successMsg = `${successCount} ${successCount === 1 ? 'documento di dettaglio archiviato' : 'documenti di dettaglio archiviati'} con successo`;
@@ -518,14 +642,17 @@ export const useMultiMasterDetailDcmts = ({ dtd, masterDcmt, onClose }) => {
518
642
  const ae = new ArchiveEngineByID(SDK_Globals.tmSession);
519
643
  ae.TID = detailTID;
520
644
  ae.Metadata_ClearAll();
521
- // Valori del form + associazioni con il master (le associazioni prevalgono)
645
+ // Valori del form + associazioni con il master (le associazioni prevalgono). I metadati di
646
+ // sistema (TID, DID, FILEEXT...) non si archiviano: li assegna il sistema e mandarli fa fallire
647
+ // l'archiviazione. Il form li esclude già (vedi useArchiveListForm), qui è la rete di sicurezza
648
+ // per le righe che arrivano da altre strade, come la copia di un dettaglio già archiviato
522
649
  const valueByMid = new Map();
523
650
  for (const v of row.__values) {
524
- if (v.mid !== undefined && v.value)
651
+ if (v.mid !== undefined && v.mid > 99 && v.value)
525
652
  valueByMid.set(v.mid, v.value);
526
653
  }
527
654
  for (const a of associationMids) {
528
- if (a.value)
655
+ if (a.mid > 99 && a.value)
529
656
  valueByMid.set(a.mid, a.value);
530
657
  }
531
658
  valueByMid.forEach((value, mid) => {
@@ -579,7 +706,7 @@ export const useMultiMasterDetailDcmts = ({ dtd, masterDcmt, onClose }) => {
579
706
  detailColumns, detailSupportsFile, archiveRows, currentRows,
580
707
  focusedRowKey, setFocusedRowKey, selectedRowKeys, setSelectedRowKeys,
581
708
  isOpenAddForm, editingRowId, editingRow, formInputMids,
582
- openAddForm, openEditForm, closeAddForm, deleteRow, duplicateRow, upsertRow, updateRowValues,
709
+ openAddForm, openEditForm, closeAddForm, deleteRow, duplicateRow, upsertRow, updateRowValues, copyArchivedDetailToRows,
583
710
  // Archiviazione
584
711
  stopOnFirstError, setStopOnFirstError, isArchiving, archiveAllAsync,
585
712
  waitPanel, abortController: abortControllerRef.current,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@topconsultnpm/sdkui-react",
3
- "version": "6.22.0-dev2.37",
3
+ "version": "6.22.0-dev2.39",
4
4
  "description": "",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1",