@topconsultnpm/sdkui-react 6.22.0-dev2.21 → 6.22.0-dev2.23

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.
@@ -386,7 +386,7 @@ const TMDataGrid = React.forwardRef((props, ref) => {
386
386
  // Temporary: iOS-specific styles to prevent default long-press menu and text selection
387
387
  WebkitTouchCallout: 'none',
388
388
  WebkitUserSelect: 'none'
389
- }, children: [dataColumns.map((column, index) => (_jsx(Column, { ...column }, column.caption + index.toString()))), sorting && _jsx(Sorting, { ...sorting }), selection && _jsx(Selection, { ...selection }), scrolling && _jsx(Scrolling, { ...scrolling }), summary && _jsx(Summary, { ...summary }), showHeaderFilter && _jsx(HeaderFilter, { visible: true, ...headerFilter }), rowDragging && _jsx(RowDragging, { ...rowDragging }), filterRow && _jsx(FilterRow, { ...filterRow }), showFilterPanel && _jsx(FilterPanel, { visible: true }), showHeaderColumnChooser && _jsxs(ColumnChooser, { height: "400px", enabled: !showHeaderColumnChooser, mode: "select", children: [_jsx(Position, { my: "center", at: "center", of: window }), _jsx(ColumnChooserSearch, { enabled: true }), _jsx(ColumnChooserSelection, { allowSelectAll: false, selectByClick: true, recursive: true })] }), stateStoring && _jsx(StateStoring, { ...stateStoring }), groupPanel && _jsx(GroupPanel, { ...groupPanel }), _jsx(Grouping, { contextMenuEnabled: true, ...grouping }), _jsx(LoadPanel, { enabled: showLoadPanel }), _jsx(SearchPanel, { visible: showSearchPanel, searchVisibleColumnsOnly: true, highlightSearchText: true }), editing && _jsx(Editing, { ...editing }), paging && _jsx(Paging, { ...paging }), effectivePager && _jsx(Pager, { ...effectivePager, visible: totalRecordCount > pageSize }), masterDetail && _jsx(MasterDetail, { ...masterDetail })] }) }), counterConfig.show && _jsx("div", { style: { width: "100%", height: "25px", display: "flex", alignItems: "center", gap: "15px", backgroundColor: "#e0e0e0" }, children: _jsx(TMCounterContainer, { items: counterValues, bgColorContainer: counterConfig.bgColorContainer, bgColorItem: counterConfig.bgColorItem, hoverColorItem: counterConfig.hoverColorItem, textColorItem: counterConfig.textColorItem }) }), customContextMenuItems && (_jsx(TMContextMenu, { target: ".dx-data-row", items: processCustomContextMenuItems(customContextMenuItems, customContextMenuRowKey), externalControl: {
389
+ }, children: [dataColumns.map((column, index) => (_jsx(Column, { ...column }, column.caption + index.toString()))), sorting && _jsx(Sorting, { ...sorting }), selection && _jsx(Selection, { ...selection }), scrolling && _jsx(Scrolling, { ...scrolling }), summary && _jsx(Summary, { ...summary }), showHeaderFilter && _jsx(HeaderFilter, { visible: true, ...headerFilter, search: { enabled: true, ...headerFilter?.search } }), rowDragging && _jsx(RowDragging, { ...rowDragging }), filterRow && _jsx(FilterRow, { ...filterRow }), showFilterPanel && _jsx(FilterPanel, { visible: true }), showHeaderColumnChooser && _jsxs(ColumnChooser, { height: "400px", enabled: !showHeaderColumnChooser, mode: "select", children: [_jsx(Position, { my: "center", at: "center", of: window }), _jsx(ColumnChooserSearch, { enabled: true }), _jsx(ColumnChooserSelection, { allowSelectAll: false, selectByClick: true, recursive: true })] }), stateStoring && _jsx(StateStoring, { ...stateStoring }), groupPanel && _jsx(GroupPanel, { ...groupPanel }), _jsx(Grouping, { contextMenuEnabled: true, ...grouping }), _jsx(LoadPanel, { enabled: showLoadPanel }), _jsx(SearchPanel, { visible: showSearchPanel, searchVisibleColumnsOnly: true, highlightSearchText: true }), editing && _jsx(Editing, { ...editing }), paging && _jsx(Paging, { ...paging }), effectivePager && _jsx(Pager, { ...effectivePager, visible: totalRecordCount > pageSize }), masterDetail && _jsx(MasterDetail, { ...masterDetail })] }) }), counterConfig.show && _jsx("div", { style: { width: "100%", height: "25px", display: "flex", alignItems: "center", gap: "15px", backgroundColor: "#e0e0e0" }, children: _jsx(TMCounterContainer, { items: counterValues, bgColorContainer: counterConfig.bgColorContainer, bgColorItem: counterConfig.bgColorItem, hoverColorItem: counterConfig.hoverColorItem, textColorItem: counterConfig.textColorItem }) }), customContextMenuItems && (_jsx(TMContextMenu, { target: ".dx-data-row", items: processCustomContextMenuItems(customContextMenuItems, customContextMenuRowKey), externalControl: {
390
390
  visible: customContextMenuVisible,
391
391
  position: customContextMenuPosition,
392
392
  onClose: () => setCustomContextMenuVisible(false)
@@ -13,11 +13,17 @@ const styles = {
13
13
  wrapper: { display: 'flex', flexDirection: 'column', flex: 1, minHeight: 0, borderTop: `1px solid ${TMColors.card_background}` },
14
14
  body: { flex: 1, minHeight: 0, padding: '13px 10px 9px', display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(185px, 1fr))', gridAutoRows: 'max-content', alignContent: 'start', columnGap: '10px', rowGap: '13px', overflow: 'auto' },
15
15
  item: { position: 'relative', display: 'flex', alignItems: 'center', minWidth: 0, minHeight: '30px', padding: '5px 10px 4px', borderRadius: '8px', border: `1px solid ${TMColors.card_background}`, background: TMColors.default_background, transition: 'border-color 0.15s ease, box-shadow 0.15s ease' },
16
- label: { position: 'absolute', top: '-7px', left: '8px', maxWidth: 'calc(100% - 16px)', padding: '0 5px', background: TMColors.default_background, fontSize: '9px', fontWeight: 700, lineHeight: '13px', letterSpacing: '0.5px', textTransform: 'uppercase', color: TMColors.label_normal, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' },
16
+ // Come per il pulsante copia, la posizione sta sull'ancora del tooltip; il troncamento con i
17
+ // puntini deve invece stare sull'elemento che contiene il testo
18
+ label: { position: 'absolute', top: '-7px', left: '8px', maxWidth: 'calc(100% - 16px)', minWidth: 0, padding: '0 5px', background: TMColors.default_background },
19
+ labelText: { maxWidth: '100%', fontSize: '9px', fontWeight: 700, lineHeight: '13px', letterSpacing: '0.5px', textTransform: 'uppercase', color: TMColors.label_normal, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' },
17
20
  value: { display: 'block', width: '100%', fontSize: '12.5px', fontWeight: 500, color: TMColors.text_normal, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', userSelect: 'text', cursor: 'text', minHeight: '17px', lineHeight: '17px' },
18
21
  emptyValue: { color: TMColors.disabled, fontWeight: 400 },
19
22
  // Pulsante di copia: pastiglia sul bordo superiore della card, come la caption
20
- copyBtn: { position: 'absolute', top: '-9px', right: '6px', display: 'flex', alignItems: 'center', justifyContent: 'center', width: '18px', height: '18px', padding: 0, borderRadius: '50%', border: `1px solid ${TMColors.card_background}`, background: TMColors.default_background, color: TMColors.label_normal, cursor: 'pointer', transition: 'opacity 0.15s ease, background 0.15s ease, color 0.15s ease, border-color 0.15s ease' },
23
+ // La posizione sta sull'ancora e non sul pulsante: è il contenitore del tooltip, che deve stare
24
+ // dove si vede il pulsante, altrimenti il tooltip si aprirebbe altrove
25
+ copyBtnAnchor: { position: 'absolute', top: '-9px', right: '6px', transition: 'opacity 0.15s ease' },
26
+ copyBtn: { display: 'flex', alignItems: 'center', justifyContent: 'center', width: '18px', height: '18px', padding: 0, borderRadius: '50%', border: `1px solid ${TMColors.card_background}`, background: TMColors.default_background, color: TMColors.label_normal, cursor: 'pointer', transition: 'background 0.15s ease, color 0.15s ease, border-color 0.15s ease' },
21
27
  placeholder: { gridColumn: '1 / -1', padding: '6px 2px', color: TMColors.label_normal, fontSize: '12px' },
22
28
  // Barra di ricerca
23
29
  searchBar: { display: 'flex', alignItems: 'center', gap: '8px', padding: '12px 14px 0', flexShrink: 0 },
@@ -58,7 +64,7 @@ const MetadataCard = ({ caption, copyText, children }) => {
58
64
  };
59
65
  // Il pulsante resta visibile durante il feedback e quando ha il focus (accessibilità da tastiera)
60
66
  const isCopyVisible = copyText !== '' && (!SUPPORTS_HOVER || isHovered || isCopied);
61
- return (_jsxs("div", { style: styles.item, onMouseEnter: (e) => { setIsHovered(true); Object.assign(e.currentTarget.style, { borderColor: TMColors.primary, boxShadow: '0 2px 8px rgba(37, 89, 165, 0.14)' }); }, onMouseLeave: (e) => { setIsHovered(false); Object.assign(e.currentTarget.style, { borderColor: TMColors.card_background, boxShadow: 'none' }); }, children: [_jsx("span", { style: { ...styles.label, maxWidth: isCopyVisible ? 'calc(100% - 38px)' : 'calc(100% - 16px)' }, title: caption, children: caption }), children, copyText !== '' && (_jsx("button", { type: 'button', style: { ...styles.copyBtn, ...(isCopied ? COPY_BTN_COPIED : COPY_BTN_IDLE), opacity: isCopyVisible ? 1 : 0, pointerEvents: isCopyVisible ? 'auto' : 'none' }, title: isCopied ? SDKUI_Localizator.CopiedSuccessfully : SDKUI_Localizator.CopyToClipboard, "aria-label": SDKUI_Localizator.CopyToClipboard, onClick: copyAsync, onFocus: () => setIsHovered(true), onBlur: () => setIsHovered(false), ...hoverStyle(isCopied ? COPY_BTN_COPIED : COPY_BTN_HOVER, isCopied ? COPY_BTN_COPIED : COPY_BTN_IDLE), children: isCopied ? _jsx(IconSuccess, { fontSize: 11 }) : _jsx(IconCopy, { fontSize: 11 }) }))] }));
67
+ return (_jsxs("div", { style: styles.item, onMouseEnter: (e) => { setIsHovered(true); Object.assign(e.currentTarget.style, { borderColor: TMColors.primary, boxShadow: '0 2px 8px rgba(37, 89, 165, 0.14)' }); }, onMouseLeave: (e) => { setIsHovered(false); Object.assign(e.currentTarget.style, { borderColor: TMColors.card_background, boxShadow: 'none' }); }, children: [_jsx(TMTooltip, { content: caption, parentStyle: { ...styles.label, maxWidth: isCopyVisible ? 'calc(100% - 38px)' : 'calc(100% - 16px)' }, childStyle: { maxWidth: '100%', minWidth: 0 }, children: _jsx("span", { style: styles.labelText, children: caption }) }), children, copyText !== '' && (_jsx(TMTooltip, { content: isCopied ? SDKUI_Localizator.CopiedSuccessfully : SDKUI_Localizator.CopyToClipboard, parentStyle: { ...styles.copyBtnAnchor, opacity: isCopyVisible ? 1 : 0, pointerEvents: isCopyVisible ? 'auto' : 'none' }, children: _jsx("button", { type: 'button', style: { ...styles.copyBtn, ...(isCopied ? COPY_BTN_COPIED : COPY_BTN_IDLE) }, "aria-label": SDKUI_Localizator.CopyToClipboard, onClick: copyAsync, onFocus: () => setIsHovered(true), onBlur: () => setIsHovered(false), ...hoverStyle(isCopied ? COPY_BTN_COPIED : COPY_BTN_HOVER, isCopied ? COPY_BTN_COPIED : COPY_BTN_IDLE), children: isCopied ? _jsx(IconSuccess, { fontSize: 11 }) : _jsx(IconCopy, { fontSize: 11 }) }) }))] }));
62
68
  };
63
69
  // Riepilogo dei metadati di un documento in card compatte
64
70
  const TMMasterInfoFields = ({ dtd, dcmt, getValuesByMidAsync, reloadToken, isExpandedView = false }) => {
@@ -157,7 +163,7 @@ const TMMasterInfoFields = ({ dtd, dcmt, getValuesByMidAsync, reloadToken, isExp
157
163
  e.preventDefault();
158
164
  e.stopPropagation();
159
165
  setFilter('');
160
- } }, onFocus: (e) => Object.assign(e.currentTarget.parentElement?.style ?? {}, { borderColor: TMColors.primary, boxShadow: '0 0 0 2px rgba(37, 89, 165, 0.14)' }), onBlur: (e) => Object.assign(e.currentTarget.parentElement?.style ?? {}, { borderColor: TMColors.card_background, boxShadow: 'none' }) }), filter !== '' && (_jsx("button", { type: 'button', style: styles.searchClear, title: SDKUI_Localizator.Clear, "aria-label": SDKUI_Localizator.Clear, onClick: () => setFilter(''), ...hoverStyle({ background: TMColors.primary_container, color: TMColors.primary }, { background: 'transparent', color: TMColors.label_normal }), children: _jsx(IconClearButton, { fontSize: 13 }) }))] }), _jsxs("span", { style: styles.searchCount, children: [filteredRows.length, "/", rows.length] })] }));
166
+ } }, onFocus: (e) => Object.assign(e.currentTarget.parentElement?.style ?? {}, { borderColor: TMColors.primary, boxShadow: '0 0 0 2px rgba(37, 89, 165, 0.14)' }), onBlur: (e) => Object.assign(e.currentTarget.parentElement?.style ?? {}, { borderColor: TMColors.card_background, boxShadow: 'none' }) }), filter !== '' && (_jsx(TMTooltip, { content: SDKUI_Localizator.Clear, parentStyle: { display: 'flex', flexShrink: 0 }, children: _jsx("button", { type: 'button', style: styles.searchClear, "aria-label": SDKUI_Localizator.Clear, onClick: () => setFilter(''), ...hoverStyle({ background: TMColors.primary_container, color: TMColors.primary }, { background: 'transparent', color: TMColors.label_normal }), children: _jsx(IconClearButton, { fontSize: 13 }) }) }))] }), _jsxs("span", { style: styles.searchCount, children: [filteredRows.length, "/", rows.length] })] }));
161
167
  // La ricerca sta solo nella vista ingrandita: in linea lo spazio è poco e l'elenco è limitato
162
168
  // all'anteprima, con il pulsante per aprirlo tutto
163
169
  const hasSearch = isExpandedView && rows.length > PREVIEW_COUNT;
@@ -104,7 +104,7 @@ const TMMultiMasterDetailDcmtsForm = (props) => {
104
104
  allowSorting: false, allowFiltering: false, allowHeaderFiltering: false, allowResizing: false,
105
105
  allowReordering: false, allowHiding: false, showInColumnChooser: false, allowEditing: false,
106
106
  cssClass: 'tm-row-actions',
107
- cellRender: (cell) => (_jsxs("div", { style: styles.actionsCell, children: [_jsx("span", { style: styles.actionIcon, title: SDKUI_Localizator.Update, onClick: () => openEditForm(cell.data), children: _jsx(IconEdit, { fontSize: 18, color: TMColors.primary }) }), _jsx("span", { style: styles.actionIcon, title: SDKUI_Localizator.Duplicate, onClick: () => duplicateRow(cell.data), children: _jsx(IconDuplicate, { fontSize: 18, color: TMColors.tertiary }) }), _jsx("span", { style: styles.actionIcon, title: SDKUI_Localizator.Deletion, onClick: () => deleteRow(cell.data), children: _jsx(IconDelete, { fontSize: 18, color: TMColors.error }) })] })),
107
+ 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 }) }) })] })),
108
108
  };
109
109
  return [actionsColumn, iconColumn, ...detailColumns, ...(detailSupportsFile ? [fileColumn] : [])];
110
110
  }, [detailColumns, detailSupportsFile]);
@@ -148,13 +148,13 @@ const TMMultiMasterDetailDcmtsForm = (props) => {
148
148
  const renderOptionsMenu = () => {
149
149
  if (!isSummaryPhase || isMobile)
150
150
  return undefined;
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" }) }) }));
151
+ return (_jsx(ContextMenu, { trigger: 'left', items: optionsMenuItems, children: _jsx(TMTooltip, { content: SDKUI_Localizator.Options, children: _jsx("span", { style: styles.titleMenuIcon, "aria-label": SDKUI_Localizator.Options, children: _jsx(IconMenuVertical, { fontSize: 16, color: "white" }) }) }) }));
152
152
  };
153
153
  // ----- Sezione: informazioni del master -----------------------------------------------------
154
154
  const renderMasterInfo = (panel) => {
155
155
  const isOpen = sectionsOpen.masterInfo;
156
156
  const editMasterButton = canUpdateMaster
157
- ? (_jsx("button", { type: 'button', onClick: (e) => { e.stopPropagation(); setIsOpenMasterForm(true); }, style: accordionStyles.accordionExpandBtn, title: `${SDKUI_Localizator.Update}: ${getDtdDisplayName(dtd)}`, ...hoverStyle({ background: TMColors.primary_container, color: TMColors.primary }, { background: 'transparent', color: TMColors.label_normal }), children: _jsx(IconEdit, { fontSize: 16 }) }))
157
+ ? (_jsx(TMTooltip, { content: `${SDKUI_Localizator.Update}: ${getDtdDisplayName(dtd)}`, children: _jsx("button", { type: 'button', onClick: (e) => { e.stopPropagation(); setIsOpenMasterForm(true); }, style: accordionStyles.accordionExpandBtn, ...hoverStyle({ background: TMColors.primary_container, color: TMColors.primary }, { background: 'transparent', color: TMColors.label_normal }), children: _jsx(IconEdit, { fontSize: 16 }) }) }))
158
158
  : undefined;
159
159
  return (_jsxs("div", { style: { ...accordionStyles.accordion, ...accordionStyles.accordionInPanel }, children: [renderAccordionHeader({
160
160
  section: 'masterInfo',
@@ -172,7 +172,7 @@ const TMMultiMasterDetailDcmtsForm = (props) => {
172
172
  const isOpen = sectionsOpen.archivedDetails;
173
173
  const hasResults = archivedDetailResults.length > 0 && archivedDetailsCount > 0;
174
174
  // Ricarica i dettagli già archiviati: la sezione va aperta, altrimenti l'esito non sarebbe visibile
175
- const refreshArchivedButton = (_jsx("button", { type: 'button', onClick: (e) => { e.stopPropagation(); setSectionOpen('archivedDetails', true); refreshArchivedDetailsAsync(); }, disabled: isLoadingArchivedDetails, style: { ...accordionStyles.accordionExpandBtn, ...(isLoadingArchivedDetails ? { cursor: 'default', opacity: 0.5 } : {}) }, title: SDKUI_Localizator.Refresh, "aria-label": SDKUI_Localizator.Refresh, ...(isLoadingArchivedDetails ? {} : hoverStyle({ background: TMColors.primary_container, color: TMColors.primary }, { background: 'transparent', color: TMColors.label_normal })), children: _jsx(IconRefresh, { fontSize: 16 }) }));
175
+ const refreshArchivedButton = (_jsx(TMTooltip, { content: SDKUI_Localizator.Refresh, children: _jsx("button", { type: 'button', onClick: (e) => { e.stopPropagation(); setSectionOpen('archivedDetails', true); refreshArchivedDetailsAsync(); }, disabled: isLoadingArchivedDetails, style: { ...accordionStyles.accordionExpandBtn, ...(isLoadingArchivedDetails ? { cursor: 'default', opacity: 0.5 } : {}) }, "aria-label": SDKUI_Localizator.Refresh, ...(isLoadingArchivedDetails ? {} : hoverStyle({ background: TMColors.primary_container, color: TMColors.primary }, { background: 'transparent', color: TMColors.label_normal })), children: _jsx(IconRefresh, { fontSize: 16 }) }) }));
176
176
  return (_jsxs("div", { style: { ...accordionStyles.accordion, ...accordionStyles.accordionInPanel }, children: [renderAccordionHeader({
177
177
  section: 'archivedDetails',
178
178
  isOpen,
@@ -46,13 +46,14 @@ export interface ArchiveRow {
46
46
  * stringhe, quindi il `value` generico di DevExtreme si restringe
47
47
  */
48
48
  export type ArchiveRowCell = Omit<DataGridTypes.ColumnCellTemplateData<ArchiveRow, number>, 'value'> & {
49
- readonly value?: string;
49
+ readonly value?: string | number;
50
50
  };
51
51
  /**
52
52
  * Modifiche di una riga in arrivo dalla griglia (mid -> nuovo valore): in modifica diretta ci sono
53
- * solo i metadati testuali (vedi isInlineEditableMetadata) e l'editor svuotato restituisce null
53
+ * i metadati testuali e numerici (vedi isInlineEditableMetadata), quindi stringhe e numeri,
54
+ * e l'editor svuotato restituisce null
54
55
  */
55
- export type ArchiveRowChanges = Record<string, string | null | undefined>;
56
+ export type ArchiveRowChanges = Record<string, string | number | null | undefined>;
56
57
  /** Aggiornamento di una riga della griglia, con le sole modifiche possibili in linea */
57
58
  export type ArchiveRowUpdatingEvent = Omit<DataGridTypes.RowUpdatingEvent<ArchiveRow, number>, 'newData'> & {
58
59
  newData: ArchiveRowChanges;
@@ -70,10 +71,15 @@ export declare const canArchive: (dtd?: DcmtTypeDescriptor) => boolean;
70
71
  */
71
72
  export declare const INLINE_EDITABLE_CELL_CLASS = "tm-cell-inline-editable";
72
73
  /**
73
- * Modifica diretta in griglia: solo il testo libero. Liste dati, calcolati, numeratori e speciali
74
- * hanno editor e vincoli propri, date e numeri una formattazione dedicata: restano al form
74
+ * Modifica diretta in griglia: testo libero e numeri. Liste dati, calcolati, numeratori e speciali
75
+ * hanno editor e vincoli propri, le date una formattazione dedicata: restano al form
75
76
  */
76
77
  export declare const isInlineEditableMetadata: (md: MetadataDescriptor) => boolean;
78
+ /**
79
+ * Valore di una cella numerica: l'editor della griglia vuole un numero, non la stringa che
80
+ * la riga porta in `__values` per l'archiviazione. Vuoto o non numerico: cella vuota
81
+ */
82
+ export declare const toGridNumber: (value: unknown) => number | undefined;
77
83
  /** Colonne della griglia di riepilogo dai metadati (utente) del tipo documento */
78
84
  export declare const buildColumnsFromDtd: (dtd: DcmtTypeDescriptor | undefined, renderDataListCell: (value: string | Date | number | undefined, dataListID: number, viewMode: DataListViewModes) => React.ReactElement) => Array<IColumnProps>;
79
85
  export declare const getDetailDtdAsync: (detailTID: number | undefined) => Promise<DcmtTypeDescriptor | undefined>;
@@ -205,7 +211,6 @@ export declare const multiDetailStyles: {
205
211
  lineHeight: number;
206
212
  };
207
213
  };
208
- /** Stili delle sezioni collassabili: qui perché la testata comune (renderAccordionHeader) vive in questo file */
209
214
  export declare const accordionStyles: {
210
215
  accordion: {
211
216
  display: "flex";
@@ -245,11 +250,6 @@ export declare const accordionStyles: {
245
250
  display: "inline-flex";
246
251
  alignItems: "center";
247
252
  justifyContent: "center";
248
- minWidth: string;
249
- height: string;
250
- padding: string;
251
- borderRadius: string;
252
- background: string;
253
253
  color: string;
254
254
  fontSize: string;
255
255
  fontWeight: number;
@@ -27,12 +27,22 @@ export const canArchive = (dtd) => dtd?.perm?.canArchive === AccessLevelsEx.Yes
27
27
  */
28
28
  export const INLINE_EDITABLE_CELL_CLASS = 'tm-cell-inline-editable';
29
29
  /**
30
- * Modifica diretta in griglia: solo il testo libero. Liste dati, calcolati, numeratori e speciali
31
- * hanno editor e vincoli propri, date e numeri una formattazione dedicata: restano al form
30
+ * Modifica diretta in griglia: testo libero e numeri. Liste dati, calcolati, numeratori e speciali
31
+ * hanno editor e vincoli propri, le date una formattazione dedicata: restano al form
32
32
  */
33
- export const isInlineEditableMetadata = (md) => md.dataType === MetadataDataTypes.Varchar
33
+ export const isInlineEditableMetadata = (md) => (md.dataType === MetadataDataTypes.Varchar || md.dataType === MetadataDataTypes.Number)
34
34
  && (md.dataDomain === undefined || md.dataDomain === MetadataDataDomains.None)
35
35
  && md.isSystem !== 1 && md.isSystemDerived !== 1;
36
+ /**
37
+ * Valore di una cella numerica: l'editor della griglia vuole un numero, non la stringa che
38
+ * la riga porta in `__values` per l'archiviazione. Vuoto o non numerico: cella vuota
39
+ */
40
+ export const toGridNumber = (value) => {
41
+ if (value === undefined || value === null || value === '')
42
+ return undefined;
43
+ const parsed = typeof value === 'number' ? value : Number(value);
44
+ return Number.isFinite(parsed) ? parsed : undefined;
45
+ };
36
46
  /** Colonne della griglia di riepilogo dai metadati (utente) del tipo documento */
37
47
  export const buildColumnsFromDtd = (dtd, renderDataListCell) => getUserMetadataSorted(dtd).map(md => {
38
48
  const dataListID = md.dataListID ?? 0;
@@ -41,21 +51,25 @@ export const buildColumnsFromDtd = (dtd, renderDataListCell) => getUserMetadataS
41
51
  const dataType = md.dataType;
42
52
  const format = md.format?.format;
43
53
  const formatCulture = md.format?.formatCulture;
44
- // Date e numeri usano la formattazione scalare; le liste dati il render comune
45
- const needsScalarFormat = !isDataList && (dataType === MetadataDataTypes.DateTime || dataType === MetadataDataTypes.Number);
46
54
  const isInlineEditable = isInlineEditableMetadata(md);
55
+ const isNumber = dataType === MetadataDataTypes.Number;
56
+ // Date e numeri usano la formattazione scalare; le liste dati il render comune. La cella
57
+ // modificabile mostra sempre l'editor, quindi non ha un proprio render
58
+ const needsScalarFormat = !isDataList && !isInlineEditable && (dataType === MetadataDataTypes.DateTime || isNumber);
47
59
  return {
48
60
  dataField: String(md.id),
49
61
  caption: (SDK_Globals.useLocalizedName ? md.nameLoc : md.name) ?? md.name ?? '',
50
62
  // La colonna modificabile dichiara il tipo: senza valori DevExtreme non saprebbe che editor usare
51
63
  ...(isInlineEditable
52
64
  ? {
53
- dataType: 'string',
65
+ // I numeri arrivano in cella già come numeri (vedi buildArchiveRow): l'editor è la casella numerica
66
+ dataType: isNumber ? 'number' : 'string',
54
67
  allowEditing: true,
55
68
  showEditorAlways: true,
56
69
  cssClass: INLINE_EDITABLE_CELL_CLASS,
57
- // Lunghezza e obbligatorietà del metadato valgono anche in griglia
58
- editorOptions: md.length ? { maxLength: md.length } : undefined,
70
+ // Lunghezza e obbligatorietà del metadato valgono anche in griglia; sui numeri
71
+ // i limiti restano all'API, in cella basta che sia un numero
72
+ editorOptions: !isNumber && md.length ? { maxLength: md.length } : undefined,
59
73
  validationRules: String(md.isRequired) === '1' ? [{ type: 'required', message: SDKUI_Localizator.RequiredField }] : undefined,
60
74
  }
61
75
  : { allowEditing: false }),
@@ -114,15 +128,13 @@ export const multiDetailStyles = {
114
128
  typeCardIcon: { width: '34px', height: '34px', borderRadius: '9px', background: TMColors.primary_container, display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 },
115
129
  typeCardName: { flex: 1, fontSize: '14px', fontWeight: 500, color: TMColors.text_normal, whiteSpace: 'normal', wordBreak: 'break-word', lineHeight: 1.3 },
116
130
  };
117
- /** Stili delle sezioni collassabili: qui perché la testata comune (renderAccordionHeader) vive in questo file */
131
+ // Stili delle sezioni collassabili
118
132
  export const accordionStyles = {
119
133
  accordion: { display: 'flex', flexDirection: 'column', minHeight: 0, borderRadius: '8px', border: `1px solid ${TMColors.card_background}`, background: TMColors.default_background, overflow: 'hidden' },
120
- // Sezione dentro un pannello: l'altezza è quella del pannello, che la sezione riempie. Serve `flex`
121
- // e non `height: 100%`, perché il pannello chiuso ha altezza 0 e vive del suo ingombro minimo
122
134
  accordionInPanel: { flex: 1 },
123
135
  accordionHeader: { display: 'flex', alignItems: 'center', gap: '8px', padding: '4px 8px', cursor: 'pointer', userSelect: 'none', transition: 'background 0.15s ease', outline: 'none', flexShrink: 0, minHeight: '28px' },
124
136
  accordionTitle: { flex: 1, fontSize: '12px', fontWeight: 600, color: TMColors.text_normal, display: 'flex', alignItems: 'center', gap: '6px', minWidth: 0 },
125
- accordionBadge: { display: 'inline-flex', alignItems: 'center', justifyContent: 'center', minWidth: '18px', height: '16px', padding: '0 5px', borderRadius: '8px', background: TMColors.primary_container, color: TMColors.primary, fontSize: '10px', fontWeight: 700, flexShrink: 0 },
137
+ accordionBadge: { display: 'inline-flex', alignItems: 'center', justifyContent: 'center', color: TMColors.primary, fontSize: '13px', fontWeight: 700, flexShrink: 0 },
126
138
  accordionListBody: { position: 'relative', flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column', borderTop: `1px solid ${TMColors.card_background}` },
127
139
  accordionListPlaceholder: { display: 'flex', flex: 1, alignItems: 'center', justifyContent: 'center', gap: '10px', padding: '16px', color: TMColors.label_normal, fontSize: '13px' },
128
140
  accordionExpandBtn: { display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: '20px', height: '20px', padding: 0, borderRadius: '6px', border: 'none', background: 'transparent', color: TMColors.label_normal, cursor: 'pointer', flexShrink: 0, transition: 'background 0.15s ease, color 0.15s ease' },
@@ -172,12 +184,10 @@ export const renderAccordionHeader = ({ section, isOpen, tooltip, count, title,
172
184
  const canMaximize = panel.canMaximize;
173
185
  const onToggle = () => onToggleOpen(section, !isOpen);
174
186
  const expandTooltip = panel.isMaximized ? SDKUI_Localizator.Minimize : SDKUI_Localizator.ZoomIn;
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,
176
- // Doppio click sulla testata: ingrandisce e riduce, come nei pannelli del prodotto
177
- onDoubleClick: canMaximize ? () => panel.toggleMaximize() : undefined, onKeyDown: canToggle ? (e) => { if (e.key === 'Enter' || e.key === ' ') {
187
+ 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, onDoubleClick: canMaximize ? () => panel.toggleMaximize() : undefined, onKeyDown: canToggle ? (e) => { if (e.key === 'Enter' || e.key === ' ') {
178
188
  e.preventDefault();
179
189
  onToggle();
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 } }))] }));
190
+ } } : undefined, ...(canToggle ? hoverStyle({ background: TMColors.primary_container }, { background: restBackground }) : {}), children: [_jsx(TMTooltip, { content: canToggle ? tooltip : undefined, parentStyle: { flex: 1, minWidth: 0 }, childStyle: { maxWidth: '100%' }, children: _jsxs("span", { style: accordionStyles.accordionTitle, children: [title, _jsx("span", { style: accordionStyles.accordionBadge, children: count })] }) }), actions, canMaximize && (_jsx(TMTooltip, { content: expandTooltip, children: _jsx("button", { type: 'button', onClick: (e) => { e.stopPropagation(); panel.toggleMaximize(); }, style: accordionStyles.accordionExpandBtn, "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 } }))] }));
181
191
  };
182
192
  /**
183
193
  * Contenuto di un pannello: il contesto del panel manager è leggibile solo dentro l'albero del
@@ -222,10 +232,7 @@ export const TMSectionPanelsHeights = ({ sections, sectionsOpen }) => {
222
232
  }
223
233
  if (hasMaximizedPanel)
224
234
  return;
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
235
+ // Solo i pannelli a schermo si spartiscono l'altezza, altrimenti resterebbe vuoto lo spazio dei nascosti
229
236
  const visibleSections = sections.filter(section => panelVisibility[section] !== false);
230
237
  const heights = sectionPanelHeights(visibleSections, sectionsOpen, dimensionsRef.current);
231
238
  visibleSections.forEach(section => setPanelDimensionsById(section, heights[section].width, heights[section].height));
@@ -8,7 +8,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 } from '../components/features/documents/TMMultiMasterDetailDcmtsUtils';
11
+ import { buildColumnsFromDtd, canArchive, canUpdateMasterCallback, getDetailDtdAsync, 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) {
@@ -44,6 +44,8 @@ export const useMultiMasterDetailDcmts = ({ dtd, masterDcmt, onClose }) => {
44
44
  // Struttura del tipo documento selezionato
45
45
  const [detailColumns, setDetailColumns] = useState([]);
46
46
  const [detailSupportsFile, setDetailSupportsFile] = useState(false);
47
+ // Mid numerici del tipo di dettaglio: in griglia le loro celle portano il numero (vedi buildArchiveRow)
48
+ const numericMidsRef = useRef(new Set());
47
49
  // Dettagli già archiviati del tipo selezionato, collegati al master
48
50
  const [archivedDetailResults, setArchivedDetailResults] = useState([]);
49
51
  const [isLoadingArchivedDetails, setIsLoadingArchivedDetails] = useState(false);
@@ -146,6 +148,7 @@ export const useMultiMasterDetailDcmts = ({ dtd, masterDcmt, onClose }) => {
146
148
  if (!selectedRelation?.detailTID) {
147
149
  setDetailColumns([]);
148
150
  setDetailSupportsFile(false);
151
+ numericMidsRef.current = new Set();
149
152
  return;
150
153
  }
151
154
  try {
@@ -153,16 +156,21 @@ export const useMultiMasterDetailDcmts = ({ dtd, masterDcmt, onClose }) => {
153
156
  setDetailSupportsFile(detailDtd?.archiveConstraint !== ArchiveConstraints.OnlyMetadata);
154
157
  // Liste dati referenziate dai metadati, servono al render delle celle
155
158
  const dataListIDs = new Set();
159
+ const numericMids = new Set();
156
160
  (detailDtd?.metadata ?? []).forEach(md => {
157
161
  if (md.dataDomain === MetadataDataDomains.DataList && md.dataListID)
158
162
  dataListIDs.add(md.dataListID);
163
+ if (md.dataType === MetadataDataTypes.Number && md.id !== undefined)
164
+ numericMids.add(md.id);
159
165
  });
166
+ numericMidsRef.current = numericMids;
160
167
  await loadDataListsAsync(dataListIDs);
161
168
  setDetailColumns(buildColumnsFromDtd(detailDtd, renderDataListCell));
162
169
  }
163
170
  catch {
164
171
  setDetailColumns([]);
165
172
  setDetailSupportsFile(false);
173
+ numericMidsRef.current = new Set();
166
174
  }
167
175
  };
168
176
  loadDetailStructureAsync();
@@ -315,11 +323,13 @@ export const useMultiMasterDetailDcmts = ({ dtd, masterDcmt, onClose }) => {
315
323
  },
316
324
  });
317
325
  }, []);
326
+ // I valori del form sono stringhe: le celle numeriche vogliono il numero, richiesto dal loro editor
318
327
  const buildArchiveRow = useCallback((id, tid, values, file) => {
319
328
  const row = { id, __tid: tid, __values: values, __file: file };
320
329
  for (const v of values) {
321
- if (v.mid !== undefined)
322
- row[String(v.mid)] = v.value ?? '';
330
+ if (v.mid === undefined)
331
+ continue;
332
+ row[String(v.mid)] = numericMidsRef.current.has(v.mid) ? toGridNumber(v.value) : (v.value ?? '');
323
333
  }
324
334
  return row;
325
335
  }, []);
@@ -338,7 +348,7 @@ export const useMultiMasterDetailDcmts = ({ dtd, masterDcmt, onClose }) => {
338
348
  closeAddForm();
339
349
  };
340
350
  /**
341
- * Modifica diretta in griglia di uno o più metadati testuali (changes: mid -> valore).
351
+ * Modifica diretta in griglia di uno o più metadati testuali o numerici (changes: mid -> valore).
342
352
  * La riga e i suoi __values sono ricreati: il duplicato, che condivide __values per
343
353
  * riferimento, resta invariato
344
354
  */
@@ -346,9 +356,10 @@ export const useMultiMasterDetailDcmts = ({ dtd, masterDcmt, onClose }) => {
346
356
  const changedMids = Object.keys(changes);
347
357
  if (changedMids.length === 0)
348
358
  return;
349
- // La griglia restituisce null sull'editor svuotato: a sistema il metadato va vuoto, non nullo
359
+ // I __values vanno a sistema come stringhe, anche quelli che in griglia sono numeri. La griglia
360
+ // restituisce null sull'editor svuotato: a sistema il metadato va vuoto, non nullo
350
361
  const normalized = {};
351
- changedMids.forEach(mid => { normalized[mid] = changes[mid] ?? ''; });
362
+ changedMids.forEach(mid => { normalized[mid] = changes[mid] === null || changes[mid] === undefined ? '' : String(changes[mid]); });
352
363
  setArchiveRows(prev => prev.map(r => {
353
364
  if (r.id !== rowId)
354
365
  return r;
@@ -364,7 +375,8 @@ export const useMultiMasterDetailDcmts = ({ dtd, masterDcmt, onClose }) => {
364
375
  values.push(added);
365
376
  });
366
377
  const updated = { ...r, __values: values };
367
- changedMids.forEach(mid => { updated[mid] = normalized[mid]; });
378
+ // In cella il numero resta un numero, come lo vuole il suo editor
379
+ changedMids.forEach(mid => { updated[mid] = numericMidsRef.current.has(Number(mid)) ? toGridNumber(normalized[mid]) : normalized[mid]; });
368
380
  return updated;
369
381
  }));
370
382
  }, []);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@topconsultnpm/sdkui-react",
3
- "version": "6.22.0-dev2.21",
3
+ "version": "6.22.0-dev2.23",
4
4
  "description": "",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1",