@topconsultnpm/sdkui-react 6.20.0-dev2.30 → 6.20.0-dev2.32
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/choosers/TMDataListItemEditor.d.ts +11 -0
- package/lib/components/choosers/TMDataListItemEditor.js +130 -0
- package/lib/components/choosers/TMDataListItemFields.d.ts +11 -0
- package/lib/components/choosers/TMDataListItemFields.js +61 -0
- package/lib/components/choosers/TMDataListItemPicker.d.ts +1 -0
- package/lib/components/choosers/TMDataListItemPicker.js +178 -18
- package/lib/components/choosers/TMImageIDChooser.d.ts +16 -0
- package/lib/components/choosers/TMImageIDChooser.js +53 -0
- package/lib/components/editors/TMLocalizedTextBox.d.ts +1 -0
- package/lib/components/editors/TMLocalizedTextBox.js +3 -3
- package/lib/components/features/workflow/diagram/DiagramItemForm.js +1 -1
- package/lib/components/index.d.ts +2 -0
- package/lib/components/index.js +2 -0
- package/lib/helper/SDKUI_Globals.d.ts +3 -0
- package/lib/helper/SDKUI_Globals.js +53 -0
- package/package.json +2 -2
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { DataListItemDescriptor } from '@topconsultnpm/sdk-ts';
|
|
3
|
+
interface TMDataListItemEditorProps {
|
|
4
|
+
isOpen: boolean;
|
|
5
|
+
item?: DataListItemDescriptor;
|
|
6
|
+
isCreating: boolean;
|
|
7
|
+
onClose: () => void;
|
|
8
|
+
onSave: (item: DataListItemDescriptor) => void;
|
|
9
|
+
}
|
|
10
|
+
declare const TMDataListItemEditor: React.FC<TMDataListItemEditorProps>;
|
|
11
|
+
export default TMDataListItemEditor;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useEffect } from 'react';
|
|
3
|
+
import styled from 'styled-components';
|
|
4
|
+
import { DataListItemDescriptor, NamesLoc3, PlatformObjectValidator } from '@topconsultnpm/sdk-ts';
|
|
5
|
+
import { IconCloseOutline, IconSave, IconUndo, SDKUI_Localizator } from '../../helper';
|
|
6
|
+
import TMButton from '../base/TMButton';
|
|
7
|
+
import TMModal from '../base/TMModal';
|
|
8
|
+
import TMDataListItemFields from './TMDataListItemFields';
|
|
9
|
+
import { TMColors } from '../../utils/theme';
|
|
10
|
+
const FormContainer = styled.div `
|
|
11
|
+
display: flex;
|
|
12
|
+
flex-direction: column;
|
|
13
|
+
gap: 15px;
|
|
14
|
+
padding: 20px;
|
|
15
|
+
`;
|
|
16
|
+
const ButtonsContainer = styled.div `
|
|
17
|
+
display: flex;
|
|
18
|
+
justify-content: flex-end;
|
|
19
|
+
gap: 10px;
|
|
20
|
+
margin-top: 10px;
|
|
21
|
+
`;
|
|
22
|
+
const TMDataListItemEditor = ({ isOpen, item, isCreating, onClose, onSave }) => {
|
|
23
|
+
const [editedItem, setEditedItem] = useState(null);
|
|
24
|
+
const [editedItemOrig, setEditedItemOrig] = useState(null);
|
|
25
|
+
const [validationItems, setValidationItems] = useState([]);
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
if (!editedItem)
|
|
28
|
+
return;
|
|
29
|
+
let vil = [];
|
|
30
|
+
PlatformObjectValidator.DataListItemValidator(editedItem, vil);
|
|
31
|
+
setValidationItems(vil);
|
|
32
|
+
}, [editedItem]);
|
|
33
|
+
// Funzione helper per clonare un DataListItemDescriptor
|
|
34
|
+
const cloneItem = (source) => {
|
|
35
|
+
const clone = new DataListItemDescriptor();
|
|
36
|
+
Object.assign(clone, source);
|
|
37
|
+
if (source.namesLoc) {
|
|
38
|
+
const namesLocClone = new NamesLoc3();
|
|
39
|
+
namesLocClone.init(source.namesLoc);
|
|
40
|
+
clone.namesLoc = namesLocClone;
|
|
41
|
+
}
|
|
42
|
+
return clone;
|
|
43
|
+
};
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
if (isOpen) {
|
|
46
|
+
if (item) {
|
|
47
|
+
// Crea una copia dell'item per editarlo
|
|
48
|
+
const clone = cloneItem(item);
|
|
49
|
+
setEditedItem(clone);
|
|
50
|
+
// Salva anche l'originale per il confronto e l'undo
|
|
51
|
+
const original = cloneItem(item);
|
|
52
|
+
setEditedItemOrig(original);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
// Nuovo item vuoto
|
|
56
|
+
const newItem = new DataListItemDescriptor();
|
|
57
|
+
newItem.value = '';
|
|
58
|
+
newItem.name = '';
|
|
59
|
+
setEditedItem(newItem);
|
|
60
|
+
setEditedItemOrig(cloneItem(newItem));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}, [isOpen, item]);
|
|
64
|
+
// Funzione per verificare se ci sono modifiche rispetto all'originale
|
|
65
|
+
const hasChanges = () => {
|
|
66
|
+
if (!editedItem || !editedItemOrig)
|
|
67
|
+
return false;
|
|
68
|
+
// Confronta i campi principali
|
|
69
|
+
if (editedItem.value !== editedItemOrig.value)
|
|
70
|
+
return true;
|
|
71
|
+
if (editedItem.name !== editedItemOrig.name)
|
|
72
|
+
return true;
|
|
73
|
+
if (editedItem.imageID !== editedItemOrig.imageID)
|
|
74
|
+
return true;
|
|
75
|
+
// Confronta namesLoc
|
|
76
|
+
const namesLoc1 = editedItem.namesLoc;
|
|
77
|
+
const namesLoc2 = editedItemOrig.namesLoc;
|
|
78
|
+
if (!namesLoc1 && !namesLoc2)
|
|
79
|
+
return false;
|
|
80
|
+
if (!namesLoc1 || !namesLoc2)
|
|
81
|
+
return true;
|
|
82
|
+
if (namesLoc1.it_IT !== namesLoc2.it_IT)
|
|
83
|
+
return true;
|
|
84
|
+
if (namesLoc1.en_US !== namesLoc2.en_US)
|
|
85
|
+
return true;
|
|
86
|
+
if (namesLoc1.fr_FR !== namesLoc2.fr_FR)
|
|
87
|
+
return true;
|
|
88
|
+
if (namesLoc1.pt_PT !== namesLoc2.pt_PT)
|
|
89
|
+
return true;
|
|
90
|
+
if (namesLoc1.es_ES !== namesLoc2.es_ES)
|
|
91
|
+
return true;
|
|
92
|
+
if (namesLoc1.de_DE !== namesLoc2.de_DE)
|
|
93
|
+
return true;
|
|
94
|
+
return false;
|
|
95
|
+
};
|
|
96
|
+
const handleUndo = () => {
|
|
97
|
+
if (!editedItemOrig)
|
|
98
|
+
return;
|
|
99
|
+
// Ripristina l'item originale clonandolo
|
|
100
|
+
const restored = cloneItem(editedItemOrig);
|
|
101
|
+
setEditedItem(restored);
|
|
102
|
+
};
|
|
103
|
+
const handleSave = () => {
|
|
104
|
+
if (!editedItem)
|
|
105
|
+
return;
|
|
106
|
+
// Pulisci namesLoc rimuovendo valori undefined o vuoti, mantenendo l'istanza della classe
|
|
107
|
+
if (editedItem.namesLoc) {
|
|
108
|
+
const cleanedData = {};
|
|
109
|
+
Object.entries(editedItem.namesLoc).forEach(([key, value]) => {
|
|
110
|
+
if (value !== undefined && value !== '') {
|
|
111
|
+
cleanedData[key] = value;
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
// Crea una nuova istanza di NamesLoc3 con i dati puliti
|
|
115
|
+
if (Object.keys(cleanedData).length > 0) {
|
|
116
|
+
const cleanedNamesLoc = new NamesLoc3();
|
|
117
|
+
cleanedNamesLoc.init(cleanedData);
|
|
118
|
+
editedItem.namesLoc = cleanedNamesLoc;
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
editedItem.namesLoc = undefined;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
onSave(editedItem);
|
|
125
|
+
};
|
|
126
|
+
if (!isOpen || !editedItem)
|
|
127
|
+
return null;
|
|
128
|
+
return (_jsx(TMModal, { title: isCreating ? SDKUI_Localizator.NewMale : SDKUI_Localizator.Update, width: "400px", height: "auto", isModal: true, onClose: onClose, children: _jsxs(FormContainer, { children: [_jsx(TMDataListItemFields, { item: editedItem, originalItem: editedItemOrig || undefined, onItemChanged: setEditedItem, validationItems: validationItems }), _jsxs(ButtonsContainer, { children: [_jsx(TMButton, { caption: SDKUI_Localizator.Save, btnStyle: 'advanced', advancedColor: TMColors.primary, icon: _jsx(IconSave, {}), showTooltip: false, onClick: handleSave }), _jsx(TMButton, { caption: SDKUI_Localizator.Undo, btnStyle: 'toolbar', color: 'primary', icon: _jsx(IconUndo, {}), showTooltip: false, disabled: !hasChanges(), onClick: handleUndo }), _jsx(TMButton, { caption: SDKUI_Localizator.Cancel, btnStyle: 'advanced', advancedColor: TMColors.primary, icon: _jsx(IconCloseOutline, {}), showTooltip: false, onClick: onClose })] })] }) }));
|
|
129
|
+
};
|
|
130
|
+
export default TMDataListItemEditor;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { DataListItemDescriptor, ValidationItem } from '@topconsultnpm/sdk-ts';
|
|
3
|
+
interface TMDataListItemFieldsProps {
|
|
4
|
+
item: DataListItemDescriptor;
|
|
5
|
+
originalItem?: DataListItemDescriptor;
|
|
6
|
+
onItemChanged: (item: DataListItemDescriptor) => void;
|
|
7
|
+
validationItems?: ValidationItem[];
|
|
8
|
+
readOnly?: boolean;
|
|
9
|
+
}
|
|
10
|
+
declare const TMDataListItemFields: React.FC<TMDataListItemFieldsProps>;
|
|
11
|
+
export default TMDataListItemFields;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import styled from 'styled-components';
|
|
3
|
+
import { DataListItemDescriptor, CultureIDs, NamesLoc3 } from '@topconsultnpm/sdk-ts';
|
|
4
|
+
import { SDKUI_Localizator } from '../../helper';
|
|
5
|
+
import TMTextBox from '../editors/TMTextBox';
|
|
6
|
+
import TMLocalizedTextBox from '../editors/TMLocalizedTextBox';
|
|
7
|
+
import TMImageIDChooser from './TMImageIDChooser';
|
|
8
|
+
const FieldsContainer = styled.div `
|
|
9
|
+
display: flex;
|
|
10
|
+
flex-direction: column;
|
|
11
|
+
gap: 15px;
|
|
12
|
+
`;
|
|
13
|
+
const TMDataListItemFields = ({ item, originalItem, onItemChanged, validationItems = [], readOnly = false }) => {
|
|
14
|
+
const handleLocalizedNameChange = (lang, newValue) => {
|
|
15
|
+
if (!item.namesLoc) {
|
|
16
|
+
item.namesLoc = new NamesLoc3();
|
|
17
|
+
}
|
|
18
|
+
const namesLoc = item.namesLoc; // Non-null assertion
|
|
19
|
+
switch (lang) {
|
|
20
|
+
case CultureIDs.It_IT:
|
|
21
|
+
namesLoc.it_IT = newValue;
|
|
22
|
+
break;
|
|
23
|
+
case CultureIDs.En_US:
|
|
24
|
+
namesLoc.en_US = newValue;
|
|
25
|
+
break;
|
|
26
|
+
case CultureIDs.Fr_FR:
|
|
27
|
+
namesLoc.fr_FR = newValue;
|
|
28
|
+
break;
|
|
29
|
+
case CultureIDs.Pt_PT:
|
|
30
|
+
namesLoc.pt_PT = newValue;
|
|
31
|
+
break;
|
|
32
|
+
case CultureIDs.Es_ES:
|
|
33
|
+
namesLoc.es_ES = newValue;
|
|
34
|
+
break;
|
|
35
|
+
case CultureIDs.De_DE:
|
|
36
|
+
namesLoc.de_DE = newValue;
|
|
37
|
+
break;
|
|
38
|
+
default:
|
|
39
|
+
item.name = newValue;
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
// Forza il re-render clonando l'oggetto
|
|
43
|
+
const clone = new DataListItemDescriptor();
|
|
44
|
+
Object.assign(clone, item);
|
|
45
|
+
onItemChanged(clone);
|
|
46
|
+
};
|
|
47
|
+
const handleValueChange = (e) => {
|
|
48
|
+
item.value = e.target.value || '';
|
|
49
|
+
const clone = new DataListItemDescriptor();
|
|
50
|
+
Object.assign(clone, item);
|
|
51
|
+
onItemChanged(clone);
|
|
52
|
+
};
|
|
53
|
+
const handleImageIDChange = (imageID) => {
|
|
54
|
+
item.imageID = imageID;
|
|
55
|
+
const clone = new DataListItemDescriptor();
|
|
56
|
+
Object.assign(clone, item);
|
|
57
|
+
onItemChanged(clone);
|
|
58
|
+
};
|
|
59
|
+
return (_jsxs(FieldsContainer, { children: [_jsx(TMTextBox, { label: SDKUI_Localizator.Value, value: item.value, isModifiedWhen: (item.value || '') != (originalItem?.value || ''), validationItems: validationItems.filter(o => o.PropertyName === "value"), readOnly: readOnly, onValueChanged: handleValueChange }), _jsx(TMLocalizedTextBox, { label: SDKUI_Localizator.Description, value: item.name, isModifiedWhen: (item.name || '') != (originalItem?.name || ''), validationItems: validationItems.filter(o => o.PropertyName === "itemName"), readOnly: readOnly, value_IT: item.namesLoc?.it_IT, value_EN: item.namesLoc?.en_US, value_FR: item.namesLoc?.fr_FR, value_PT: item.namesLoc?.pt_PT, value_ES: item.namesLoc?.es_ES, value_DE: item.namesLoc?.de_DE, onValueChanged: handleLocalizedNameChange }), _jsx(TMImageIDChooser, { elementStyle: { marginBottom: '10px' }, label: "Immagine", value: item.imageID, isModifiedWhen: (item.imageID || '') != (originalItem?.imageID || ''), validationItems: validationItems.filter(o => o.PropertyName === "imageID"), readOnly: readOnly, onValueChanged: handleImageIDChange })] }));
|
|
60
|
+
};
|
|
61
|
+
export default TMDataListItemFields;
|
|
@@ -4,6 +4,7 @@ interface TMDataListItemPickerProps {
|
|
|
4
4
|
dataListID: number | undefined;
|
|
5
5
|
selectedValue: string | undefined;
|
|
6
6
|
onItemSelect: (item: DataListItemDescriptor) => void;
|
|
7
|
+
allowEdit?: boolean;
|
|
7
8
|
}
|
|
8
9
|
declare const TMDataListItemPicker: React.FC<TMDataListItemPickerProps>;
|
|
9
10
|
export default TMDataListItemPicker;
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useState } from 'react';
|
|
3
3
|
import styled, { keyframes } from 'styled-components';
|
|
4
|
-
import { DataListCacheService, SDK_Localizator } from '@topconsultnpm/sdk-ts';
|
|
5
|
-
import { SDKUI_Localizator, TMImageLibrary } from '../../helper';
|
|
4
|
+
import { DataListCacheService, SDK_Localizator, ObjectClasses, JobTypes } from '@topconsultnpm/sdk-ts';
|
|
5
|
+
import { IconDelete, SDKUI_Localizator, TMImageLibrary } from '../../helper';
|
|
6
|
+
import { PlatformObjectService } from '../../services/platform_services';
|
|
7
|
+
import TMDataListItemEditor from './TMDataListItemEditor';
|
|
8
|
+
import { ButtonNames, TMMessageBoxManager } from '../base/TMPopUp';
|
|
9
|
+
import TMSpinner from '../base/TMSpinner';
|
|
6
10
|
const PickerContainer = styled.div `
|
|
7
11
|
display: flex;
|
|
8
12
|
flex-direction: column;
|
|
@@ -30,6 +34,11 @@ const slideIn = keyframes `
|
|
|
30
34
|
transform: translateX(0);
|
|
31
35
|
}
|
|
32
36
|
`;
|
|
37
|
+
const ItemWrapper = styled.div `
|
|
38
|
+
position: relative;
|
|
39
|
+
display: flex;
|
|
40
|
+
flex-shrink: 0;
|
|
41
|
+
`;
|
|
33
42
|
const StatusItem = styled.div `
|
|
34
43
|
display: flex;
|
|
35
44
|
flex-direction: column;
|
|
@@ -58,39 +67,190 @@ const StatusItem = styled.div `
|
|
|
58
67
|
background-color: #e6f2ff;
|
|
59
68
|
`}
|
|
60
69
|
`;
|
|
70
|
+
const DeleteButton = styled.button `
|
|
71
|
+
position: absolute;
|
|
72
|
+
top: -8px;
|
|
73
|
+
right: -8px;
|
|
74
|
+
width: 24px;
|
|
75
|
+
height: 24px;
|
|
76
|
+
border-radius: 50%;
|
|
77
|
+
background-color: #dc3545;
|
|
78
|
+
color: white;
|
|
79
|
+
border: 2px solid white;
|
|
80
|
+
cursor: pointer;
|
|
81
|
+
display: flex;
|
|
82
|
+
align-items: center;
|
|
83
|
+
justify-content: center;
|
|
84
|
+
font-size: 14px;
|
|
85
|
+
font-weight: bold;
|
|
86
|
+
opacity: 0;
|
|
87
|
+
transition: all 0.2s ease-in-out;
|
|
88
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
|
|
89
|
+
z-index: 10;
|
|
90
|
+
|
|
91
|
+
${ItemWrapper}:hover & {
|
|
92
|
+
opacity: 1;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
&:hover {
|
|
96
|
+
background-color: #c82333;
|
|
97
|
+
transform: scale(1.1);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
&:active {
|
|
101
|
+
transform: scale(0.95);
|
|
102
|
+
}
|
|
103
|
+
`;
|
|
104
|
+
const AddButton = styled.div `
|
|
105
|
+
display: flex;
|
|
106
|
+
flex-direction: column;
|
|
107
|
+
align-items: center;
|
|
108
|
+
justify-content: center;
|
|
109
|
+
cursor: pointer;
|
|
110
|
+
text-align: center;
|
|
111
|
+
font-size: 0.8em;
|
|
112
|
+
padding: 5px 10px;
|
|
113
|
+
border-radius: 8px;
|
|
114
|
+
border: 2px dashed #007bff;
|
|
115
|
+
background-color: #f8f9fa;
|
|
116
|
+
transition: all 0.2s ease-in-out;
|
|
117
|
+
flex-shrink: 0;
|
|
118
|
+
min-width: 80px;
|
|
119
|
+
color: #007bff;
|
|
120
|
+
font-weight: 500;
|
|
121
|
+
|
|
122
|
+
&:hover {
|
|
123
|
+
background-color: #e6f2ff;
|
|
124
|
+
border-color: #0056b3;
|
|
125
|
+
}
|
|
126
|
+
`;
|
|
61
127
|
const Label = styled.div `
|
|
62
128
|
font-weight: bold;
|
|
63
129
|
margin-bottom: 5px;
|
|
64
130
|
`;
|
|
65
|
-
const TMDataListItemPicker = ({ dataListID, selectedValue, onItemSelect }) => {
|
|
131
|
+
const TMDataListItemPicker = ({ dataListID, selectedValue, onItemSelect, allowEdit = false }) => {
|
|
66
132
|
const [dataList, setDataList] = useState(undefined);
|
|
133
|
+
const [items, setItems] = useState([]);
|
|
67
134
|
const [loading, setLoading] = useState(true);
|
|
68
135
|
const [error, setError] = useState(undefined);
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
136
|
+
const [showEditDialog, setShowEditDialog] = useState(false);
|
|
137
|
+
const [editingItem, setEditingItem] = useState(undefined);
|
|
138
|
+
const [isCreating, setIsCreating] = useState(false);
|
|
139
|
+
const loadDataList = async (showLoading = true) => {
|
|
140
|
+
if (!dataListID)
|
|
141
|
+
return;
|
|
142
|
+
try {
|
|
143
|
+
if (showLoading)
|
|
144
|
+
setLoading(true);
|
|
72
145
|
setError(undefined);
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
146
|
+
const dl = await PlatformObjectService.retrieveAdminAsync(ObjectClasses.DataList, JobTypes.None, dataListID);
|
|
147
|
+
setDataList(dl);
|
|
148
|
+
setItems(dl?.items ?? []);
|
|
149
|
+
}
|
|
150
|
+
catch (err) {
|
|
151
|
+
console.error("Failed to fetch data list:", err);
|
|
152
|
+
setError("Failed to load data list.");
|
|
153
|
+
}
|
|
154
|
+
finally {
|
|
155
|
+
if (showLoading)
|
|
81
156
|
setLoading(false);
|
|
82
|
-
});
|
|
83
157
|
}
|
|
158
|
+
};
|
|
159
|
+
useEffect(() => {
|
|
160
|
+
loadDataList(true);
|
|
84
161
|
}, [dataListID]);
|
|
162
|
+
const handleCreateNew = () => {
|
|
163
|
+
setIsCreating(true);
|
|
164
|
+
setEditingItem(undefined);
|
|
165
|
+
setShowEditDialog(true);
|
|
166
|
+
};
|
|
167
|
+
const handleEdit = (item) => {
|
|
168
|
+
setIsCreating(false);
|
|
169
|
+
setEditingItem(item);
|
|
170
|
+
setShowEditDialog(true);
|
|
171
|
+
};
|
|
172
|
+
const handleSave = async (newItem) => {
|
|
173
|
+
if (!dataList || !dataListID)
|
|
174
|
+
return;
|
|
175
|
+
try {
|
|
176
|
+
let updatedItems;
|
|
177
|
+
TMSpinner.show({ description: 'Salvataggio in corso...' });
|
|
178
|
+
if (isCreating) {
|
|
179
|
+
// Aggiungi nuovo item
|
|
180
|
+
updatedItems = [...items, newItem];
|
|
181
|
+
}
|
|
182
|
+
else if (editingItem) {
|
|
183
|
+
// Modifica item esistente
|
|
184
|
+
updatedItems = items.map(item => item.value === editingItem.value ? newItem : item);
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
// Modifica direttamente la dataList invece di usare spread operator
|
|
190
|
+
dataList.items = updatedItems;
|
|
191
|
+
// Salva sul server usando l'API corretta
|
|
192
|
+
await PlatformObjectService.updateAsync(ObjectClasses.DataList, JobTypes.None, dataList);
|
|
193
|
+
// Invalida la cache per garantire la sincronizzazione
|
|
194
|
+
DataListCacheService.Remove();
|
|
195
|
+
// Ricarica i dati dalla cache (che richiamerà il server)
|
|
196
|
+
await loadDataList(false);
|
|
197
|
+
handleCloseDialog();
|
|
198
|
+
}
|
|
199
|
+
catch (error) {
|
|
200
|
+
console.error('Failed to save DataList item:', error);
|
|
201
|
+
setError('Failed to save changes.');
|
|
202
|
+
}
|
|
203
|
+
finally {
|
|
204
|
+
TMSpinner.hide();
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
const handleCloseDialog = () => {
|
|
208
|
+
setShowEditDialog(false);
|
|
209
|
+
setEditingItem(undefined);
|
|
210
|
+
setIsCreating(false);
|
|
211
|
+
};
|
|
212
|
+
const handleDelete = async (item, event) => {
|
|
213
|
+
event.stopPropagation(); // Previeni la selezione dell'item quando si clicca delete
|
|
214
|
+
const confirmMessage = SDKUI_Localizator.Delete_ConfirmFor1.replaceParams(item.name);
|
|
215
|
+
TMMessageBoxManager.show({
|
|
216
|
+
title: SDKUI_Localizator.Delete, message: confirmMessage, buttons: [ButtonNames.YES, ButtonNames.NO],
|
|
217
|
+
onButtonClick: async (e) => {
|
|
218
|
+
if (e !== ButtonNames.YES)
|
|
219
|
+
return;
|
|
220
|
+
if (!dataList || !dataListID)
|
|
221
|
+
return;
|
|
222
|
+
try {
|
|
223
|
+
TMSpinner.show({ description: 'Eliminazione in corso...' });
|
|
224
|
+
// Rimuovi l'item dalla lista
|
|
225
|
+
const updatedItems = items.filter(i => i.value !== item.value);
|
|
226
|
+
// Modifica direttamente la dataList
|
|
227
|
+
dataList.items = updatedItems;
|
|
228
|
+
// Salva sul server usando l'API corretta
|
|
229
|
+
await PlatformObjectService.updateAsync(ObjectClasses.DataList, JobTypes.None, dataList);
|
|
230
|
+
// Invalida la cache per garantire la sincronizzazione
|
|
231
|
+
DataListCacheService.Remove();
|
|
232
|
+
// Ricarica i dati dalla cache (che richiamerà il server)
|
|
233
|
+
await loadDataList(false);
|
|
234
|
+
}
|
|
235
|
+
catch (error) {
|
|
236
|
+
console.error('Failed to delete DataList item:', error);
|
|
237
|
+
setError('Failed to delete item.');
|
|
238
|
+
}
|
|
239
|
+
finally {
|
|
240
|
+
TMSpinner.hide();
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
};
|
|
85
245
|
if (loading) {
|
|
86
246
|
return _jsxs("p", { children: [SDKUI_Localizator.Loading, "..."] });
|
|
87
247
|
}
|
|
88
248
|
if (error) {
|
|
89
249
|
return _jsx("p", { children: error });
|
|
90
250
|
}
|
|
91
|
-
if (!dataList || !
|
|
92
|
-
return _jsx("p", { children: SDKUI_Localizator.NoDataToDisplay });
|
|
251
|
+
if (!dataList || !items || items.length === 0) {
|
|
252
|
+
return (_jsxs(PickerContainer, { children: [_jsx(Label, { children: dataList ? `${SDK_Localizator.DataList}: ${dataList.name}` : SDKUI_Localizator.NoDataToDisplay }), _jsx(ItemsContainer, { children: allowEdit && (_jsxs(AddButton, { onClick: handleCreateNew, children: ["+ ", SDKUI_Localizator.NewMale] })) }), !allowEdit && _jsx("p", { children: SDKUI_Localizator.NoDataToDisplay })] }));
|
|
93
253
|
}
|
|
94
|
-
return (_jsxs(PickerContainer, { children: [_jsx(Label, { children: `${SDK_Localizator.DataList}: ${dataList.name}` }),
|
|
254
|
+
return (_jsxs(PickerContainer, { children: [_jsx(Label, { children: `${SDK_Localizator.DataList}: ${dataList.name}` }), _jsxs(ItemsContainer, { children: [items.map((item, index) => (_jsxs(ItemWrapper, { children: [_jsxs(StatusItem, { "$isSelected": item.value === selectedValue, "$index": index, onClick: () => onItemSelect(item), onDoubleClick: () => allowEdit && handleEdit(item), children: [item.imageID && _jsx(TMImageLibrary, { imageID: item.imageID }), _jsx("span", { children: item.name })] }), allowEdit && (_jsx(DeleteButton, { onClick: (e) => handleDelete(item, e), title: SDKUI_Localizator.Delete || 'Delete', "aria-label": "Delete item", children: _jsx(IconDelete, { fontSize: 16 }) }))] }, item.value))), allowEdit && (_jsxs(AddButton, { onClick: handleCreateNew, children: ["+ ", SDKUI_Localizator.NewMale] }))] }), _jsx(TMDataListItemEditor, { isOpen: showEditDialog, item: editingItem, isCreating: isCreating, onClose: handleCloseDialog, onSave: handleSave })] }));
|
|
95
255
|
};
|
|
96
256
|
export default TMDataListItemPicker;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { ITMChooserProps, ITMChooserFormProps } from '../../ts';
|
|
3
|
+
interface ITMImageIDChooserProps extends ITMChooserProps {
|
|
4
|
+
/** Contiene i values selezionati -> attualmente sempre e solo 1 elemento */
|
|
5
|
+
value?: string;
|
|
6
|
+
readOnly?: boolean;
|
|
7
|
+
}
|
|
8
|
+
declare const TMImageIDChooser: React.FunctionComponent<ITMImageIDChooserProps>;
|
|
9
|
+
export default TMImageIDChooser;
|
|
10
|
+
interface ITMImageIDChooserFormProps extends ITMChooserFormProps<Image_Wrap> {
|
|
11
|
+
}
|
|
12
|
+
declare class Image_Wrap {
|
|
13
|
+
id: number;
|
|
14
|
+
imageID?: string;
|
|
15
|
+
}
|
|
16
|
+
export declare const TMImageIDChooserForm: React.FunctionComponent<ITMImageIDChooserFormProps>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo, useState } from 'react';
|
|
3
|
+
import { TMImageLibrary, IconSearch } from '../../helper';
|
|
4
|
+
import { StyledDivHorizontal } from '../base/Styled';
|
|
5
|
+
import TMSummary from '../editors/TMSummary';
|
|
6
|
+
import TMChooserForm from '../forms/TMChooserForm';
|
|
7
|
+
import { ImageIDList } from '../viewers/TMTidViewer';
|
|
8
|
+
const TMImageIDChooser = ({ backgroundColor, elementStyle, value, isModifiedWhen, placeHolder, validationItems = [], readOnly, onValueChanged }) => {
|
|
9
|
+
const [showChooser, setShowChooser] = useState(false);
|
|
10
|
+
const renderTemplate = () => {
|
|
11
|
+
return (_jsx(StyledDivHorizontal, { style: { minWidth: '125px' }, children: placeHolder && (!value || value == '')
|
|
12
|
+
? _jsx("p", { children: placeHolder })
|
|
13
|
+
: _jsx(TMImageLibrary, { imageID: value }) }));
|
|
14
|
+
};
|
|
15
|
+
return (_jsxs(_Fragment, { children: [_jsx(TMSummary, { backgroundColor: backgroundColor, showClearButton: true, iconEditButton: _jsx(IconSearch, {}), onEditorClick: () => setShowChooser(true), onClearClick: () => onValueChanged?.(''), elementStyle: elementStyle, isModifiedWhen: isModifiedWhen, label: "Immagine", hasValue: value != undefined && value?.length > 0, template: renderTemplate(), validationItems: validationItems,
|
|
16
|
+
// openEditorOnSummaryClick={!readOnly}
|
|
17
|
+
readOnly: readOnly, disabled: readOnly }), showChooser &&
|
|
18
|
+
_jsx(TMImageIDChooserForm, { selectedIDs: [value], onClose: () => setShowChooser(false), onChoose: (img) => { onValueChanged?.(img); } })] }));
|
|
19
|
+
};
|
|
20
|
+
export default TMImageIDChooser;
|
|
21
|
+
class Image_Wrap {
|
|
22
|
+
constructor() {
|
|
23
|
+
this.id = 0;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export const TMImageIDChooserForm = (props) => {
|
|
27
|
+
const getItems = async () => {
|
|
28
|
+
let datasource = [];
|
|
29
|
+
let i = 0;
|
|
30
|
+
for (let img of Object.values(ImageIDList))
|
|
31
|
+
datasource.push({ id: i, imageID: img });
|
|
32
|
+
return datasource;
|
|
33
|
+
};
|
|
34
|
+
const cellRenderImageListItemIcon = (data) => {
|
|
35
|
+
let imageID = data.row.data;
|
|
36
|
+
return _jsx(TMImageLibrary, { imageID: imageID.imageID });
|
|
37
|
+
};
|
|
38
|
+
const cellRenderName = (data) => {
|
|
39
|
+
let imageID = data.row.data;
|
|
40
|
+
return _jsxs("p", { children: [imageID.imageID, " "] });
|
|
41
|
+
};
|
|
42
|
+
const dataColumns = useMemo(() => {
|
|
43
|
+
return [
|
|
44
|
+
{ name: 'icon', dataField: 'imageID', caption: '', width: '40px', allowResizing: false, cellRender: cellRenderImageListItemIcon },
|
|
45
|
+
{ name: 'name', dataField: 'imageID', caption: '', cellRender: cellRenderName }
|
|
46
|
+
];
|
|
47
|
+
}, []);
|
|
48
|
+
return (_jsx(TMChooserForm, { title: "Immagini", hasShowId: false, allowMultipleSelection: false, height: props.height, width: '200px', showDefaultColumns: false, keyName: 'imageID', columns: dataColumns, selectedIDs: props.selectedIDs, dataSource: props.dataSource, getItems: getItems, onClose: props.onClose, onChoose: (IDs) => {
|
|
49
|
+
if (IDs && IDs.length > 0) {
|
|
50
|
+
props.onChoose?.(IDs[0]);
|
|
51
|
+
}
|
|
52
|
+
} }));
|
|
53
|
+
};
|
|
@@ -15,6 +15,7 @@ interface TMLocalizedTextBoxProps {
|
|
|
15
15
|
validationItems?: ValidationItem[];
|
|
16
16
|
qd?: QueryDescriptor;
|
|
17
17
|
tid?: number;
|
|
18
|
+
readOnly?: boolean;
|
|
18
19
|
onValueChanged: (lang: CultureIDs, value: string) => void;
|
|
19
20
|
}
|
|
20
21
|
declare const TMLocalizedTextBox: React.FC<TMLocalizedTextBoxProps>;
|
|
@@ -40,7 +40,7 @@ const Popover = styled.div `
|
|
|
40
40
|
border-radius: 4px;
|
|
41
41
|
background-color: #fff;
|
|
42
42
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
43
|
-
z-index:
|
|
43
|
+
z-index: 2000000001;
|
|
44
44
|
padding: 8px;
|
|
45
45
|
`;
|
|
46
46
|
const Badge = styled.span `
|
|
@@ -71,7 +71,7 @@ export const getCultureIDImg = (cultureID) => {
|
|
|
71
71
|
default: return it;
|
|
72
72
|
}
|
|
73
73
|
};
|
|
74
|
-
const TMLocalizedTextBox = ({ label, value, value_IT, value_EN, value_FR, value_PT, value_ES, value_DE, isModifiedWhen, validationItems, qd, tid, onValueChanged, }) => {
|
|
74
|
+
const TMLocalizedTextBox = ({ label, value, value_IT, value_EN, value_FR, value_PT, value_ES, value_DE, isModifiedWhen, validationItems, qd, tid, readOnly, onValueChanged, }) => {
|
|
75
75
|
const [isPopoverVisible, setIsPopoverVisible] = useState(false);
|
|
76
76
|
const containerRef = useRef(null);
|
|
77
77
|
const popoverRef = useRef(null);
|
|
@@ -122,6 +122,6 @@ const TMLocalizedTextBox = ({ label, value, value_IT, value_EN, value_FR, value_
|
|
|
122
122
|
icon: (_jsxs(IconContainer, { children: [_jsx(IconLanguage, {}), localizedCount > 0 && _jsx(Badge, { children: localizedCount })] })),
|
|
123
123
|
onClick: handleTogglePopover,
|
|
124
124
|
};
|
|
125
|
-
return (_jsxs(LocalizedContainer, { ref: containerRef, children: [(qd || tid) ? (_jsx(TMMetadataTextBox, { type: "text", label: label, value: value, isModifiedWhen: isModifiedWhen, buttons: [localizationButton], validationItems: validationItems, qd: qd, tid: tid, onValueChanged: (e) => onValueChanged(CultureIDs.None, e.target.value) })) : (_jsx(TMTextBox, { type: "text", label: label, value: value, isModifiedWhen: isModifiedWhen, buttons: [localizationButton], validationItems: validationItems, onValueChanged: (e) => onValueChanged(CultureIDs.None, e.target.value) })), isPopoverVisible && (_jsx(Portal, { popoverRef: popoverRef, children: _jsx(Popover, { ref: popoverRef, "$isVisible": isPopoverVisible, "$top": popoverPosition.top, "$left": popoverPosition.left, "$width": popoverPosition.width, onMouseDown: (e) => e.stopPropagation(), children: languages.map((lang) => ((qd || tid) ? (_jsx(TMMetadataTextBox, { label: `${lang.label}`, showClearButton: true, icon: _jsx("img", { src: getCultureIDImg(lang.code), alt: "Lang", width: 18, height: 18 }), value: lang.value || '', qd: qd, tid: tid, onValueChanged: (e) => onValueChanged(lang.code, e.target.value) }, lang.code)) : (_jsx(TMTextBox, { label: `${lang.label}`, showClearButton: true, icon: _jsx("img", { src: getCultureIDImg(lang.code), alt: "Lang", width: 18, height: 18 }), value: lang.value || '', onValueChanged: (e) => onValueChanged(lang.code, e.target.value) }, lang.code)))) }) }))] }));
|
|
125
|
+
return (_jsxs(LocalizedContainer, { ref: containerRef, children: [(qd || tid) ? (_jsx(TMMetadataTextBox, { type: "text", label: label, value: value, isModifiedWhen: isModifiedWhen, buttons: [localizationButton], validationItems: validationItems, qd: qd, tid: tid, readOnly: readOnly, onValueChanged: (e) => onValueChanged(CultureIDs.None, e.target.value) })) : (_jsx(TMTextBox, { type: "text", label: label, value: value, isModifiedWhen: isModifiedWhen, buttons: [localizationButton], validationItems: validationItems, readOnly: readOnly, onValueChanged: (e) => onValueChanged(CultureIDs.None, e.target.value) })), isPopoverVisible && (_jsx(Portal, { popoverRef: popoverRef, children: _jsx(Popover, { ref: popoverRef, "$isVisible": isPopoverVisible, "$top": popoverPosition.top, "$left": popoverPosition.left, "$width": popoverPosition.width, onMouseDown: (e) => e.stopPropagation(), children: languages.map((lang) => ((qd || tid) ? (_jsx(TMMetadataTextBox, { label: `${lang.label}`, showClearButton: true, icon: _jsx("img", { src: getCultureIDImg(lang.code), alt: "Lang", width: 18, height: 18 }), value: lang.value || '', qd: qd, tid: tid, readOnly: readOnly, onValueChanged: (e) => onValueChanged(lang.code, e.target.value) }, lang.code)) : (_jsx(TMTextBox, { label: `${lang.label}`, showClearButton: true, icon: _jsx("img", { src: getCultureIDImg(lang.code), alt: "Lang", width: 18, height: 18 }), value: lang.value || '', readOnly: readOnly, onValueChanged: (e) => onValueChanged(lang.code, e.target.value) }, lang.code)))) }) }))] }));
|
|
126
126
|
};
|
|
127
127
|
export default TMLocalizedTextBox;
|
|
@@ -392,7 +392,7 @@ const DiagramItemForm = ({ itemToEdit, wf, onClose, onApply }) => {
|
|
|
392
392
|
};
|
|
393
393
|
// Function to render Status-specific fields
|
|
394
394
|
const renderStatusFields = () => {
|
|
395
|
-
return (_jsx(TMDataListItemPicker, { dataListID: wf?.MStatusDLID, selectedValue: localItem.StatusValue, onItemSelect: handleStatusChange }));
|
|
395
|
+
return (_jsx(TMDataListItemPicker, { dataListID: wf?.MStatusDLID, selectedValue: localItem.StatusValue, onItemSelect: handleStatusChange, allowEdit: true }));
|
|
396
396
|
};
|
|
397
397
|
const renderAppFields = () => {
|
|
398
398
|
return (_jsxs(_Fragment, { children: [_jsx(TMDropDown, { label: SDKUI_Localizator.WorkflowAppType, dataSource: APP_TYPES_DATASOURCE, value: localItem.AppType, isModifiedWhen: localItem.AppType !== localItemOrig.AppType, validationItems: validationItems.filter(v => v.PropertyName === DiagramItemProps.AppType), onValueChanged: (e) => { handleAppTypeChange(e.target.value); } }), localItem.AppType === WFAppTypes.EXE
|
|
@@ -43,6 +43,8 @@ export { editorColorManager } from './editors/TMEditorStyled';
|
|
|
43
43
|
export { default as TMLocalizedTextBox } from './editors/TMLocalizedTextBox';
|
|
44
44
|
export * from './choosers/TMCultureIDPicker';
|
|
45
45
|
export * from './choosers/TMDataListItemChooser';
|
|
46
|
+
export * from './choosers/TMDataListItemFields';
|
|
47
|
+
export * from './choosers/TMImageIDChooser';
|
|
46
48
|
export * from './choosers/TMDistinctValues';
|
|
47
49
|
export * from './choosers/TMDiskChooser';
|
|
48
50
|
export * from './choosers/TMDynDataListItemChooser';
|
package/lib/components/index.js
CHANGED
|
@@ -46,6 +46,8 @@ export { default as TMLocalizedTextBox } from './editors/TMLocalizedTextBox';
|
|
|
46
46
|
// chooserss
|
|
47
47
|
export * from './choosers/TMCultureIDPicker';
|
|
48
48
|
export * from './choosers/TMDataListItemChooser';
|
|
49
|
+
export * from './choosers/TMDataListItemFields';
|
|
50
|
+
export * from './choosers/TMImageIDChooser';
|
|
49
51
|
export * from './choosers/TMDistinctValues';
|
|
50
52
|
export * from './choosers/TMDiskChooser';
|
|
51
53
|
export * from './choosers/TMDynDataListItemChooser';
|
|
@@ -26,6 +26,9 @@ export declare class UserSettings {
|
|
|
26
26
|
static LoadSettings(userID: number | undefined, archiveID: string | undefined): UserSettings;
|
|
27
27
|
/** Save settings to local storage or other sources */
|
|
28
28
|
static SaveSettings(settings: UserSettings): void;
|
|
29
|
+
/** Save default settings for properties that don't exist in localStorage yet.*/
|
|
30
|
+
static SaveDefaultSettings(//nosonar
|
|
31
|
+
userID: number | undefined, archiveID: string | undefined, defaultProperties?: string[]): void;
|
|
29
32
|
}
|
|
30
33
|
export declare class DataGridSettings {
|
|
31
34
|
showColumnLines: number;
|
|
@@ -59,6 +59,59 @@ export class UserSettings {
|
|
|
59
59
|
}));
|
|
60
60
|
LocalStorageService.setItem(`userSettings_${settings.archiveID}_${settings.userID}`, JSON.stringify(filteredSettings));
|
|
61
61
|
}
|
|
62
|
+
/** Save default settings for properties that don't exist in localStorage yet.*/
|
|
63
|
+
static SaveDefaultSettings(//nosonar
|
|
64
|
+
userID, archiveID, defaultProperties = ['devSettings.betaFeatures']) {
|
|
65
|
+
if (!userID || !archiveID) {
|
|
66
|
+
throw new Error('User ID and Archive ID are required to save default settings.');
|
|
67
|
+
}
|
|
68
|
+
// Load existing settings from local storage
|
|
69
|
+
const loadedSettings = LocalStorageService.getItem(`userSettings_${archiveID}_${userID}`);
|
|
70
|
+
// If no settings exist yet, save all defaults
|
|
71
|
+
if (!loadedSettings) {
|
|
72
|
+
const defaultSettings = new UserSettings(true);
|
|
73
|
+
const settingsToSave = {
|
|
74
|
+
userID: userID,
|
|
75
|
+
archiveID: archiveID,
|
|
76
|
+
devSettings: defaultSettings.devSettings
|
|
77
|
+
};
|
|
78
|
+
LocalStorageService.setItem(`userSettings_${archiveID}_${userID}`, JSON.stringify(settingsToSave));
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
// Check if any of the default properties are missing
|
|
82
|
+
let needsUpdate = false;
|
|
83
|
+
const defaultSettings = new UserSettings(true);
|
|
84
|
+
for (const propertyPath of defaultProperties) {
|
|
85
|
+
const pathParts = propertyPath.split('.');
|
|
86
|
+
let current = loadedSettings;
|
|
87
|
+
let exists = true;
|
|
88
|
+
// Check if the property exists in loaded settings
|
|
89
|
+
for (const part of pathParts) {
|
|
90
|
+
if (current && typeof current === 'object' && part in current) {
|
|
91
|
+
current = current[part];
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
exists = false;
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// If property doesn't exist, we need to add it
|
|
99
|
+
if (!exists) {
|
|
100
|
+
needsUpdate = true;
|
|
101
|
+
// Add the missing property from defaults
|
|
102
|
+
const [topLevel] = pathParts;
|
|
103
|
+
if (!loadedSettings[topLevel]) {
|
|
104
|
+
loadedSettings[topLevel] = defaultSettings[topLevel];
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// Save updated settings if needed
|
|
109
|
+
if (needsUpdate) {
|
|
110
|
+
loadedSettings.userID = userID;
|
|
111
|
+
loadedSettings.archiveID = archiveID;
|
|
112
|
+
LocalStorageService.setItem(`userSettings_${archiveID}_${userID}`, JSON.stringify(loadedSettings));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
62
115
|
}
|
|
63
116
|
export class DataGridSettings {
|
|
64
117
|
constructor() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@topconsultnpm/sdkui-react",
|
|
3
|
-
"version": "6.20.0-dev2.
|
|
3
|
+
"version": "6.20.0-dev2.32",
|
|
4
4
|
"description": "",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"lib"
|
|
41
41
|
],
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@topconsultnpm/sdk-ts": "6.20.0-dev2.
|
|
43
|
+
"@topconsultnpm/sdk-ts": "6.20.0-dev2.9",
|
|
44
44
|
"buffer": "^6.0.3",
|
|
45
45
|
"devextreme": "25.2.4",
|
|
46
46
|
"devextreme-react": "25.2.4",
|