@topconsultnpm/sdkui-react 6.22.0-dev2.20 → 6.22.0-dev2.21
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.
- package/lib/components/features/documents/TMMultiMasterDetailDcmtsForm.js +35 -14
- package/lib/components/features/documents/TMMultiMasterDetailDcmtsUtils.d.ts +2 -0
- package/lib/components/features/documents/TMMultiMasterDetailDcmtsUtils.js +22 -9
- package/lib/helper/SDKUI_Localizator.d.ts +1 -0
- package/lib/helper/SDKUI_Localizator.js +10 -0
- package/lib/hooks/useMultiMasterDetailDcmts.d.ts +1 -0
- package/lib/hooks/useMultiMasterDetailDcmts.js +13 -1
- package/package.json +1 -1
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { useCallback, useMemo, useState } from "react";
|
|
2
|
+
import { useCallback, useEffect, useMemo, 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, IconMenuVertical, IconRefresh, IconSearch, IconSearchCheck, saveMultiDetailPanelStates, SDKUI_Localizator } from "../../../helper";
|
|
6
|
+
import { getFullFileExtension, getMultiDetailPanelStates, hasMultiDetailPanelStates, hoverStyle, IconArchive, IconDelete, IconDuplicate, IconEdit, IconEraser, IconInfo, IconMenuVertical, IconRefresh, IconSearch, IconSearchCheck, saveMultiDetailPanelStates, SDKUI_Localizator } from "../../../helper";
|
|
7
7
|
import { TMColors } from "../../../utils/theme";
|
|
8
|
+
import { DeviceType, useDeviceType } from "../../base/TMDeviceProvider";
|
|
8
9
|
import { TMLayoutWaitingContainer } from "../../base/TMWaitPanel";
|
|
9
10
|
import TMModal from "../../base/TMModal";
|
|
10
11
|
import TMDataGrid from "../../base/TMDataGrid";
|
|
@@ -41,9 +42,20 @@ const styles = {
|
|
|
41
42
|
archiveButton: { display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: '8px', height: '38px', padding: '0 22px', border: 'none', borderRadius: '10px', fontSize: '14px', fontWeight: 600, letterSpacing: '0.2px', color: '#ffffff', cursor: 'pointer', transition: 'transform 0.15s ease, box-shadow 0.15s ease, background 0.15s ease' },
|
|
42
43
|
checkbox: { width: '16px', height: '16px', accentColor: TMColors.primary, cursor: 'pointer', margin: 0 },
|
|
43
44
|
};
|
|
45
|
+
/** Sezione mostrata per prima su mobile, dove i pannelli si vedono uno alla volta */
|
|
46
|
+
const MOBILE_SECTION = 'archiveList';
|
|
44
47
|
const TMMultiMasterDetailDcmtsForm = (props) => {
|
|
45
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;
|
|
46
|
-
const { isLoading, isSummaryReady, phase, setPhase, detailRelationItems, selectedRelation, selectedItem, hasMultipleTypes, selectRelation, backToTypeSelection, isBackLocked, closeModal, sectionsOpen, setSectionOpen, clearLayout, layoutResetToken, 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, 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 });
|
|
50
|
+
const deviceType = useDeviceType();
|
|
51
|
+
// Su mobile il panel manager mostra un pannello alla volta: le sezioni si raggiungono dalla sua barra
|
|
52
|
+
const isMobile = useMemo(() => deviceType === DeviceType.MOBILE, [deviceType]);
|
|
53
|
+
// Un pannello alla volta non lascia spazio da cedere: le sezioni chiuse nella vista impilata si
|
|
54
|
+
// aprono qui, altrimenti il pannello a schermo mostrerebbe la sola testata
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
if (isMobile && isSummaryReady)
|
|
57
|
+
openAllSections();
|
|
58
|
+
}, [isMobile, isSummaryReady]);
|
|
47
59
|
// Ricerca nella griglia dei dettagli da archiviare, come in TMSearch (vedi useDocumentOperations)
|
|
48
60
|
const [showGridSearch, setShowGridSearch] = useState(false);
|
|
49
61
|
const toggleGridSearchItem = {
|
|
@@ -131,9 +143,10 @@ const TMMultiMasterDetailDcmtsForm = (props) => {
|
|
|
131
143
|
const optionsMenuItems = useMemo(() => [
|
|
132
144
|
{ name: 'Cancella layout', icon: _jsx(IconEraser, { fontSize: 18, color: TMColors.error }), onClick: clearLayout },
|
|
133
145
|
], [clearLayout]);
|
|
134
|
-
// Le opzioni riguardano i pannelli del riepilogo: fuori da quella fase non hanno effetto
|
|
146
|
+
// Le opzioni riguardano i pannelli del riepilogo: fuori da quella fase non hanno effetto, e su mobile
|
|
147
|
+
// non c'è un layout da cancellare (i pannelli si vedono uno alla volta, a tutta altezza)
|
|
135
148
|
const renderOptionsMenu = () => {
|
|
136
|
-
if (!isSummaryPhase)
|
|
149
|
+
if (!isSummaryPhase || isMobile)
|
|
137
150
|
return undefined;
|
|
138
151
|
return (_jsx(ContextMenu, { trigger: 'left', items: optionsMenuItems, children: _jsx("span", { style: styles.titleMenuIcon, title: SDKUI_Localizator.Options, "aria-label": SDKUI_Localizator.Options, children: _jsx(IconMenuVertical, { fontSize: 16, color: "white" }) }) }));
|
|
139
152
|
};
|
|
@@ -191,13 +204,13 @@ const TMMultiMasterDetailDcmtsForm = (props) => {
|
|
|
191
204
|
// ----- Pannelli del riepilogo, uno per sezione ----------------------------------------------
|
|
192
205
|
const renderSectionsInPanels = () => {
|
|
193
206
|
const definitions = [
|
|
194
|
-
{ section: 'masterInfo', name: `Master: ${getDtdDisplayName(dtd)}`, render: renderMasterInfo },
|
|
207
|
+
{ section: 'masterInfo', name: `Master: ${getDtdDisplayName(dtd)}`, icon: _jsx(IconInfo, { fontSize: 22 }), count: masterFieldsCount, render: renderMasterInfo },
|
|
195
208
|
...(selectedRelation?.detailTID !== undefined
|
|
196
|
-
? [{ section: 'archivedDetails', name: SDKUI_Localizator.ArchivedDetails, render: renderArchivedDetails }]
|
|
209
|
+
? [{ section: 'archivedDetails', name: SDKUI_Localizator.ArchivedDetails, icon: _jsx(IconSearchCheck, { fontSize: 22 }), count: archivedDetailsCount, render: renderArchivedDetails }]
|
|
197
210
|
: []),
|
|
198
|
-
{ section: 'archiveList', name: SDKUI_Localizator.DetailsToArchive, render: renderArchiveList },
|
|
211
|
+
{ section: 'archiveList', name: SDKUI_Localizator.DetailsToArchive, icon: _jsx(IconArchive, { fontSize: 22 }), count: currentRows.length, render: renderArchiveList },
|
|
199
212
|
];
|
|
200
|
-
const panels = definitions.map(definition => ({
|
|
213
|
+
const panels = definitions.map((definition, index) => ({
|
|
201
214
|
id: definition.section,
|
|
202
215
|
name: definition.name,
|
|
203
216
|
currentGroupDirection: 'vertical',
|
|
@@ -209,6 +222,8 @@ const TMMultiMasterDetailDcmtsForm = (props) => {
|
|
|
209
222
|
// La sezione collassata è alta quanto la sua testata: i separatori non la ingrandiscono
|
|
210
223
|
isCollapsed: !sectionsOpen[definition.section],
|
|
211
224
|
},
|
|
225
|
+
// Barra dei pannelli: su mobile è l'unico modo per passare da una sezione all'altra
|
|
226
|
+
toolbarOptions: { icon: definition.icon, visible: true, isActive: true, orderNumber: index + 1, count: definition.count, tooltip: definition.name },
|
|
212
227
|
}));
|
|
213
228
|
const sections = panels.map(panel => panel.id);
|
|
214
229
|
// Le altezze salvate sono il punto di partenza, ma è il collasso corrente a decidere le quote
|
|
@@ -220,13 +235,19 @@ const TMMultiMasterDetailDcmtsForm = (props) => {
|
|
|
220
235
|
return acc;
|
|
221
236
|
}, {})
|
|
222
237
|
: undefined;
|
|
223
|
-
|
|
224
|
-
|
|
238
|
+
// Con un pannello alla volta non ci sono quote da spartire: quello a schermo prende tutta l'altezza.
|
|
239
|
+
// Le quote proporzionali (e il layout salvato) valgono solo per i pannelli impilati
|
|
240
|
+
const fullHeights = sections.reduce((acc, section) => { acc[section] = { width: '100%', height: '100%' }; return acc; }, {});
|
|
241
|
+
const initialDimensions = isMobile ? fullHeights : sectionPanelHeights(sections, sectionsOpen, savedDimensions);
|
|
242
|
+
const initialVisibility = sections.reduce((acc, section) => { acc[section] = isMobile ? section === MOBILE_SECTION : true; return acc; }, {});
|
|
225
243
|
return (_jsx("div", { style: styles.panelsHost, children: _jsx("div", { style: styles.panelsHostFill, children: _jsxs(TMPanelManagerWithPersistenceProvider
|
|
226
|
-
// Le dimensioni sono lette solo al montaggio: dopo la cancellazione del layout
|
|
227
|
-
|
|
244
|
+
// Le dimensioni sono lette solo al montaggio: dopo la cancellazione del layout, e al
|
|
245
|
+
// passaggio fra impilato e un pannello alla volta, i pannelli vanno rimontati
|
|
246
|
+
, { panels: panels, initialVisibility: initialVisibility, defaultDimensions: initialDimensions, initialDimensions: initialDimensions, initialMobilePanelId: MOBILE_SECTION,
|
|
247
|
+
// Su mobile il layout è imposto dal dispositivo: salvarlo sovrascriverebbe quello dell'utente
|
|
248
|
+
isPersistenceEnabled: !isMobile, persistPanelStates: !isMobile ? saveMultiDetailPanelStates : undefined, children: [_jsx(TMSectionPanelsHeights, { sections: sections, sectionsOpen: sectionsOpen }), _jsx(TMPanelManagerContainer, { panels: panels, direction: 'vertical', showToolbar: isMobile, minPanelSizePx: SECTION_HEADER_HEIGHT_PX, gutterSizePx: isMobile ? undefined : PANEL_GUTTER_PX })] }, `${layoutResetToken}-${isMobile}`) }) }));
|
|
228
249
|
};
|
|
229
|
-
const renderArchiveSummary = () => (_jsxs("div", { style: styles.summaryContainer, children: [renderSectionsInPanels(), _jsxs("div", { style: styles.footer, children: [_jsxs("label", { style: styles.footerLabel, ...hoverStyle({ borderColor: TMColors.primary, color: TMColors.text_normal }, { borderColor: TMColors.card_background, color: TMColors.label_normal }), children: [_jsx("input", { type: "checkbox", checked: stopOnFirstError, onChange: (e) => setStopOnFirstError(e.target.checked), style: styles.checkbox }),
|
|
250
|
+
const renderArchiveSummary = () => (_jsxs("div", { style: styles.summaryContainer, children: [renderSectionsInPanels(), _jsxs("div", { style: styles.footer, children: [_jsxs("label", { style: styles.footerLabel, ...hoverStyle({ borderColor: TMColors.primary, color: TMColors.text_normal }, { borderColor: TMColors.card_background, color: TMColors.label_normal }), children: [_jsx("input", { type: "checkbox", checked: stopOnFirstError, onChange: (e) => setStopOnFirstError(e.target.checked), style: styles.checkbox }), SDKUI_Localizator.StopOnFirstError] }), _jsxs("button", { type: "button", onClick: archiveAllAsync, disabled: isArchiveDisabled, style: {
|
|
230
251
|
...styles.archiveButton,
|
|
231
252
|
background: isArchiveDisabled ? TMColors.disabled : TMColors.success,
|
|
232
253
|
boxShadow: isArchiveDisabled ? 'none' : `0 4px 14px ${TMColors.success}55`,
|
|
@@ -19,6 +19,8 @@ export type AccordionSection = 'masterInfo' | 'archivedDetails' | 'archiveList';
|
|
|
19
19
|
*/
|
|
20
20
|
export type SectionPanelState = {
|
|
21
21
|
isMaximized: boolean;
|
|
22
|
+
canMaximize: boolean;
|
|
23
|
+
canCollapse: boolean;
|
|
22
24
|
toggleMaximize: () => void;
|
|
23
25
|
};
|
|
24
26
|
/** Valore di un metadato letto da un documento: una cella del risultato di ricerca */
|
|
@@ -6,6 +6,7 @@ import { formatScalarValue, getUserMetadataSorted } from '../../../helper/dcmtsH
|
|
|
6
6
|
import { TMColors } from '../../../utils/theme';
|
|
7
7
|
import { TMDcmtTypeIcon } from '../../viewers/TMTidViewer';
|
|
8
8
|
import TMTooltip from '../../base/TMTooltip';
|
|
9
|
+
import { DeviceType, useDeviceType } from '../../base/TMDeviceProvider';
|
|
9
10
|
import { useTMPanelManagerContext } from '../../layout/panelManager/TMPanelManagerContext';
|
|
10
11
|
// l'elenco da archiviare è l'informazione più importante, quindi prende la fetta maggiore
|
|
11
12
|
export const SECTION_WEIGHTS = { masterInfo: 25, archivedDetails: 30, archiveList: 45 };
|
|
@@ -164,16 +165,19 @@ export const renderSelectDcmtType = ({ detailRelationItems, selectedRelation, on
|
|
|
164
165
|
}) })] }));
|
|
165
166
|
export const renderAccordionHeader = ({ section, isOpen, tooltip, count, title, actions, panel, onToggleOpen }) => {
|
|
166
167
|
const restBackground = isOpen ? TMColors.toolbar_background : 'transparent';
|
|
167
|
-
// Un pannello ingrandito non si collassa: resterebbe la sola testata al posto di tutto il riepilogo
|
|
168
|
-
|
|
168
|
+
// Un pannello ingrandito non si collassa: resterebbe la sola testata al posto di tutto il riepilogo.
|
|
169
|
+
// Dove i pannelli si vedono uno alla volta (mobile) il collasso è precluso per lo stesso motivo
|
|
170
|
+
const canToggle = panel.canCollapse && !panel.isMaximized;
|
|
171
|
+
// Con un pannello alla volta l'ingrandimento non ha nulla da nascondere
|
|
172
|
+
const canMaximize = panel.canMaximize;
|
|
169
173
|
const onToggle = () => onToggleOpen(section, !isOpen);
|
|
170
174
|
const expandTooltip = panel.isMaximized ? SDKUI_Localizator.Minimize : SDKUI_Localizator.ZoomIn;
|
|
171
175
|
return (_jsxs("div", { role: canToggle ? 'button' : undefined, tabIndex: canToggle ? 0 : undefined, "aria-expanded": canToggle ? isOpen : undefined, style: { ...accordionStyles.accordionHeader, background: restBackground, ...(canToggle ? {} : { cursor: 'default' }) }, onClick: canToggle ? onToggle : undefined,
|
|
172
176
|
// Doppio click sulla testata: ingrandisce e riduce, come nei pannelli del prodotto
|
|
173
|
-
onDoubleClick: () => panel.toggleMaximize(), onKeyDown: canToggle ? (e) => { if (e.key === 'Enter' || e.key === ' ') {
|
|
177
|
+
onDoubleClick: canMaximize ? () => panel.toggleMaximize() : undefined, onKeyDown: canToggle ? (e) => { if (e.key === 'Enter' || e.key === ' ') {
|
|
174
178
|
e.preventDefault();
|
|
175
179
|
onToggle();
|
|
176
|
-
} } : undefined, title: canToggle ? tooltip : undefined, ...(canToggle ? hoverStyle({ background: TMColors.primary_container }, { background: restBackground }) : {}), children: [_jsxs("span", { style: accordionStyles.accordionTitle, children: [title, _jsx("span", { style: accordionStyles.accordionBadge, children: count })] }), actions, _jsx("button", { type: 'button', onClick: (e) => { e.stopPropagation(); panel.toggleMaximize(); }, style: accordionStyles.accordionExpandBtn, title: expandTooltip, "aria-label": expandTooltip, ...hoverStyle({ background: TMColors.primary_container, color: TMColors.primary }, { background: 'transparent', color: TMColors.label_normal }), children: panel.isMaximized ? _jsx(IconWindowMinimize, { fontSize: 14 }) : _jsx(IconWindowMaximize, { fontSize: 14 }) }), canToggle && (_jsx(IconChevronRight, { fontSize: 16, color: TMColors.label_normal, style: { transform: isOpen ? 'rotate(90deg)' : 'none', transition: 'transform 0.2s ease', flexShrink: 0 } }))] }));
|
|
180
|
+
} } : undefined, title: canToggle ? tooltip : undefined, ...(canToggle ? hoverStyle({ background: TMColors.primary_container }, { background: restBackground }) : {}), children: [_jsxs("span", { style: accordionStyles.accordionTitle, children: [title, _jsx("span", { style: accordionStyles.accordionBadge, children: count })] }), actions, canMaximize && (_jsx("button", { type: 'button', onClick: (e) => { e.stopPropagation(); panel.toggleMaximize(); }, style: accordionStyles.accordionExpandBtn, title: expandTooltip, "aria-label": expandTooltip, ...hoverStyle({ background: TMColors.primary_container, color: TMColors.primary }, { background: 'transparent', color: TMColors.label_normal }), children: panel.isMaximized ? _jsx(IconWindowMinimize, { fontSize: 14 }) : _jsx(IconWindowMaximize, { fontSize: 14 }) })), canToggle && (_jsx(IconChevronRight, { fontSize: 16, color: TMColors.label_normal, style: { transform: isOpen ? 'rotate(90deg)' : 'none', transition: 'transform 0.2s ease', flexShrink: 0 } }))] }));
|
|
177
181
|
};
|
|
178
182
|
/**
|
|
179
183
|
* Contenuto di un pannello: il contesto del panel manager è leggibile solo dentro l'albero del
|
|
@@ -182,6 +186,10 @@ export const renderAccordionHeader = ({ section, isOpen, tooltip, count, title,
|
|
|
182
186
|
export const TMSummarySectionSlot = ({ section, render, onBeforeMaximize }) => {
|
|
183
187
|
const { maximizedPanels, toggleMaximize } = useTMPanelManagerContext();
|
|
184
188
|
const isMaximized = maximizedPanels.includes(section);
|
|
189
|
+
// Su mobile il panel manager mostra un pannello alla volta: la sezione è già a tutto schermo, e
|
|
190
|
+
// ingrandirla nasconderebbe la barra con cui si passa da una sezione all'altra; chiuderla lascerebbe
|
|
191
|
+
// a schermo la sola testata, perché non ci sono altre sezioni a cui cedere lo spazio
|
|
192
|
+
const isMobile = useDeviceType() === DeviceType.MOBILE;
|
|
185
193
|
// Una sezione collassata non ha corpo: ingrandirla mostrerebbe un pannello vuoto, senza
|
|
186
194
|
// nemmeno poterla riaprire (da ingrandita il collasso è bloccato), quindi la si apre prima
|
|
187
195
|
const toggleMaximizeSection = () => {
|
|
@@ -189,7 +197,7 @@ export const TMSummarySectionSlot = ({ section, render, onBeforeMaximize }) => {
|
|
|
189
197
|
onBeforeMaximize?.(section);
|
|
190
198
|
toggleMaximize(section);
|
|
191
199
|
};
|
|
192
|
-
return _jsx(_Fragment, { children: render({ isMaximized, toggleMaximize: toggleMaximizeSection }) });
|
|
200
|
+
return _jsx(_Fragment, { children: render({ isMaximized, canMaximize: !isMobile, canCollapse: !isMobile, toggleMaximize: toggleMaximizeSection }) });
|
|
193
201
|
};
|
|
194
202
|
/**
|
|
195
203
|
* Tiene allineate le altezze dei pannelli al collasso delle sezioni: il panel manager non
|
|
@@ -197,7 +205,7 @@ export const TMSummarySectionSlot = ({ section, render, onBeforeMaximize }) => {
|
|
|
197
205
|
* Vive dentro il provider perché il contesto è leggibile solo lì
|
|
198
206
|
*/
|
|
199
207
|
export const TMSectionPanelsHeights = ({ sections, sectionsOpen }) => {
|
|
200
|
-
const { panelDimensions, setPanelDimensionsById, maximizedPanels } = useTMPanelManagerContext();
|
|
208
|
+
const { panelDimensions, panelVisibility, setPanelDimensionsById, maximizedPanels } = useTMPanelManagerContext();
|
|
201
209
|
// Con un pannello ingrandito le quote sono quelle dell'ingrandimento (100% e 0%): riallinearle al
|
|
202
210
|
// collasso lo svuoterebbe, quindi si aspetta il ritorno alla vista normale per rifare i conti
|
|
203
211
|
const hasMaximizedPanel = maximizedPanels.length > 0;
|
|
@@ -214,8 +222,13 @@ export const TMSectionPanelsHeights = ({ sections, sectionsOpen }) => {
|
|
|
214
222
|
}
|
|
215
223
|
if (hasMaximizedPanel)
|
|
216
224
|
return;
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
225
|
+
// Solo i pannelli a schermo si spartiscono l'altezza: contando anche i nascosti (su mobile ne
|
|
226
|
+
// resta visibile uno solo) al pannello a schermo toccherebbe la sua sola quota nominale,
|
|
227
|
+
// lasciando vuoto lo spazio dei pannelli che non ci sono. Chi è fuori schermo resta alle proprie
|
|
228
|
+
// quote, che sono quelle da cui il panel manager riparte quando lo si torna a mostrare
|
|
229
|
+
const visibleSections = sections.filter(section => panelVisibility[section] !== false);
|
|
230
|
+
const heights = sectionPanelHeights(visibleSections, sectionsOpen, dimensionsRef.current);
|
|
231
|
+
visibleSections.forEach(section => setPanelDimensionsById(section, heights[section].width, heights[section].height));
|
|
232
|
+
}, [sectionsOpen, hasMaximizedPanel, panelVisibility]);
|
|
220
233
|
return null;
|
|
221
234
|
};
|
|
@@ -820,6 +820,7 @@ export declare class SDKUI_Localizator {
|
|
|
820
820
|
static get Statistics(): "Statistiken" | "Statistics" | "Estadística" | "Statistiques" | "Estatísticas" | "Statistiche";
|
|
821
821
|
static get Status(): "Status" | "Estado" | "Statut" | "Stato";
|
|
822
822
|
static get StatusAndAnswer(): "Status und Antwort" | "Status and answer" | "Estado y respuesta" | "Statut et réponse" | "Estado e resposta" | "Stato e risposta";
|
|
823
|
+
static get StopOnFirstError(): "Beim ersten Fehler abbrechen" | "Stop at the first error" | "Detener en el primer error" | "Arrêter à la première erreur" | "Parar no primeiro erro" | "Interrompi al primo errore";
|
|
823
824
|
static get Subject(): "Betreff" | "Subject" | "Asunto" | "Objet" | "Assunto" | "Oggetto";
|
|
824
825
|
static get Summary(): "Zusammenfassung" | "Summary" | "Resumen" | "Résumé" | "Resumo" | "Riepilogo";
|
|
825
826
|
static get SwitchUser(): "Benutzer wechseln" | "Switch user" | "Cambiar usuario" | "Changer d'utilisateur" | "Mudar de usuário" | "Cambia utente";
|
|
@@ -8180,6 +8180,16 @@ export class SDKUI_Localizator {
|
|
|
8180
8180
|
default: return "Stato e risposta";
|
|
8181
8181
|
}
|
|
8182
8182
|
}
|
|
8183
|
+
static get StopOnFirstError() {
|
|
8184
|
+
switch (this._cultureID) {
|
|
8185
|
+
case CultureIDs.De_DE: return "Beim ersten Fehler abbrechen";
|
|
8186
|
+
case CultureIDs.En_US: return "Stop at the first error";
|
|
8187
|
+
case CultureIDs.Es_ES: return "Detener en el primer error";
|
|
8188
|
+
case CultureIDs.Fr_FR: return "Arrêter à la première erreur";
|
|
8189
|
+
case CultureIDs.Pt_PT: return "Parar no primeiro erro";
|
|
8190
|
+
default: return "Interrompi al primo errore";
|
|
8191
|
+
}
|
|
8192
|
+
}
|
|
8183
8193
|
static get Subject() {
|
|
8184
8194
|
switch (this._cultureID) {
|
|
8185
8195
|
case CultureIDs.De_DE: return "Betreff";
|
|
@@ -56,6 +56,7 @@ export declare const useMultiMasterDetailDcmts: ({ dtd, masterDcmt, onClose }: U
|
|
|
56
56
|
closeModal: () => void;
|
|
57
57
|
sectionsOpen: Record<AccordionSection, boolean>;
|
|
58
58
|
setSectionOpen: (section: AccordionSection, isOpen: boolean) => void;
|
|
59
|
+
openAllSections: () => void;
|
|
59
60
|
clearLayout: () => void;
|
|
60
61
|
layoutResetToken: number;
|
|
61
62
|
canUpdateMaster: boolean;
|
|
@@ -218,6 +218,18 @@ export const useMultiMasterDetailDcmts = ({ dtd, masterDcmt, onClose }) => {
|
|
|
218
218
|
if (section === 'archivedDetails' && isOpen && !areArchivedDetailsLoadedRef.current && !isLoadingArchivedDetails)
|
|
219
219
|
refreshArchivedDetailsAsync();
|
|
220
220
|
};
|
|
221
|
+
/**
|
|
222
|
+
* Apre tutte le sezioni senza salvare: serve dove il collasso non è una scelta dell'utente ma una
|
|
223
|
+
* conseguenza del layout (su mobile si vede un pannello alla volta, e una sezione chiusa lascerebbe
|
|
224
|
+
* a schermo la sola testata). Non si persiste per non sovrascrivere le sezioni che l'utente ha
|
|
225
|
+
* chiuso nella vista a pannelli impilati.
|
|
226
|
+
*/
|
|
227
|
+
const openAllSections = () => {
|
|
228
|
+
setSectionsOpen(prev => Object.values(prev).every(Boolean) ? prev : { masterInfo: true, archivedDetails: true, archiveList: true });
|
|
229
|
+
// I dettagli già archiviati si caricano come alla prima apertura della sezione
|
|
230
|
+
if (!areArchivedDetailsLoadedRef.current && !isLoadingArchivedDetails)
|
|
231
|
+
refreshArchivedDetailsAsync();
|
|
232
|
+
};
|
|
221
233
|
/**
|
|
222
234
|
* Cancella il layout configurato dall'utente (altezze dei pannelli e sezioni collassate), previa
|
|
223
235
|
* conferma. Il token cambia per rimontare il panel manager, che legge le dimensioni solo all'avvio.
|
|
@@ -537,7 +549,7 @@ export const useMultiMasterDetailDcmts = ({ dtd, masterDcmt, onClose }) => {
|
|
|
537
549
|
detailRelationItems, selectedRelation, selectedItem, hasMultipleTypes,
|
|
538
550
|
selectRelation, backToTypeSelection, isBackLocked, closeModal,
|
|
539
551
|
// Sezioni
|
|
540
|
-
sectionsOpen, setSectionOpen, clearLayout, layoutResetToken,
|
|
552
|
+
sectionsOpen, setSectionOpen, openAllSections, clearLayout, layoutResetToken,
|
|
541
553
|
// Master
|
|
542
554
|
canUpdateMaster, masterFieldsCount, getMasterMetadataAsync, masterInfoReloadToken,
|
|
543
555
|
isOpenMasterForm, setIsOpenMasterForm, handleMasterSavedAsync,
|