@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.
@@ -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
- const initialDimensions = sectionPanelHeights(sections, sectionsOpen, savedDimensions);
224
- const initialVisibility = sections.reduce((acc, section) => { acc[section] = true; return acc; }, {});
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 i pannelli vanno rimontati
227
- , { panels: panels, initialVisibility: initialVisibility, defaultDimensions: initialDimensions, initialDimensions: initialDimensions, initialMobilePanelId: 'archiveList', isPersistenceEnabled: true, persistPanelStates: saveMultiDetailPanelStates, children: [_jsx(TMSectionPanelsHeights, { sections: sections, sectionsOpen: sectionsOpen }), _jsx(TMPanelManagerContainer, { panels: panels, direction: 'vertical', showToolbar: false, minPanelSizePx: SECTION_HEADER_HEIGHT_PX, gutterSizePx: PANEL_GUTTER_PX })] }, layoutResetToken) }) }));
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 }), "Ferma l'archiviazione al primo errore"] }), _jsxs("button", { type: "button", onClick: archiveAllAsync, disabled: isArchiveDisabled, style: {
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
- const canToggle = !panel.isMaximized;
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
- const heights = sectionPanelHeights(sections, sectionsOpen, dimensionsRef.current);
218
- sections.forEach(section => setPanelDimensionsById(section, heights[section].width, heights[section].height));
219
- }, [sectionsOpen, hasMaximizedPanel]);
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,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@topconsultnpm/sdkui-react",
3
- "version": "6.22.0-dev2.20",
3
+ "version": "6.22.0-dev2.21",
4
4
  "description": "",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1",