@topconsultnpm/sdkui-react 6.22.0-dev1.9 → 6.22.0-dev2.11
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/base/TMButton.d.ts +13 -0
- package/lib/components/base/TMButton.js +137 -5
- package/lib/components/base/TMDataGridExportForm.js +39 -30
- package/lib/components/base/TMModal.js +1 -1
- package/lib/components/choosers/TMDynDataListItemChooser.d.ts +2 -0
- package/lib/components/choosers/TMDynDataListItemChooser.js +4 -6
- package/lib/components/editors/TMDateBox.d.ts +16 -1
- package/lib/components/editors/TMDateBox.js +90 -22
- package/lib/components/editors/TMMetadataEditor.d.ts +5 -1
- package/lib/components/editors/TMMetadataEditor.js +27 -23
- package/lib/components/editors/TMTextArea.d.ts +1 -0
- package/lib/components/editors/TMTextArea.js +23 -15
- package/lib/components/editors/TMTextBox.d.ts +2 -0
- package/lib/components/editors/TMTextBox.js +38 -22
- package/lib/components/features/documents/TMDcmtForm.d.ts +2 -1
- package/lib/components/features/documents/TMDcmtForm.js +107 -8
- package/lib/components/features/documents/TMRelationViewer.js +13 -32
- package/lib/components/features/search/TMSearch.d.ts +2 -1
- package/lib/components/features/search/TMSearch.js +31 -18
- package/lib/components/features/search/TMSearchQueryPanel.d.ts +1 -1
- package/lib/components/features/search/TMSearchQueryPanel.js +28 -7
- package/lib/components/features/search/TMSearchResult.d.ts +2 -1
- package/lib/components/features/search/TMSearchResult.js +16 -41
- package/lib/components/features/search/TMSignatureInfoContent.d.ts +4 -2
- package/lib/components/features/search/TMSignatureInfoContent.js +373 -79
- package/lib/components/forms/Login/TMLoginForm.d.ts +2 -0
- package/lib/components/forms/Login/TMLoginForm.js +17 -3
- package/lib/components/forms/Login/TextBox.d.ts +3 -0
- package/lib/components/forms/Login/TextBox.js +2 -2
- package/lib/components/pages/TMPage.js +3 -1
- package/lib/components/query/TMQueryEditor.js +1 -1
- package/lib/components/viewers/TMMidViewer.js +1 -1
- package/lib/helper/Globalization.d.ts +1 -1
- package/lib/helper/SDKUI_Globals.js +22 -2
- package/lib/helper/SDKUI_Localizator.d.ts +11 -0
- package/lib/helper/SDKUI_Localizator.js +110 -0
- package/lib/helper/TMUtils.d.ts +29 -1
- package/lib/helper/TMUtils.js +249 -10
- package/lib/helper/grafometricSignaturesCache.d.ts +45 -0
- package/lib/helper/grafometricSignaturesCache.js +56 -0
- package/lib/helper/helpers.d.ts +11 -0
- package/lib/helper/helpers.js +32 -0
- package/lib/helper/index.d.ts +1 -0
- package/lib/helper/index.js +1 -0
- package/lib/hooks/useDocumentOperations.d.ts +2 -1
- package/lib/hooks/useDocumentOperations.js +15 -15
- package/lib/hooks/usePreventFileDrop.js +14 -3
- package/lib/ts/graphometricTypes.d.ts +62 -0
- package/lib/ts/graphometricTypes.js +1 -0
- package/lib/ts/index.d.ts +1 -0
- package/lib/ts/index.js +1 -0
- package/package.json +7 -2
|
@@ -3,6 +3,17 @@ import { ITMEditorBase } from './TMEditorBase';
|
|
|
3
3
|
export type ButtonColors = 'error' | 'warning' | 'success' | 'info' | 'primary' | 'secondary' | 'tertiary' | 'standard' | 'primaryOutline' | 'secondaryOutline' | 'tertiaryOutline' | 'errorOutline' | 'warningOutline' | 'successOutline' | 'infoOutline';
|
|
4
4
|
export type ButtonStyle = 'normal' | 'toolbar' | 'icon' | 'text' | 'advanced';
|
|
5
5
|
export type AdvancedButtonType = 'success' | 'error' | 'tertiary' | 'primary';
|
|
6
|
+
/** Azione secondaria mostrata nel dropdown "split button" (freccia a destra) */
|
|
7
|
+
export interface ITMButtonSecondaryAction {
|
|
8
|
+
/** Etichetta mostrata nella voce di menu */
|
|
9
|
+
name: string;
|
|
10
|
+
/** Icona opzionale a sinistra della voce */
|
|
11
|
+
icon?: React.ReactNode;
|
|
12
|
+
/** Callback al click della voce */
|
|
13
|
+
onClick?: () => void;
|
|
14
|
+
/** Disabilita la singola voce */
|
|
15
|
+
disabled?: boolean;
|
|
16
|
+
}
|
|
6
17
|
export interface ITMButton extends ITMEditorBase {
|
|
7
18
|
color?: ButtonColors;
|
|
8
19
|
btnStyle?: ButtonStyle;
|
|
@@ -17,6 +28,8 @@ export interface ITMButton extends ITMEditorBase {
|
|
|
17
28
|
showTooltip?: boolean;
|
|
18
29
|
onClick?: VoidFunction;
|
|
19
30
|
onMouseDown?: (e: any) => any;
|
|
31
|
+
/** Azioni secondarie: se valorizzate, il bottone mostra una freccia a destra che apre un dropdown con queste azioni */
|
|
32
|
+
secondaryActions?: ITMButtonSecondaryAction[];
|
|
20
33
|
}
|
|
21
34
|
declare const TMButton: React.ForwardRefExoticComponent<ITMButton & React.RefAttributes<HTMLButtonElement>>;
|
|
22
35
|
export default TMButton;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { useState, forwardRef } from 'react';
|
|
2
|
+
import { useState, forwardRef, useRef, useEffect } from 'react';
|
|
3
|
+
import { createPortal } from 'react-dom';
|
|
3
4
|
import styled from 'styled-components';
|
|
4
|
-
import { getColor } from '../../helper';
|
|
5
|
+
import { getColor, IconChevronDown } from '../../helper';
|
|
5
6
|
import { FontSize, TMColors } from '../../utils/theme';
|
|
6
7
|
import TMTooltip from './TMTooltip';
|
|
7
8
|
const StyledNormalButton = styled.button.withConfig({ shouldForwardProp: prop => !['text'].includes(prop) }) `
|
|
@@ -149,10 +150,132 @@ const StyledAdvancedButtonText = styled.div `
|
|
|
149
150
|
border-top-right-radius: 10px;
|
|
150
151
|
border-bottom-right-radius: 10px;
|
|
151
152
|
padding: 5px;
|
|
153
|
+
padding-right: ${props => props.$reserveArrow ? '26px' : '5px'};
|
|
154
|
+
`;
|
|
155
|
+
const StyledSplitContainer = styled.div `
|
|
156
|
+
display: inline-flex;
|
|
157
|
+
align-items: stretch;
|
|
158
|
+
position: relative;
|
|
159
|
+
`;
|
|
160
|
+
const StyledSplitArrow = styled.button `
|
|
161
|
+
position: absolute;
|
|
162
|
+
right: 4px;
|
|
163
|
+
top: 50%;
|
|
164
|
+
transform: translateY(-50%);
|
|
165
|
+
z-index: 2;
|
|
166
|
+
display: flex;
|
|
167
|
+
align-items: center;
|
|
168
|
+
justify-content: center;
|
|
169
|
+
width: 20px;
|
|
170
|
+
height: 20px;
|
|
171
|
+
padding: 0;
|
|
172
|
+
border: none;
|
|
173
|
+
border-radius: 6px;
|
|
174
|
+
cursor: ${props => props.$isDisabled ? 'default' : 'pointer'};
|
|
175
|
+
color: white;
|
|
176
|
+
background-color: rgba(255, 255, 255, 0.22);
|
|
177
|
+
transition: background-color ease 150ms;
|
|
178
|
+
&:hover { background-color: ${props => props.$isDisabled ? 'rgba(255, 255, 255, 0.22)' : 'rgba(255, 255, 255, 0.38)'}; }
|
|
179
|
+
& svg {
|
|
180
|
+
transition: transform ease 150ms;
|
|
181
|
+
transform: rotate(${props => props.$isOpen ? '180deg' : '0deg'});
|
|
182
|
+
}
|
|
183
|
+
`;
|
|
184
|
+
const StyledDropdown = styled.div `
|
|
185
|
+
position: fixed;
|
|
186
|
+
left: ${props => props.$left}px;
|
|
187
|
+
top: ${props => props.$top}px;
|
|
188
|
+
transform: translateY(-100%);
|
|
189
|
+
transform-origin: bottom left;
|
|
190
|
+
min-width: ${props => props.$minWidth}px;
|
|
191
|
+
background: #ffffff;
|
|
192
|
+
border-radius: 14px;
|
|
193
|
+
box-shadow: 0 10px 30px rgba(20, 40, 80, 0.20);
|
|
194
|
+
border: 1px solid rgba(74, 150, 210, 0.18);
|
|
195
|
+
padding: 5px;
|
|
196
|
+
z-index: 100000;
|
|
197
|
+
display: flex;
|
|
198
|
+
flex-direction: column;
|
|
199
|
+
gap: 2px;
|
|
200
|
+
animation: tmSplitDropdownIn cubic-bezier(0.2, 0.8, 0.2, 1) 150ms;
|
|
201
|
+
@keyframes tmSplitDropdownIn {
|
|
202
|
+
from { opacity: 0; transform: translateY(calc(-100% + 8px)); }
|
|
203
|
+
to { opacity: 1; transform: translateY(-100%); }
|
|
204
|
+
}
|
|
205
|
+
`;
|
|
206
|
+
const StyledDropdownItem = styled.button `
|
|
207
|
+
display: flex;
|
|
208
|
+
align-items: center;
|
|
209
|
+
gap: 9px;
|
|
210
|
+
width: 100%;
|
|
211
|
+
padding: 5px 9px;
|
|
212
|
+
border: none;
|
|
213
|
+
border-radius: 8px;
|
|
214
|
+
background: transparent;
|
|
215
|
+
text-align: left;
|
|
216
|
+
font-size: ${FontSize.defaultFontSize};
|
|
217
|
+
font-weight: 500;
|
|
218
|
+
color: ${props => props.$disabled ? '#b4b4b4' : '#2c3e50'};
|
|
219
|
+
cursor: ${props => props.$disabled ? 'default' : 'pointer'};
|
|
220
|
+
transition: background ease 150ms, color ease 150ms;
|
|
221
|
+
&:hover {
|
|
222
|
+
background: ${props => props.$disabled ? 'transparent' : 'linear-gradient(135deg, rgba(74, 150, 210, 0.16) 0%, rgba(37, 89, 165, 0.16) 100%)'};
|
|
223
|
+
color: ${props => props.$disabled ? '#b4b4b4' : '#2559A5'};
|
|
224
|
+
}
|
|
225
|
+
& > .tm-split-item-icon {
|
|
226
|
+
display: flex;
|
|
227
|
+
align-items: center;
|
|
228
|
+
justify-content: center;
|
|
229
|
+
width: 22px;
|
|
230
|
+
height: 22px;
|
|
231
|
+
flex-shrink: 0;
|
|
232
|
+
border-radius: 6px;
|
|
233
|
+
background: rgba(74, 150, 210, 0.14);
|
|
234
|
+
color: #4A96D2;
|
|
235
|
+
transition: background ease 150ms, color ease 150ms;
|
|
236
|
+
}
|
|
237
|
+
&:hover > .tm-split-item-icon {
|
|
238
|
+
background: ${props => props.$disabled ? 'rgba(74, 150, 210, 0.14)' : '#4A96D2'};
|
|
239
|
+
color: ${props => props.$disabled ? '#4A96D2' : '#ffffff'};
|
|
240
|
+
}
|
|
152
241
|
`;
|
|
153
242
|
const TMButton = forwardRef((props, ref) => {
|
|
154
|
-
const { width, height, keyGesture, btnStyle = 'normal', advancedColor, advancedType = 'primary', color = 'primary', fontSize = FontSize.defaultFontSize, disabled = false, showTooltip = true, caption, icon, description, padding, elementStyle, onClick = () => { }, onMouseDown = () => { } } = props;
|
|
243
|
+
const { width, height, keyGesture, btnStyle = 'normal', advancedColor, advancedType = 'primary', color = 'primary', fontSize = FontSize.defaultFontSize, disabled = false, showTooltip = true, caption, icon, description, padding, elementStyle, onClick = () => { }, onMouseDown = () => { }, secondaryActions } = props;
|
|
155
244
|
const [isHovered, setIsHovered] = useState(false);
|
|
245
|
+
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
|
|
246
|
+
const [dropdownPos, setDropdownPos] = useState({ left: 0, top: 0, minWidth: 200 });
|
|
247
|
+
const splitContainerRef = useRef(null);
|
|
248
|
+
const dropdownRef = useRef(null);
|
|
249
|
+
const hasSecondaryActions = !!secondaryActions && secondaryActions.length > 0;
|
|
250
|
+
useEffect(() => {
|
|
251
|
+
if (!isDropdownOpen)
|
|
252
|
+
return;
|
|
253
|
+
const handlePointerDown = (e) => {
|
|
254
|
+
const target = e.target;
|
|
255
|
+
if (splitContainerRef.current?.contains(target) || dropdownRef.current?.contains(target))
|
|
256
|
+
return;
|
|
257
|
+
setIsDropdownOpen(false);
|
|
258
|
+
};
|
|
259
|
+
const handleKeyDown = (e) => { if (e.key === 'Escape')
|
|
260
|
+
setIsDropdownOpen(false); };
|
|
261
|
+
document.addEventListener('mousedown', handlePointerDown);
|
|
262
|
+
document.addEventListener('touchstart', handlePointerDown);
|
|
263
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
264
|
+
return () => {
|
|
265
|
+
document.removeEventListener('mousedown', handlePointerDown);
|
|
266
|
+
document.removeEventListener('touchstart', handlePointerDown);
|
|
267
|
+
document.removeEventListener('keydown', handleKeyDown);
|
|
268
|
+
};
|
|
269
|
+
}, [isDropdownOpen]);
|
|
270
|
+
const toggleDropdown = () => {
|
|
271
|
+
if (disabled)
|
|
272
|
+
return;
|
|
273
|
+
if (!isDropdownOpen && splitContainerRef.current) {
|
|
274
|
+
const rect = splitContainerRef.current.getBoundingClientRect();
|
|
275
|
+
setDropdownPos({ left: rect.left, top: rect.top - 6, minWidth: Math.max(rect.width, 200) });
|
|
276
|
+
}
|
|
277
|
+
setIsDropdownOpen(prev => !prev);
|
|
278
|
+
};
|
|
156
279
|
const renderedButton = () => {
|
|
157
280
|
if (btnStyle === 'normal') {
|
|
158
281
|
return (_jsx(StyledNormalButton, { ref: ref, width: width, height: height, color: color, caption: caption, disabled: disabled, fontSize: fontSize, onClick: onClick, onMouseDown: (e) => !disabled && onMouseDown?.(e), children: caption }));
|
|
@@ -164,7 +287,7 @@ const TMButton = forwardRef((props, ref) => {
|
|
|
164
287
|
return (_jsx(StyledIconButton, { ref: ref, color: color, caption: caption, disabled: disabled, fontSize: fontSize, padding: padding, onClick: onClick, onMouseDown: (e) => !disabled && onMouseDown?.(e), children: icon }));
|
|
165
288
|
}
|
|
166
289
|
else if (btnStyle === 'advanced') {
|
|
167
|
-
return (_jsxs(StyledAdvancedButton, { ref: ref, "$width": width ?? '90px', "$height": height ?? '30px', onMouseOver: () => !disabled && setIsHovered(true), onMouseOut: () => setIsHovered(false), "$isDisabled": disabled, "$isPrimaryOutline": advancedType === 'primary' && (advancedColor === 'primaryOutline' || color === 'primaryOutline'), onClick: () => !disabled && onClick?.(), onMouseDown: (e) => !disabled && onMouseDown?.(e), children: [_jsx(StyledAdvancedButtonIcon, { "$color": advancedColor, "$isVisible": isHovered, "$isDisabled": disabled, "$type": advancedType, children: icon }), _jsx(StyledAdvancedButtonText, { "$color": advancedColor, "$isDisabled": disabled, "$type": advancedType, children: caption })] }));
|
|
290
|
+
return (_jsxs(StyledAdvancedButton, { ref: ref, "$width": width ?? '90px', "$height": height ?? '30px', onMouseOver: () => !disabled && setIsHovered(true), onMouseOut: () => setIsHovered(false), "$isDisabled": disabled, "$isPrimaryOutline": advancedType === 'primary' && (advancedColor === 'primaryOutline' || color === 'primaryOutline'), onClick: () => !disabled && onClick?.(), onMouseDown: (e) => !disabled && onMouseDown?.(e), children: [_jsx(StyledAdvancedButtonIcon, { "$color": advancedColor, "$isVisible": isHovered, "$isDisabled": disabled, "$type": advancedType, children: icon }), _jsx(StyledAdvancedButtonText, { "$color": advancedColor, "$isDisabled": disabled, "$type": advancedType, "$reserveArrow": hasSecondaryActions, children: caption })] }));
|
|
168
291
|
}
|
|
169
292
|
else {
|
|
170
293
|
return (_jsx(StyledTextButton, { ref: ref, color: color, caption: caption, disabled: disabled, fontSize: fontSize, onClick: onClick, onMouseDown: (e) => !disabled && onMouseDown?.(e), children: caption }));
|
|
@@ -175,6 +298,15 @@ const TMButton = forwardRef((props, ref) => {
|
|
|
175
298
|
_jsxs(_Fragment, { children: [caption &&
|
|
176
299
|
_jsx(StyledButtonTooltipHeader, { "$description": description, children: _jsx(StyledButtonTooltipItem, { "$color": color, children: caption }) }), description && _jsx(StyledButtonTooltipItem, { children: description })] }));
|
|
177
300
|
};
|
|
178
|
-
|
|
301
|
+
const buttonWithTooltip = showTooltip
|
|
302
|
+
? _jsx(TMTooltip, { hideAfterDelay: true, content: renderTooltip(), children: renderedButton() })
|
|
303
|
+
: renderedButton();
|
|
304
|
+
const renderSplitButton = () => (_jsxs(StyledSplitContainer, { ref: splitContainerRef, children: [buttonWithTooltip, _jsx(StyledSplitArrow, { type: 'button', "$isDisabled": disabled, "$isOpen": isDropdownOpen, "aria-haspopup": 'menu', "aria-expanded": isDropdownOpen, onClick: (e) => { e.stopPropagation(); toggleDropdown(); }, onMouseDown: (e) => e.stopPropagation(), children: _jsx(IconChevronDown, { fontSize: 14 }) }), isDropdownOpen && createPortal(_jsx(StyledDropdown, { ref: dropdownRef, role: 'menu', "$left": dropdownPos.left, "$top": dropdownPos.top, "$minWidth": dropdownPos.minWidth, children: secondaryActions.map((action, idx) => (_jsxs(StyledDropdownItem, { type: 'button', role: 'menuitem', "$disabled": action.disabled, disabled: action.disabled, onClick: () => {
|
|
305
|
+
if (action.disabled)
|
|
306
|
+
return;
|
|
307
|
+
setIsDropdownOpen(false);
|
|
308
|
+
action.onClick?.();
|
|
309
|
+
}, children: [action.icon && _jsx("span", { className: 'tm-split-item-icon', children: action.icon }), _jsx("span", { children: action.name })] }, `${action.name}-${idx}`))) }), document.body)] }));
|
|
310
|
+
return (_jsx("div", { style: elementStyle, children: hasSecondaryActions ? renderSplitButton() : buttonWithTooltip }));
|
|
179
311
|
});
|
|
180
312
|
export default TMButton;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useState } from 'react';
|
|
3
3
|
import { Workbook } from 'devextreme-exceljs-fork';
|
|
4
|
-
import { buildValueToLabelMapFromDataColumns, getExceptionMessage, SDKUI_Localizator } from '../../helper';
|
|
4
|
+
import { buildValueToLabelMapFromDataColumns, findDataColumnByField, formatValueForExport, getExceptionMessage, SDKUI_Localizator } from '../../helper';
|
|
5
5
|
import TMCheckBox from '../editors/TMCheckBox';
|
|
6
6
|
import TMButton from './TMButton';
|
|
7
7
|
import TMModal from './TMModal';
|
|
@@ -96,43 +96,52 @@ const TMDataGridExportForm = (props) => {
|
|
|
96
96
|
if (mapForField) {
|
|
97
97
|
result = mapForField.get(value) ?? value;
|
|
98
98
|
}
|
|
99
|
-
else {
|
|
100
|
-
result = value;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
// If the column is a datetime type, attempt to format it as a locale date string
|
|
104
|
-
if (col.dataType === 'datetime' && result) {
|
|
105
|
-
const parsedDate = new Date(result);
|
|
106
|
-
if (!isNaN(parsedDate.getTime())) {
|
|
107
|
-
result = parsedDate.toLocaleDateString();
|
|
108
|
-
}
|
|
109
99
|
}
|
|
110
|
-
//
|
|
111
|
-
|
|
100
|
+
// Find column and format value
|
|
101
|
+
const dataCol = findDataColumnByField(columns, col.dataField, col.caption);
|
|
102
|
+
return formatValueForExport(result, dataCol, col.dataType);
|
|
112
103
|
};
|
|
113
104
|
switch (formatSelected) {
|
|
114
105
|
case 'csv': {
|
|
115
|
-
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
106
|
+
// Escape dei valori CSV per gestire separatori, virgolette e ritorni a capo
|
|
107
|
+
const escapeCsv = (v) => { const s = String(v ?? ''); return /[;"\r\n]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s; };
|
|
108
|
+
// Recupera il nome della colonna e lo prepara per il formato CSV
|
|
109
|
+
const getHeader = (c) => escapeCsv(c.caption ?? c.dataField);
|
|
110
|
+
// Crea la prima riga del CSV con le intestazioni delle colonne
|
|
111
|
+
const rows = [
|
|
112
|
+
(exportSelectedFormatColumns
|
|
113
|
+
? [SDKUI_Localizator.SelectedSingular, ...visibleColumns.map(getHeader)]
|
|
114
|
+
: visibleColumns.map(getHeader)).join(';')
|
|
115
|
+
];
|
|
116
|
+
// Scorre tutte le righe da esportare
|
|
119
117
|
rowsToExport.forEach((item, idx) => {
|
|
118
|
+
// Recupera l'indice originale della riga per verificare la selezione
|
|
120
119
|
const originalIndex = exportSelectedOnly ? selectedRowKeys[idx] : dataSource.indexOf(item);
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
120
|
+
// Costruisce i valori della riga corrente
|
|
121
|
+
const values = [
|
|
122
|
+
// Aggiunge lo stato selezionato/deselezionato se richiesto
|
|
123
|
+
...(exportSelectedFormatColumns
|
|
124
|
+
? [selectedSet.has(originalIndex) ? SDKUI_Localizator.SelectedSingular : SDKUI_Localizator.Deselected]
|
|
125
|
+
: []),
|
|
126
|
+
// Aggiunge i valori delle colonne applicando l'escape CSV
|
|
127
|
+
...visibleColumns.map(col => escapeCsv(col.dataField ? getValue(col, item[col.dataField]) : ''))
|
|
128
|
+
];
|
|
129
|
+
// Aggiunge la riga completa al contenuto CSV
|
|
130
|
+
rows.push(values.join(';'));
|
|
132
131
|
});
|
|
133
|
-
|
|
132
|
+
// Crea il file CSV aggiungendo il BOM UTF-8 per la corretta lettura degli accenti in Excel
|
|
133
|
+
const blob = new Blob(['\uFEFF', rows.join('\r\n')], { type: 'text/csv;charset=utf-8;' });
|
|
134
|
+
// Avvia il download del file generato
|
|
134
135
|
downloadFile(blob, formatSelected);
|
|
135
|
-
|
|
136
|
+
// Registra il risultato positivo dell'operazione di esportazione
|
|
137
|
+
result.push({
|
|
138
|
+
rowIndex: 1,
|
|
139
|
+
id1: 1,
|
|
140
|
+
id2: 0,
|
|
141
|
+
description: SDKUI_Localizator.OperationSuccess,
|
|
142
|
+
resultType: ResultTypes.SUCCESS
|
|
143
|
+
});
|
|
144
|
+
// Termina il caso CSV nello switch
|
|
136
145
|
break;
|
|
137
146
|
}
|
|
138
147
|
case 'xlsx': {
|
|
@@ -101,7 +101,7 @@ const TMModal = ({ resizable = true, expandable = false, isModal = true, title =
|
|
|
101
101
|
setShowPopup(false);
|
|
102
102
|
onClose && onClose();
|
|
103
103
|
};
|
|
104
|
-
return (_jsx(_Fragment, { children: isModal ? (_jsx(Popup, { ref: popupRef, showCloseButton: showHeader ? showCloseButton : false, showTitle: showHeader, animation: undefined, minWidth: minWidth, minHeight: minHeight, maxHeight: '95%', maxWidth: '95%', dragEnabled: !isResizing, resizeEnabled: resizable, width: expandable && isFullScreen ? '95%' : initialWidth, height: expandable && isFullScreen ? '95%' : initialHeight, title: title, visible: showPopup, onShown: handleShown, onResizeStart: handleResizeStart, onResizeEnd: handleResizeEnd, onHiding: onHiding, toolbarItems: showHeader && expandable ? [
|
|
104
|
+
return (_jsx(_Fragment, { children: isModal ? (_jsx(Popup, { ref: popupRef, showCloseButton: showHeader ? showCloseButton : false, showTitle: showHeader, animation: undefined, minWidth: minWidth, minHeight: minHeight, maxHeight: '95%', maxWidth: '95%', dragEnabled: !isResizing && !isFullScreen, resizeEnabled: resizable && !isFullScreen, position: expandable && isFullScreen ? { my: 'center', at: 'center', of: window } : undefined, width: expandable && isFullScreen ? '95%' : initialWidth, height: expandable && isFullScreen ? '95%' : initialHeight, title: title, visible: showPopup, onShown: handleShown, onResizeStart: handleResizeStart, onResizeEnd: handleResizeEnd, onHiding: onHiding, toolbarItems: showHeader && expandable ? [
|
|
105
105
|
{
|
|
106
106
|
widget: 'dxButton',
|
|
107
107
|
location: 'after',
|
|
@@ -38,5 +38,7 @@ interface ITMDynDataListItemChooserFormProps extends ITMChooserFormProps<DataLis
|
|
|
38
38
|
layoutMode?: LayoutModes;
|
|
39
39
|
dynDL?: DynamicDataListDescriptor;
|
|
40
40
|
searchResult?: SearchResultDescriptor;
|
|
41
|
+
/** Se presente, sono i parametri da passare alla query per recuperare il dataSource */
|
|
42
|
+
queryParamsDynDataList?: string[];
|
|
41
43
|
}
|
|
42
44
|
export declare const TMDynDataListItemChooserForm: (props: ITMDynDataListItemChooserFormProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -122,7 +122,7 @@ const TMDynDataListItemChooser = ({ tid, md, width = '100%', titleForm, openChoo
|
|
|
122
122
|
updateIsModalOpen?.(true);
|
|
123
123
|
}
|
|
124
124
|
}, elementStyle: elementStyle, isModifiedWhen: isModifiedWhen, openEditorOnSummaryClick: openChooserBySingleClick, label: label, template: renderTemplate(), onClearClick: showClearButton ? () => { onValueChanged?.([]); } : undefined, validationItems: validationItems }), showChooser &&
|
|
125
|
-
_jsx(TMDynDataListItemChooserForm, { TID: tid, MID: md?.id, dynDL: dynDl, title: titleForm, allowMultipleSelection: allowMultipleSelection, searchResult: dataSource, selectedIDs: values, onClose: () => {
|
|
125
|
+
_jsx(TMDynDataListItemChooserForm, { TID: tid, MID: md?.id, dynDL: dynDl, title: titleForm, allowMultipleSelection: allowMultipleSelection, searchResult: dataSource, selectedIDs: values, queryParamsDynDataList: queryParamsDynDataList, onClose: () => {
|
|
126
126
|
setShowChooser(false);
|
|
127
127
|
updateIsModalOpen?.(false);
|
|
128
128
|
summaryInputRef.current?.focus();
|
|
@@ -167,7 +167,7 @@ const TMDynDataListItemChooser = ({ tid, md, width = '100%', titleForm, openChoo
|
|
|
167
167
|
export default TMDynDataListItemChooser;
|
|
168
168
|
const cellRenderIcon = () => _jsx(IconDetails, {});
|
|
169
169
|
export const TMDynDataListItemChooserForm = (props) => {
|
|
170
|
-
const { TID, MID, layoutMode, dynDL, searchResult, selectedIDs, title, width, height, onChoose } = props;
|
|
170
|
+
const { TID, MID, layoutMode, dynDL, searchResult, selectedIDs, title, width, height, onChoose, queryParamsDynDataList } = props;
|
|
171
171
|
// Generate unique keys for all columns
|
|
172
172
|
const uniqueKeys = generateUniqueColumnKeys(searchResult?.dtdResult?.columns, searchResult?.fromTID);
|
|
173
173
|
const dataColumns = searchResult?.dtdResult?.columns?.map((col, index) => {
|
|
@@ -184,12 +184,10 @@ export const TMDynDataListItemChooserForm = (props) => {
|
|
|
184
184
|
});
|
|
185
185
|
const keyValue = uniqueKeys[dynDL?.selectItemForValue ?? 0] ?? '';
|
|
186
186
|
const getItems = async (refreshCache) => {
|
|
187
|
-
if (!searchResult)
|
|
188
|
-
return [];
|
|
189
187
|
if (refreshCache)
|
|
190
188
|
DataListCacheService.RemoveAll();
|
|
191
189
|
TMSpinner.show({ description: `${SDKUI_Localizator.Loading} - ${SDK_Localizator.DataList} ...` });
|
|
192
|
-
let result = await SDK_Globals.tmSession?.NewSearchEngine().GetDynDataListValuesAsync(TID, MID, layoutMode, [])
|
|
190
|
+
let result = await SDK_Globals.tmSession?.NewSearchEngine().GetDynDataListValuesAsync(TID, MID, layoutMode, queryParamsDynDataList ?? [])
|
|
193
191
|
.catch((err) => { TMSpinner.hide(); TMExceptionBoxManager.show({ exception: err }); });
|
|
194
192
|
TMSpinner.hide();
|
|
195
193
|
return result ? searchResultDescriptorToSimpleArray(result) ?? [] : [];
|
|
@@ -200,5 +198,5 @@ export const TMDynDataListItemChooserForm = (props) => {
|
|
|
200
198
|
titleDataList += `: ${title}`;
|
|
201
199
|
return titleDataList;
|
|
202
200
|
};
|
|
203
|
-
return (_jsx(TMChooserForm, { title: getTitle(), allowMultipleSelection: props.allowMultipleSelection, width: width, height: height, keyName: keyValue ?? '', showDefaultColumns: false, hasShowId: false, columns: dataColumns, selectedIDs: selectedIDs, cellRenderIcon: cellRenderIcon,
|
|
201
|
+
return (_jsx(TMChooserForm, { title: getTitle(), allowMultipleSelection: props.allowMultipleSelection, width: width, height: height, keyName: keyValue ?? '', showDefaultColumns: false, hasShowId: false, columns: dataColumns, selectedIDs: selectedIDs, cellRenderIcon: cellRenderIcon, getItems: getItems, onClose: props.onClose, onChoose: (IDs) => onChoose?.(IDs) }));
|
|
204
202
|
};
|
|
@@ -1,17 +1,32 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { ValidationItem } from '@topconsultnpm/sdk-ts';
|
|
2
|
+
import { MetadataFormats, ValidationItem } from '@topconsultnpm/sdk-ts';
|
|
3
3
|
import { IDateBoxOptions } from 'devextreme-react/date-box';
|
|
4
4
|
import { DateDisplayTypes } from '../../helper';
|
|
5
5
|
interface ITMDateBoxProps extends IDateBoxOptions {
|
|
6
|
+
/** Tipo di picker da mostrare: Date (solo calendario), Time (solo ora), DateTime (entrambi). Determina quali controlli appaiono nel dropdown. */
|
|
6
7
|
dateDisplayType?: DateDisplayTypes;
|
|
8
|
+
/** Lista di errori/warning di validazione da mostrare sotto il campo */
|
|
7
9
|
validationItems?: ValidationItem[];
|
|
10
|
+
/** Se true, evidenzia il campo come modificato (bordo e testo colorati) */
|
|
8
11
|
isModifiedWhen?: boolean;
|
|
12
|
+
/** Se true, serializza la data nel formato ISO 'yyyy-MM-ddTHH:mm:ss' */
|
|
9
13
|
useDateSerializationFormat?: boolean;
|
|
14
|
+
/** Elemento DOM contenitore per il dropdown del calendario */
|
|
10
15
|
containerElement?: Element;
|
|
16
|
+
/** Pulsanti aggiuntivi da mostrare nel campo */
|
|
11
17
|
buttons?: any;
|
|
18
|
+
/** Se true (default), resetta l'ora a 00:00:00 quando si preme un tasto */
|
|
12
19
|
resetTimeToZeroOnKeyPress?: boolean;
|
|
20
|
+
/** Icona da mostrare a sinistra del campo */
|
|
13
21
|
icon?: any;
|
|
22
|
+
/** Padding del contenitore esterno */
|
|
14
23
|
padding?: string;
|
|
24
|
+
/** Cultura/locale per la formattazione (es. 'en-gb', 'it-it'). Usata per tradurre i nomi dei mesi/giorni nella lingua corretta. */
|
|
25
|
+
formatCulture?: string;
|
|
26
|
+
/** Formato di visualizzazione della data (es. ShortDate='17/07/2026', LongDate='17 July 2026'). Determina come la data viene rappresentata come stringa nel campo. */
|
|
27
|
+
metadataFormat?: MetadataFormats;
|
|
28
|
+
/** Messaggio personalizzato per data non valida. Se non specificato, genera automaticamente un messaggio con esempio del formato (es. 'Formato richiesto: 20/07/2026') */
|
|
29
|
+
invalidDateMessage?: string;
|
|
15
30
|
}
|
|
16
31
|
declare const TMDateBox: React.FC<ITMDateBoxProps>;
|
|
17
32
|
export default TMDateBox;
|
|
@@ -1,35 +1,103 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useId, useRef } from 'react';
|
|
2
|
+
import { useEffect, useId, useMemo, useRef } from 'react';
|
|
3
3
|
import { DateBox } from 'devextreme-react';
|
|
4
|
-
import {
|
|
4
|
+
import { MetadataFormats } from '@topconsultnpm/sdk-ts';
|
|
5
|
+
import { DateDisplayTypes, Globalization, SDKUI_Localizator } from '../../helper';
|
|
5
6
|
import { TMColors } from '../../utils/theme';
|
|
6
7
|
import TMVilViewer from '../base/TMVilViewer';
|
|
8
|
+
import { formatDateTimeByMetadataFormat } from '../../helper/TMUtils';
|
|
7
9
|
const TMDateBox = (props) => {
|
|
8
|
-
const
|
|
10
|
+
const { dateDisplayType, validationItems, isModifiedWhen, useDateSerializationFormat, containerElement, resetTimeToZeroOnKeyPress, icon, padding, formatCulture, metadataFormat, invalidDateMessage, readOnly, showClearButton, disabled, displayFormat, label, type, value, width, onValueChange, onInitialized, onContentReady, placeholder, name, inputAttr } = props;
|
|
11
|
+
const resetTimeToZero = resetTimeToZeroOnKeyPress ?? true;
|
|
9
12
|
const autoId = useId();
|
|
10
|
-
const effectiveInputAttr = { id: autoId, ...
|
|
13
|
+
const effectiveInputAttr = { id: autoId, ...inputAttr };
|
|
14
|
+
// Determina se il formato richiede nomi testuali (mesi/giorni) che dipendono dalla cultura
|
|
15
|
+
const requiresTextualFormat = useMemo(() => {
|
|
16
|
+
// Solo i formati con "Long" nella data hanno nomi di mesi/giorni che variano per cultura
|
|
17
|
+
return metadataFormat === MetadataFormats.LongDate ||
|
|
18
|
+
metadataFormat === MetadataFormats.LongDateShortTime ||
|
|
19
|
+
metadataFormat === MetadataFormats.LongDateLongTime;
|
|
20
|
+
}, [metadataFormat]);
|
|
21
|
+
// Funzione di formattazione personalizzata per rispettare formatCulture
|
|
22
|
+
// Usata SOLO per formati con nomi testuali (LongDate, LongDateShortTime, LongDateLongTime) e se formatCulture è specificato
|
|
23
|
+
const customDisplayFormat = useMemo(() => {
|
|
24
|
+
// Usa funzione custom solo se c'è formatCulture E il formato richiede nomi testuali
|
|
25
|
+
if (!formatCulture || !requiresTextualFormat)
|
|
26
|
+
return undefined;
|
|
27
|
+
// Restituisce una funzione che DevExtreme userà per formattare la data
|
|
28
|
+
return (date) => {
|
|
29
|
+
try {
|
|
30
|
+
if (!date)
|
|
31
|
+
return '';
|
|
32
|
+
// Assicurati che sia un oggetto Date valido
|
|
33
|
+
const dateObj = date instanceof Date ? date : new Date(date);
|
|
34
|
+
if (isNaN(dateObj.getTime()))
|
|
35
|
+
return '';
|
|
36
|
+
return formatDateTimeByMetadataFormat(dateObj, metadataFormat, formatCulture);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// Fallback: restituisce stringa ISO o vuota in caso di errore
|
|
40
|
+
try {
|
|
41
|
+
return date instanceof Date ? date.toISOString() : String(date ?? '');
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return '';
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
}, [formatCulture, metadataFormat, requiresTextualFormat]);
|
|
49
|
+
// Genera il messaggio di errore con esempio del formato supportato
|
|
50
|
+
const computedInvalidDateMessage = useMemo(() => {
|
|
51
|
+
try {
|
|
52
|
+
if (invalidDateMessage)
|
|
53
|
+
return invalidDateMessage;
|
|
54
|
+
// Genera un esempio usando la data corrente
|
|
55
|
+
const exampleDate = new Date();
|
|
56
|
+
let exampleFormatted;
|
|
57
|
+
if (customDisplayFormat) {
|
|
58
|
+
// Usa la funzione custom se disponibile
|
|
59
|
+
exampleFormatted = customDisplayFormat(exampleDate);
|
|
60
|
+
}
|
|
61
|
+
else if (typeof displayFormat === 'string') {
|
|
62
|
+
// Usa formatDateTimeByMetadataFormat se abbiamo un formato stringa
|
|
63
|
+
exampleFormatted = formatDateTimeByMetadataFormat(exampleDate, metadataFormat, formatCulture) || displayFormat;
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
// Fallback: usa il formato di Globalization o localizzazione standard
|
|
67
|
+
const globFormat = Globalization.getDateDisplayFormat(dateDisplayType);
|
|
68
|
+
exampleFormatted = typeof globFormat === 'string' ? globFormat : exampleDate.toLocaleDateString(formatCulture || 'it-IT');
|
|
69
|
+
}
|
|
70
|
+
if (!exampleFormatted)
|
|
71
|
+
return SDKUI_Localizator.ValueMustBeDateOrTime;
|
|
72
|
+
return `${SDKUI_Localizator.RequiredFormat}: ${exampleFormatted}`;
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
// In caso di errore, usa il messaggio generico tradotto
|
|
76
|
+
return SDKUI_Localizator.ValueMustBeDateOrTime;
|
|
77
|
+
}
|
|
78
|
+
}, [invalidDateMessage, customDisplayFormat, displayFormat, dateDisplayType, metadataFormat, formatCulture]);
|
|
11
79
|
useEffect(() => {
|
|
12
80
|
let htmlElement = dateBoxRef?.current?.instance().element();
|
|
13
81
|
if (!htmlElement)
|
|
14
82
|
return;
|
|
15
|
-
let borderColor =
|
|
16
|
-
let textColor =
|
|
83
|
+
let borderColor = isModifiedWhen ? TMColors.tertiary : TMColors.border_normal;
|
|
84
|
+
let textColor = isModifiedWhen ? TMColors.isModified : TMColors.text_normal;
|
|
17
85
|
let inputContainer = htmlElement.getElementsByClassName("dx-texteditor-input-container")[0];
|
|
18
86
|
if (inputContainer) {
|
|
19
|
-
inputContainer.style.background =
|
|
20
|
-
inputContainer.style.color =
|
|
87
|
+
inputContainer.style.background = readOnly ? 'linear-gradient(white 20%, #d6d6d6 80%)' : '';
|
|
88
|
+
inputContainer.style.color = readOnly ? '#525252' : textColor;
|
|
21
89
|
}
|
|
22
90
|
let input = htmlElement.querySelector(".dx-texteditor-input");
|
|
23
91
|
if (input) {
|
|
24
|
-
if (!
|
|
92
|
+
if (!readOnly) {
|
|
25
93
|
input.style.color = textColor;
|
|
26
94
|
}
|
|
27
95
|
input.style.fontSize = 'var(--base-font-size)';
|
|
28
96
|
input.style.fontFamily = 'var(--base-font-family)';
|
|
29
97
|
}
|
|
30
|
-
let
|
|
31
|
-
if (
|
|
32
|
-
|
|
98
|
+
let labelEl = htmlElement.getElementsByClassName("dx-label")[0];
|
|
99
|
+
if (labelEl) {
|
|
100
|
+
labelEl.style.borderBottomColor = borderColor;
|
|
33
101
|
}
|
|
34
102
|
let labelBefore = htmlElement.getElementsByClassName("dx-label-before")[0];
|
|
35
103
|
if (labelBefore) {
|
|
@@ -43,13 +111,13 @@ const TMDateBox = (props) => {
|
|
|
43
111
|
labelAfter.style.borderTopColor = borderColor;
|
|
44
112
|
labelAfter.style.borderInlineEndColor = borderColor;
|
|
45
113
|
}
|
|
46
|
-
}, [
|
|
114
|
+
}, [isModifiedWhen, readOnly]);
|
|
47
115
|
const dateBoxRef = useRef(null);
|
|
48
116
|
// soluzione tratta da https://supportcenter.devexpress.com/ticket/details/t1025617/setting-default-time-in-datebox
|
|
49
117
|
const dropDownOptions = {
|
|
50
|
-
container:
|
|
118
|
+
container: containerElement,
|
|
51
119
|
onShown: (e) => {
|
|
52
|
-
if (
|
|
120
|
+
if (value != undefined)
|
|
53
121
|
return;
|
|
54
122
|
let dateBoxInstance = dateBoxRef.current?.instance();
|
|
55
123
|
const currentDate = new Date();
|
|
@@ -59,13 +127,13 @@ const TMDateBox = (props) => {
|
|
|
59
127
|
}
|
|
60
128
|
};
|
|
61
129
|
const getType = () => {
|
|
62
|
-
if (
|
|
63
|
-
return
|
|
64
|
-
if (!
|
|
130
|
+
if (type != undefined)
|
|
131
|
+
return type;
|
|
132
|
+
if (!dateDisplayType || dateDisplayType == DateDisplayTypes.DateTime)
|
|
65
133
|
return "datetime";
|
|
66
|
-
return
|
|
134
|
+
return dateDisplayType == DateDisplayTypes.Date ? "date" : "time";
|
|
67
135
|
};
|
|
68
|
-
return (_jsxs("div", { style: { display: 'flex', alignItems: 'center', width: '100%', padding:
|
|
136
|
+
return (_jsxs("div", { style: { display: 'flex', alignItems: 'center', width: '100%', padding: padding }, children: [icon && (_jsx("span", { style: { marginRight: '8px', marginTop: '8px', display: 'flex', alignItems: 'center' }, children: icon })), _jsxs("div", { onContextMenu: (e) => e.stopPropagation(), style: { display: 'flex', flexDirection: 'column', gap: '5px', width: '100%' }, children: [_jsx(DateBox, { readOnly: readOnly, ref: dateBoxRef, showClearButton: showClearButton, dateSerializationFormat: useDateSerializationFormat ? 'yyyy-MM-ddTHH:mm:ss' : undefined, disabled: disabled, displayFormat: customDisplayFormat ?? displayFormat ?? Globalization.getDateDisplayFormat(dateDisplayType), dropDownOptions: dropDownOptions, invalidDateMessage: computedInvalidDateMessage, label: label, labelMode: 'static', type: getType(), useMaskBehavior: !customDisplayFormat, height: '28px', value: value, width: width, valueChangeEvent: 'keyup input change', onValueChange: (e) => { onValueChange?.(e); }, onInitialized: (e) => { onInitialized?.(e); }, onContentReady: (e) => { onContentReady?.(e); }, placeholder: placeholder, ...(name && { name: name }), inputAttr: effectiveInputAttr, onKeyUp: (e) => {
|
|
69
137
|
if (e.event?.code == "Space") {
|
|
70
138
|
const currentDate = new Date();
|
|
71
139
|
currentDate.setHours(0, 0, 0, 0);
|
|
@@ -73,7 +141,7 @@ const TMDateBox = (props) => {
|
|
|
73
141
|
}
|
|
74
142
|
}, onValueChanged: (e) => {
|
|
75
143
|
if (resetTimeToZero) {
|
|
76
|
-
if (
|
|
144
|
+
if (value != undefined)
|
|
77
145
|
return;
|
|
78
146
|
if (!e.value)
|
|
79
147
|
return;
|
|
@@ -81,6 +149,6 @@ const TMDateBox = (props) => {
|
|
|
81
149
|
currentDate.setHours(0, 0, 0, 0);
|
|
82
150
|
e.component.option("value", currentDate.toISOString());
|
|
83
151
|
}
|
|
84
|
-
} }), _jsx(TMVilViewer, { vil:
|
|
152
|
+
} }), _jsx(TMVilViewer, { vil: validationItems })] })] }));
|
|
85
153
|
};
|
|
86
154
|
export default TMDateBox;
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { ValidationItem, LayoutModes, QueryOperators } from '@topconsultnpm/sdk-ts';
|
|
2
|
+
import { MetadataDescriptor, ValidationItem, LayoutModes, QueryOperators } from '@topconsultnpm/sdk-ts';
|
|
3
3
|
import { DynDataListsToBeRefreshed, MIDsToBeUpdated } from '../choosers/TMDynDataListItemChooser';
|
|
4
4
|
interface ITMMetadataEditorProps {
|
|
5
5
|
tid: number | undefined;
|
|
6
6
|
mid?: number;
|
|
7
|
+
/** MetadataDescriptor esplicito. Se valorizzato, l'editor lo usa direttamente
|
|
8
|
+
* e salta la risoluzione interna via tid/mid (utile per metadati non legati
|
|
9
|
+
* a un tipo documento, es. copertina CaseFlow). */
|
|
10
|
+
md?: MetadataDescriptor;
|
|
7
11
|
value: string | undefined;
|
|
8
12
|
queryParamsDynDataList?: string[];
|
|
9
13
|
autoFocus?: boolean;
|