@topconsultnpm/sdkui-react 6.22.0-dev2.17 → 6.22.0-dev2.18
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/TMEditorBase.d.ts +2 -0
- package/lib/components/base/TMTooltip.d.ts +2 -1
- package/lib/components/base/TMTooltip.js +23 -6
- package/lib/components/choosers/TMDcmtTypeChooser.js +4 -3
- package/lib/components/choosers/TMDistinctValues.d.ts +9 -7
- package/lib/components/choosers/TMDistinctValues.js +147 -195
- package/lib/components/choosers/TMMetadataChooser.js +1 -1
- package/lib/components/choosers/TMQuickSearchInfo.d.ts +12 -0
- package/lib/components/choosers/TMQuickSearchInfo.js +279 -0
- package/lib/components/choosers/TMQuickSearchSettingsForm.d.ts +16 -0
- package/lib/components/choosers/TMQuickSearchSettingsForm.js +94 -0
- package/lib/components/choosers/TMSelectedValuesSummary.d.ts +13 -0
- package/lib/components/choosers/TMSelectedValuesSummary.js +91 -0
- package/lib/components/editors/TMDropDown.d.ts +3 -0
- package/lib/components/editors/TMDropDown.js +197 -5
- package/lib/components/features/search/TMSearchResult.js +55 -4
- package/lib/components/viewers/TMMidViewer.js +4 -1
- package/lib/components/viewers/TMTidViewer.js +25 -11
- package/lib/helper/SDKUI_Globals.d.ts +20 -0
- package/lib/helper/SDKUI_Globals.js +44 -0
- package/lib/helper/SDKUI_Localizator.d.ts +1 -0
- package/lib/helper/SDKUI_Localizator.js +10 -0
- package/lib/helper/dataGridSearchHelper.d.ts +28 -0
- package/lib/helper/dataGridSearchHelper.js +51 -0
- package/lib/helper/index.d.ts +1 -0
- package/lib/helper/index.js +1 -0
- package/lib/helper/queryHelper.d.ts +2 -0
- package/lib/helper/queryHelper.js +3 -1
- package/lib/hooks/tmDistinctValuesGridHelper.d.ts +16 -0
- package/lib/hooks/tmDistinctValuesGridHelper.js +31 -0
- package/lib/hooks/useTMDistinctValuesMetadataDisplay.d.ts +18 -0
- package/lib/hooks/useTMDistinctValuesMetadataDisplay.js +103 -0
- package/lib/hooks/useTMDistinctValuesQuickSearch.d.ts +49 -0
- package/lib/hooks/useTMDistinctValuesQuickSearch.js +307 -0
- package/lib/hooks/useTMDistinctValuesSelection.d.ts +42 -0
- package/lib/hooks/useTMDistinctValuesSelection.js +103 -0
- package/lib/hooks/useTMDistinctValuesSource.d.ts +29 -0
- package/lib/hooks/useTMDistinctValuesSource.js +105 -0
- package/lib/services/platform_services.d.ts +1 -1
- package/package.json +1 -1
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useState } from "react";
|
|
3
|
-
import
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef, useState } from "react";
|
|
3
|
+
import { createPortal } from "react-dom";
|
|
4
|
+
import styled, { css } from "styled-components";
|
|
4
5
|
import { FontSize, TMColors } from '../../utils/theme';
|
|
5
6
|
import { StyledEditorContainer, StyledEditorIcon, StyledEditorLabel, TMEditorsDefaultBorderRadius, editorColorManager } from "./TMEditorStyled";
|
|
6
7
|
import TMLayoutContainer, { TMLayoutItem } from "../base/TMLayout";
|
|
7
8
|
import TMVilViewer from "../base/TMVilViewer";
|
|
8
9
|
import TMTooltip from "../base/TMTooltip";
|
|
10
|
+
import { SDKUI_Localizator } from "../../helper";
|
|
9
11
|
const StyledDropDownWrapper = styled.div `
|
|
10
12
|
position: relative;
|
|
11
13
|
width: ${props => props.$width};
|
|
@@ -54,9 +56,199 @@ const StyledDropDownEditorButton = styled.div `
|
|
|
54
56
|
align-items: center;
|
|
55
57
|
cursor: pointer;
|
|
56
58
|
`;
|
|
57
|
-
|
|
59
|
+
// Variante filtrabile: un input di testo con la stessa resa grafica della select nativa
|
|
60
|
+
const StyledSearchInput = styled.input `
|
|
61
|
+
width: 100%;
|
|
62
|
+
padding: 5px 35px 3px 9px;
|
|
63
|
+
border: 1px solid;
|
|
64
|
+
border-color: ${props => props.$isModified ? TMColors.isModified : TMColors.border_normal};
|
|
65
|
+
border-bottom: ${props => (props.$vil.length > 0) ? '3px' : '1px'} solid;
|
|
66
|
+
border-bottom-color: ${props => (props.$vil.length === 0) ? !props.$isModified ? TMColors.border_normal : TMColors.isModified : editorColorManager(props.$vil)};
|
|
67
|
+
color: ${props => !props.$disabled ? (props.$vil.length === 0 ? (!props.$isModified ? TMColors.text_normal : TMColors.isModified) : editorColorManager(props.$vil)) : TMColors.disabled};
|
|
68
|
+
border-radius: ${props => props.$borderRadius ?? TMEditorsDefaultBorderRadius};
|
|
69
|
+
font-size: ${props => props.$fontSize};
|
|
70
|
+
background-color: ${props => props.$disabled ? '#F5F5F5' : 'white'};
|
|
71
|
+
text-overflow: ellipsis;
|
|
72
|
+
|
|
73
|
+
&:focus {
|
|
74
|
+
outline: none;
|
|
75
|
+
border: ${props => props.$isModified ? `1px solid ${TMColors.isModified}` : '1px solid rgb(180,180,180)'};
|
|
76
|
+
border-bottom: 2px solid;
|
|
77
|
+
border-bottom-color: ${props => (props.$vil.length === 0) ? !props.$isModified ? TMColors.primary : TMColors.isModified : editorColorManager(props.$vil)};
|
|
78
|
+
}
|
|
79
|
+
`;
|
|
80
|
+
const OPTIONS_PANEL_MAX_HEIGHT = 220;
|
|
81
|
+
const OPTIONS_PANEL_MIN_HEIGHT = 80;
|
|
82
|
+
const OPTIONS_PANEL_MARGIN = 4;
|
|
83
|
+
// L'elenco aperto con usePortal esce dal contenitore dell'editor: deve stare sopra tutto ciò che può trovarsi nella stessa pagina
|
|
84
|
+
const DROPDOWN_OPTIONS_PANEL_Z_INDEX = 100001;
|
|
85
|
+
const optionsPanelCss = css `
|
|
86
|
+
overflow-y: auto;
|
|
87
|
+
background-color: white;
|
|
88
|
+
border: 1px solid ${TMColors.border_normal};
|
|
89
|
+
border-radius: ${TMEditorsDefaultBorderRadius};
|
|
90
|
+
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.15);
|
|
91
|
+
`;
|
|
92
|
+
// Elenco ancorato all'editor: resta nel flusso del DOM e quindi soggetto al clipping dei contenitori con overflow
|
|
93
|
+
const StyledOptionsPanel = styled.div `
|
|
94
|
+
${optionsPanelCss}
|
|
95
|
+
position: absolute;
|
|
96
|
+
top: calc(100% + 2px);
|
|
97
|
+
left: 0;
|
|
98
|
+
right: 0;
|
|
99
|
+
z-index: 10001;
|
|
100
|
+
max-height: ${OPTIONS_PANEL_MAX_HEIGHT}px;
|
|
101
|
+
`;
|
|
102
|
+
// Variante usePortal: posizione calcolata rispetto alla finestra, così l'elenco non viene tagliato da nessun contenitore
|
|
103
|
+
const StyledPortalOptionsPanel = styled.div `
|
|
104
|
+
${optionsPanelCss}
|
|
105
|
+
position: fixed;
|
|
106
|
+
left: ${props => props.$left}px;
|
|
107
|
+
${props => props.$top !== undefined ? `top: ${props.$top}px;` : ''}
|
|
108
|
+
${props => props.$bottom !== undefined ? `bottom: ${props.$bottom}px;` : ''}
|
|
109
|
+
width: ${props => props.$width}px;
|
|
110
|
+
z-index: ${DROPDOWN_OPTIONS_PANEL_Z_INDEX};
|
|
111
|
+
max-height: ${props => props.$maxHeight}px;
|
|
112
|
+
`;
|
|
113
|
+
const StyledOption = styled.div `
|
|
114
|
+
padding: 5px 9px;
|
|
115
|
+
font-size: ${props => props.$fontSize};
|
|
116
|
+
cursor: pointer;
|
|
117
|
+
white-space: nowrap;
|
|
118
|
+
overflow: hidden;
|
|
119
|
+
text-overflow: ellipsis;
|
|
120
|
+
font-weight: ${props => props.$isSelected ? 600 : 'normal'};
|
|
121
|
+
background-color: ${props => props.$isHighlighted ? TMColors.primary_container : 'transparent'};
|
|
122
|
+
`;
|
|
123
|
+
const StyledNoOptions = styled.div `
|
|
124
|
+
padding: 5px 9px;
|
|
125
|
+
font-size: ${props => props.$fontSize};
|
|
126
|
+
color: ${TMColors.disabled};
|
|
127
|
+
`;
|
|
128
|
+
// L'elenco si apre sotto l'editor se c'è spazio, altrimenti sopra, e in ogni caso non esce dalla finestra
|
|
129
|
+
const calcOptionsPanelPosition = (rect) => {
|
|
130
|
+
const spaceBelow = window.innerHeight - rect.bottom - OPTIONS_PANEL_MARGIN;
|
|
131
|
+
const spaceAbove = rect.top - OPTIONS_PANEL_MARGIN;
|
|
132
|
+
const openUpwards = spaceBelow < OPTIONS_PANEL_MAX_HEIGHT && spaceAbove > spaceBelow;
|
|
133
|
+
const availableHeight = openUpwards ? spaceAbove : spaceBelow;
|
|
134
|
+
return {
|
|
135
|
+
left: Math.max(OPTIONS_PANEL_MARGIN, Math.min(rect.left, window.innerWidth - rect.width - OPTIONS_PANEL_MARGIN)),
|
|
136
|
+
top: openUpwards ? undefined : rect.bottom + OPTIONS_PANEL_MARGIN,
|
|
137
|
+
bottom: openUpwards ? window.innerHeight - rect.top + OPTIONS_PANEL_MARGIN : undefined,
|
|
138
|
+
width: rect.width,
|
|
139
|
+
maxHeight: Math.max(OPTIONS_PANEL_MIN_HEIGHT, Math.min(OPTIONS_PANEL_MAX_HEIGHT, availableHeight)),
|
|
140
|
+
};
|
|
141
|
+
};
|
|
142
|
+
const TMDropDown = ({ validationItems = [], disabled = false, elementStyle, isModifiedWhen, labelPosition = 'left', label, icon, width = '100%', fontSize = FontSize.defaultFontSize, dataSource, value, searchEnabled = false, usePortal = false, itemRender, onValueChanged, buttons = [], placeHolder, }) => {
|
|
58
143
|
const [isFocused, setIsFocused] = useState(false);
|
|
59
|
-
const
|
|
144
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
145
|
+
const [searchText, setSearchText] = useState('');
|
|
146
|
+
const [highlightedIndex, setHighlightedIndex] = useState(-1);
|
|
147
|
+
const [optionsPanelPosition, setOptionsPanelPosition] = useState();
|
|
148
|
+
const optionsPanelRef = useRef(null);
|
|
149
|
+
const dropDownWrapperRef = useRef(null);
|
|
150
|
+
const selectedItem = dataSource?.find(o => String(o.value) === String(value));
|
|
151
|
+
// A elenco chiuso l'input mostra la voce selezionata, ad elenco aperto il testo digitato
|
|
152
|
+
const filteredDataSource = (!isOpen || !searchText) ? (dataSource ?? []) : (dataSource ?? []).filter(o => String(o.display ?? '').toLocaleLowerCase().includes(searchText.toLocaleLowerCase()));
|
|
153
|
+
// Mantiene visibile la voce evidenziata durante la navigazione da tastiera
|
|
154
|
+
useEffect(() => {
|
|
155
|
+
if (!isOpen || highlightedIndex < 0)
|
|
156
|
+
return;
|
|
157
|
+
const option = optionsPanelRef.current?.children[highlightedIndex];
|
|
158
|
+
option?.scrollIntoView({ block: 'nearest' });
|
|
159
|
+
}, [isOpen, highlightedIndex]);
|
|
160
|
+
// Solo nel portal la posizione va ricalcolata: essendo fissa non segue lo scorrimento o il ridimensionamento del contenitore
|
|
161
|
+
useEffect(() => {
|
|
162
|
+
if (!isOpen || !usePortal)
|
|
163
|
+
return;
|
|
164
|
+
const updatePosition = () => updateOptionsPanelPosition();
|
|
165
|
+
window.addEventListener('scroll', updatePosition, true);
|
|
166
|
+
window.addEventListener('resize', updatePosition);
|
|
167
|
+
return () => {
|
|
168
|
+
window.removeEventListener('scroll', updatePosition, true);
|
|
169
|
+
window.removeEventListener('resize', updatePosition);
|
|
170
|
+
};
|
|
171
|
+
}, [isOpen, usePortal]);
|
|
172
|
+
const updateOptionsPanelPosition = () => {
|
|
173
|
+
const rect = dropDownWrapperRef.current?.getBoundingClientRect();
|
|
174
|
+
if (rect)
|
|
175
|
+
setOptionsPanelPosition(calcOptionsPanelPosition(rect));
|
|
176
|
+
};
|
|
177
|
+
const openOptions = () => {
|
|
178
|
+
if (disabled)
|
|
179
|
+
return;
|
|
180
|
+
if (usePortal)
|
|
181
|
+
updateOptionsPanelPosition();
|
|
182
|
+
setIsOpen(true);
|
|
183
|
+
setSearchText('');
|
|
184
|
+
setHighlightedIndex(dataSource?.findIndex(o => String(o.value) === String(value)) ?? -1);
|
|
185
|
+
};
|
|
186
|
+
const closeOptions = () => {
|
|
187
|
+
setIsOpen(false);
|
|
188
|
+
setSearchText('');
|
|
189
|
+
setHighlightedIndex(-1);
|
|
190
|
+
};
|
|
191
|
+
const selectItem = (item) => {
|
|
192
|
+
closeOptions();
|
|
193
|
+
// La select nativa notifica un ChangeEvent: qui viene ricostruito il solo dato letto dai consumatori (target.value)
|
|
194
|
+
onValueChanged?.({ target: { value: String(item.value) } });
|
|
195
|
+
};
|
|
196
|
+
const onSearchKeyDown = (e) => {
|
|
197
|
+
switch (e.key) {
|
|
198
|
+
case 'ArrowDown':
|
|
199
|
+
case 'ArrowUp':
|
|
200
|
+
e.preventDefault();
|
|
201
|
+
if (!isOpen) {
|
|
202
|
+
openOptions();
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
if (filteredDataSource.length <= 0)
|
|
206
|
+
return;
|
|
207
|
+
setHighlightedIndex(prev => {
|
|
208
|
+
const delta = e.key === 'ArrowDown' ? 1 : -1;
|
|
209
|
+
const next = prev + delta;
|
|
210
|
+
if (next < 0)
|
|
211
|
+
return filteredDataSource.length - 1;
|
|
212
|
+
if (next >= filteredDataSource.length)
|
|
213
|
+
return 0;
|
|
214
|
+
return next;
|
|
215
|
+
});
|
|
216
|
+
break;
|
|
217
|
+
case 'Enter': {
|
|
218
|
+
if (!isOpen)
|
|
219
|
+
return;
|
|
220
|
+
e.preventDefault();
|
|
221
|
+
const item = filteredDataSource[highlightedIndex] ?? filteredDataSource[0];
|
|
222
|
+
if (item)
|
|
223
|
+
selectItem(item);
|
|
224
|
+
break;
|
|
225
|
+
}
|
|
226
|
+
case 'Escape':
|
|
227
|
+
if (isOpen) {
|
|
228
|
+
e.preventDefault();
|
|
229
|
+
closeOptions();
|
|
230
|
+
}
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
const renderOptions = () => (filteredDataSource.length > 0
|
|
235
|
+
? filteredDataSource.map((data, index) => (_jsx(StyledOption, { "$fontSize": fontSize, "$isSelected": String(data.value) === String(value), "$isHighlighted": index === highlightedIndex, onMouseEnter: () => setHighlightedIndex(index), onClick: () => selectItem(data), children: itemRender ? itemRender(data) : data.display }, index)))
|
|
236
|
+
: _jsx(StyledNoOptions, { "$fontSize": fontSize, children: SDKUI_Localizator.NoDataToDisplay }));
|
|
237
|
+
// onMouseDown va bloccato: senza preventDefault il blur dell'input chiuderebbe l'elenco prima del click
|
|
238
|
+
const renderOptionsPanel = () => {
|
|
239
|
+
if (!isOpen)
|
|
240
|
+
return null;
|
|
241
|
+
if (!usePortal) {
|
|
242
|
+
return (_jsx(StyledOptionsPanel, { ref: optionsPanelRef, onMouseDown: (e) => e.preventDefault(), children: renderOptions() }));
|
|
243
|
+
}
|
|
244
|
+
if (!optionsPanelPosition)
|
|
245
|
+
return null;
|
|
246
|
+
return createPortal(_jsx(StyledPortalOptionsPanel, { ref: optionsPanelRef, "$left": optionsPanelPosition.left, "$top": optionsPanelPosition.top, "$bottom": optionsPanelPosition.bottom, "$width": optionsPanelPosition.width, "$maxHeight": optionsPanelPosition.maxHeight, onMouseDown: (e) => e.preventDefault(), children: renderOptions() }), document.body);
|
|
247
|
+
};
|
|
248
|
+
const renderSearchableDropDown = () => (_jsxs(_Fragment, { children: [_jsx(StyledSearchInput, { value: isOpen ? searchText : (selectedItem?.display ?? ''), placeholder: isOpen ? SDKUI_Localizator.Search : placeHolder, readOnly: disabled, disabled: disabled, "$disabled": disabled, "$fontSize": fontSize, "$isModified": isModifiedWhen, "$vil": validationItems, onFocus: () => { setIsFocused(true); openOptions(); }, onBlur: () => { setIsFocused(false); closeOptions(); }, onClick: () => { if (!isOpen)
|
|
249
|
+
openOptions(); }, onChange: (e) => { setSearchText(e.target.value); setIsOpen(true); setHighlightedIndex(0); }, onKeyDown: onSearchKeyDown }), renderOptionsPanel()] }));
|
|
250
|
+
const renderNativeDropDown = () => (_jsxs(StyledDropDown, { value: value, onFocus: () => setIsFocused(true), onBlur: () => setIsFocused(false), "$disabled": disabled, disabled: disabled, "$fontSize": fontSize, "$isModified": isModifiedWhen, "$vil": validationItems, onChange: onValueChanged, children: [placeHolder && _jsx("option", { value: "", disabled: true, hidden: true, children: placeHolder }), dataSource?.map((data, index) => (_jsx("option", { value: data.value, children: data.display }, index)))] }));
|
|
251
|
+
const renderedLeftLabelTextBox = () => (_jsxs(TMLayoutContainer, { direction: 'horizontal', children: [icon && _jsx(TMLayoutItem, { width: '20px', children: _jsx(StyledEditorIcon, { "$disabled": disabled, "$vil": validationItems, "$isModified": isModifiedWhen, children: icon }) }), _jsx(TMLayoutItem, { children: _jsxs(StyledEditorContainer, { "$width": width, children: [label && _jsx(StyledEditorLabel, { "$isFocused": isFocused, "$labelPosition": labelPosition, "$disabled": disabled, children: label }), _jsxs(StyledDropDownWrapper, { ref: dropDownWrapperRef, "$width": width, children: [searchEnabled ? renderSearchableDropDown() : renderNativeDropDown(), buttons.length > 0 && (_jsx(ButtonsContainer, { children: buttons.map((buttonItem) => (_jsx(StyledDropDownEditorButton, { onClick: buttonItem.onClick, children: _jsx(TMTooltip, { content: buttonItem.tooltipContent ?? buttonItem.text, children: buttonItem.icon }) }, buttonItem.text))) })), _jsx(CustomArrow, {})] }), _jsx(TMVilViewer, { vil: validationItems })] }) })] }));
|
|
60
252
|
return _jsx("div", { style: elementStyle, children: renderedLeftLabelTextBox() });
|
|
61
253
|
};
|
|
62
254
|
export default TMDropDown;
|
|
@@ -3,7 +3,7 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|
|
3
3
|
import styled from 'styled-components';
|
|
4
4
|
import { LoadIndicator } from 'devextreme-react';
|
|
5
5
|
import { AppModules, DataColumnTypes, DataListViewModes, DcmtTypeListCacheService, LayoutCacheService, LayoutModes, MetadataDataDomains, SDK_Globals, SystemMIDsAsNumber, UserListCacheService, } from '@topconsultnpm/sdk-ts';
|
|
6
|
-
import { deepCompare, formatDateTimeByMetadataFormat, formatNumberByMetadataFormat, generateUniqueColumnKeys, genUniqueId, getColumnFormatInfo, getSearchToolbarVisibility, IconAll, IconBoard, IconDcmtTypeSys, IconDelete, IconMenuVertical, IconPlatform, IconRefresh, IconSearchCheck, IconShow, isApprovalWorkflowView, isSign4TopEnabled, searchResultDescriptorToSimpleArray, searchResultToMetadataValues, SDKUI_Globals, SDKUI_Localizator } from '../../../helper';
|
|
6
|
+
import { customizeSearchByDisplayText, deepCompare, formatDateTimeByMetadataFormat, formatNumberByMetadataFormat, generateUniqueColumnKeys, genUniqueId, getColumnFormatInfo, getDateDisplayText, getNumberDisplayText, getSearchToolbarVisibility, IconAll, IconBoard, IconDcmtTypeSys, IconDelete, IconMenuVertical, IconPlatform, IconRefresh, IconSearchCheck, IconShow, isApprovalWorkflowView, isSign4TopEnabled, searchResultDescriptorToSimpleArray, searchResultToMetadataValues, SDKUI_Globals, SDKUI_Localizator } from '../../../helper';
|
|
7
7
|
import { CounterItemKey } from '../../base/TMCounterContainer';
|
|
8
8
|
import { getDcmtCicoStatus } from '../../../helper/checkinCheckoutManager';
|
|
9
9
|
import { DcmtOperationTypes, SearchResultContext, } from '../../../ts';
|
|
@@ -926,8 +926,8 @@ const TMSearchResultGrid = ({ openInOffice, fromDTD, operationItems, allUsers, i
|
|
|
926
926
|
useEffect(() => {
|
|
927
927
|
updateDataSourceFromDataGrid(dataSource ?? []);
|
|
928
928
|
}, [dataSource]);
|
|
929
|
-
const { loadDataListsAsync, renderDataListCell, dataListsCache } = useDataListItem();
|
|
930
|
-
const { loadUsersAsync, renderUserIdViewer, usersCache } = useDataUserIdItem();
|
|
929
|
+
const { loadDataListsAsync, renderDataListCell, dataListsCache, getDataListItem } = useDataListItem();
|
|
930
|
+
const { loadUsersAsync, renderUserIdViewer, usersCache, getUserItem, getCompleteUserName } = useDataUserIdItem();
|
|
931
931
|
useEffect(() => {
|
|
932
932
|
// Sincronizza focusedItem con inputFocusedItem dal padre
|
|
933
933
|
if (deepCompare(inputFocusedItem, focusedItem))
|
|
@@ -1159,6 +1159,57 @@ const TMSearchResultGrid = ({ openInOffice, fromDTD, operationItems, allUsers, i
|
|
|
1159
1159
|
});
|
|
1160
1160
|
return cols;
|
|
1161
1161
|
}, [searchResult, dataType, cellRender, getDisplayFormat, dataListsCache, usersCache]);
|
|
1162
|
+
/**
|
|
1163
|
+
* Colonne in cui il testo mostrato è diverso dal valore archiviato: date e numeri formattati, descrizioni
|
|
1164
|
+
* di liste dati e utenti. Su queste la ricerca della griglia va fatta sul testo mostrato, altrimenti non
|
|
1165
|
+
* trova nulla (es. cercando 29/01/2023 su una data, che nei dati è una stringa ISO).
|
|
1166
|
+
* Le cache sono ref lette al momento della ricerca: a quel punto sono già popolate.
|
|
1167
|
+
*/
|
|
1168
|
+
const searchTextGetters = useMemo(() => {
|
|
1169
|
+
const getters = new Map();
|
|
1170
|
+
const columnsOfResult = searchResult?.dtdResult?.columns;
|
|
1171
|
+
if (!columnsOfResult)
|
|
1172
|
+
return getters;
|
|
1173
|
+
const uniqueKeys = generateUniqueColumnKeys(columnsOfResult, searchResult?.fromTID);
|
|
1174
|
+
columnsOfResult.forEach((col, index) => {
|
|
1175
|
+
const dataField = uniqueKeys[index];
|
|
1176
|
+
if (!dataField)
|
|
1177
|
+
return;
|
|
1178
|
+
const dataDomain = MetadataDataDomains[(col.extendedProperties?.["DataDomain"] ?? "None")];
|
|
1179
|
+
const dataListID = Number(col.extendedProperties?.["DataListID"]);
|
|
1180
|
+
if (dataDomain === MetadataDataDomains.DataList && dataListID) {
|
|
1181
|
+
getters.set(dataField, (rowData) => getDataListItem(dataListID, rowData?.[dataField])?.name ?? String(rowData?.[dataField] ?? ''));
|
|
1182
|
+
return;
|
|
1183
|
+
}
|
|
1184
|
+
if (dataDomain === MetadataDataDomains.UserID) {
|
|
1185
|
+
getters.set(dataField, (rowData) => {
|
|
1186
|
+
const rawValue = rowData?.[dataField];
|
|
1187
|
+
// Un campo utente vuoto non mostra nulla: senza questo controllo Number('') darebbe 0, cioè l'utente di sistema
|
|
1188
|
+
if (rawValue === undefined || rawValue === null || rawValue === '')
|
|
1189
|
+
return '';
|
|
1190
|
+
const userId = Number(rawValue);
|
|
1191
|
+
if (Number.isNaN(userId))
|
|
1192
|
+
return String(rawValue);
|
|
1193
|
+
if (userId === 0)
|
|
1194
|
+
return SDKUI_Localizator.SystemUser ?? '';
|
|
1195
|
+
const ud = getUserItem(userId);
|
|
1196
|
+
return (ud ? getCompleteUserName(ud.domain, ud.name) : undefined) ?? String(rawValue);
|
|
1197
|
+
});
|
|
1198
|
+
return;
|
|
1199
|
+
}
|
|
1200
|
+
// Data e numero vengono mostrati con il formato definito sulle proprietà estese della colonna
|
|
1201
|
+
const { format, formatCulture } = getColumnFormatInfo(col);
|
|
1202
|
+
if (col.dataType === DataColumnTypes.DateTime) {
|
|
1203
|
+
getters.set(dataField, (rowData) => getDateDisplayText(rowData?.[dataField], format, formatCulture));
|
|
1204
|
+
return;
|
|
1205
|
+
}
|
|
1206
|
+
if (col.dataType === DataColumnTypes.Number) {
|
|
1207
|
+
getters.set(dataField, (rowData) => getNumberDisplayText(rowData?.[dataField], format, formatCulture));
|
|
1208
|
+
}
|
|
1209
|
+
});
|
|
1210
|
+
return getters;
|
|
1211
|
+
}, [searchResult, getDataListItem, getUserItem, getCompleteUserName]);
|
|
1212
|
+
const customizeColumns = useMemo(() => customizeSearchByDisplayText(searchTextGetters), [searchTextGetters]);
|
|
1162
1213
|
/**
|
|
1163
1214
|
* Effect che carica i dati necessari e genera le colonne della griglia.
|
|
1164
1215
|
* Esegue il caricamento delle cache (DataList e UserID) in parallelo prima di generare le colonne,
|
|
@@ -1289,7 +1340,7 @@ const TMSearchResultGrid = ({ openInOffice, fromDTD, operationItems, allUsers, i
|
|
|
1289
1340
|
setVisibleItems(visibleData);
|
|
1290
1341
|
}, []);
|
|
1291
1342
|
useEffect(() => { onVisibleItemChanged?.(visibleItems); }, [visibleItems]);
|
|
1292
|
-
return _jsxs("div", { style: { width: "100%", height: "100%" }, children: [!isDataGridReady && (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', height: '100%', width: '100%', gap: '10px' }, children: [_jsx(LoadIndicator, { height: 60, width: 60 }), _jsx("div", { children: SDKUI_Localizator.Loading })] })), isDataGridReady && _jsx(TMDataGrid, { ref: dataGridRef, id: "tm-search-result", keyExpr: "rowIndex", dataColumns: dataColumns, dataSource: dataSource, repaintChangesOnly: true, selectedRowKeys: selectedRowKeys, focusedRowKey: disableAutoFocus ? undefined : Number(focusedItem?.rowIndex ?? 0), showSearchPanel: showSearchTMDatagrid, showFilterPanel: true, sorting: { mode: "multiple" }, selection: { mode: allowMultipleSelection ? 'multiple' : 'single' }, pageSize: pageSize, onSelectionChanged: handleSelectionChange, onFocusedRowChanged: handleFocusedRowChange, onRowDblClick: onRowDblClick, onContentReady: onContentReady, showHeaderColumnChooser: true, onKeyDown: onKeyDown, customContextMenuItems: operationItems, counterConfig: {
|
|
1343
|
+
return _jsxs("div", { style: { width: "100%", height: "100%" }, children: [!isDataGridReady && (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', height: '100%', width: '100%', gap: '10px' }, children: [_jsx(LoadIndicator, { height: 60, width: 60 }), _jsx("div", { children: SDKUI_Localizator.Loading })] })), isDataGridReady && _jsx(TMDataGrid, { ref: dataGridRef, id: "tm-search-result", keyExpr: "rowIndex", dataColumns: dataColumns, dataSource: dataSource, customizeColumns: customizeColumns, repaintChangesOnly: true, selectedRowKeys: selectedRowKeys, focusedRowKey: disableAutoFocus ? undefined : Number(focusedItem?.rowIndex ?? 0), showSearchPanel: showSearchTMDatagrid, showFilterPanel: true, sorting: { mode: "multiple" }, selection: { mode: allowMultipleSelection ? 'multiple' : 'single' }, pageSize: pageSize, onSelectionChanged: handleSelectionChange, onFocusedRowChanged: handleFocusedRowChange, onRowDblClick: onRowDblClick, onContentReady: onContentReady, showHeaderColumnChooser: true, onKeyDown: onKeyDown, customContextMenuItems: operationItems, counterConfig: {
|
|
1293
1344
|
show: true,
|
|
1294
1345
|
items: dcmtsReturned !== undefined && dcmtsFound !== undefined
|
|
1295
1346
|
? new Map([
|
|
@@ -92,8 +92,11 @@ export const TMMetadataIcon = ({ tid, md, color, layoutMode = LayoutModes.Update
|
|
|
92
92
|
break;
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
|
+
// color='inherit' serve solo all'icona (eredita il colore dell'editor): il tooltip è renderizzato in overlay,
|
|
96
|
+
// dove 'inherit' non risolverebbe al colore dell'editor, quindi lì manteniamo la palette normale
|
|
97
|
+
let tooltipColor = color === 'inherit' ? undefined : color;
|
|
95
98
|
return (_jsx(_Fragment, { children: icon
|
|
96
|
-
? _jsx(TMMetadataTooltip, { tid: tid, md: md, layoutMode: layoutMode, color:
|
|
99
|
+
? _jsx(TMMetadataTooltip, { tid: tid, md: md, layoutMode: layoutMode, color: tooltipColor, children: icon })
|
|
97
100
|
: null }));
|
|
98
101
|
};
|
|
99
102
|
export const TMMetadataTooltip = ({ tid, md, color, children, layoutMode = LayoutModes.Update }) => {
|
|
@@ -259,7 +259,7 @@ export const TMDcmtTypeIcon = ({ dtd }) => {
|
|
|
259
259
|
return (_jsx(TMDcmtTypeTooltip, { dtd: dtd, children: icon }));
|
|
260
260
|
};
|
|
261
261
|
export const TMDcmtTypeTooltip = ({ dtd, children }) => {
|
|
262
|
-
return (_jsx("div", { style: { pointerEvents: 'all' }, children: _jsx(TMTooltip, { content: renderDTDTooltipContent(dtd), children: children }) }));
|
|
262
|
+
return (_jsx("div", { style: { pointerEvents: 'all' }, children: _jsx(TMTooltip, { allowContentInteraction: true, content: renderDTDTooltipContent(dtd), children: children }) }));
|
|
263
263
|
};
|
|
264
264
|
const TMTidViewer = ({ tmSession, tid, did, showIcon = false, color, showId = false, noneSelectionText = `<${SDKUI_Localizator.NoneSelection}>` }) => {
|
|
265
265
|
const [dtd, setDtd] = useState();
|
|
@@ -297,29 +297,43 @@ const TMTidViewer = ({ tmSession, tid, did, showIcon = false, color, showId = fa
|
|
|
297
297
|
textOverflow: 'ellipsis'
|
|
298
298
|
}, children: [showIcon && dtd && _jsx(TMDcmtTypeIcon, { dtd: dtd }), _jsx("p", { title: displayName(), style: {
|
|
299
299
|
textAlign: 'left',
|
|
300
|
+
margin: 0,
|
|
300
301
|
marginLeft: showIcon ? '5px' : '',
|
|
301
|
-
color:
|
|
302
|
+
color: dtd?.isView ? 'red' : (color ?? TMColors.primary),
|
|
302
303
|
whiteSpace: 'nowrap',
|
|
303
304
|
overflow: 'hidden',
|
|
304
305
|
textOverflow: 'ellipsis',
|
|
305
306
|
// paddingRight: '10px'
|
|
306
|
-
}, children: displayName() }), showId && _jsx("p", { style: { padding: '0px 3px' }, children: `(TID: ${tid})` })] }));
|
|
307
|
+
}, children: displayName() }), showId && _jsx("p", { style: { padding: '0px 3px', margin: 0 }, children: `(TID: ${tid})` })] }));
|
|
307
308
|
};
|
|
308
309
|
export default TMTidViewer;
|
|
309
310
|
export const cellRenderTID = (data, noneSelectionText) => {
|
|
310
311
|
return (_jsx(TMTidViewer, { tid: data.value, noneSelectionText: noneSelectionText }));
|
|
311
312
|
};
|
|
312
|
-
/**
|
|
313
|
+
/** Metodi di visualizzazione già richiesti, per non ricaricare i metadati ad ogni riapertura del tooltip */
|
|
314
|
+
const displayNameMethodCache = new Map();
|
|
313
315
|
const TMDisplayNameMethodItem = ({ dtd }) => {
|
|
314
|
-
const [displayNameMethod, setDisplayNameMethod] = useState(
|
|
315
|
-
|
|
316
|
-
|
|
316
|
+
const [displayNameMethod, setDisplayNameMethod] = useState(() => dtd.id ? displayNameMethodCache.get(dtd.id) : undefined);
|
|
317
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
318
|
+
const onShowClick = async (e) => {
|
|
319
|
+
/* Un click qualsiasi chiude il tooltip: senza questo il risultato non sarebbe mai visibile */
|
|
320
|
+
e.stopPropagation();
|
|
321
|
+
if (isLoading || displayNameMethod)
|
|
322
|
+
return;
|
|
323
|
+
setIsLoading(true);
|
|
324
|
+
try {
|
|
317
325
|
const method = await getDTDDisplayNameInfo(dtd);
|
|
326
|
+
if (dtd.id)
|
|
327
|
+
displayNameMethodCache.set(dtd.id, method);
|
|
318
328
|
setDisplayNameMethod(method);
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
329
|
+
}
|
|
330
|
+
finally {
|
|
331
|
+
setIsLoading(false);
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
return (_jsxs(StyledTooltipItem, { children: [`${SDKUI_Localizator.DisplayNameMethod}: `, displayNameMethod ?? (isLoading
|
|
335
|
+
? `${SDKUI_Localizator.Loading} ...`
|
|
336
|
+
: _jsx("span", { style: { color: TMColors.primary, cursor: 'pointer', textDecoration: 'underline' }, onClick: onShowClick, children: SDKUI_Localizator.Details }))] }));
|
|
323
337
|
};
|
|
324
338
|
export const renderDTDTooltipContent = (dtd) => {
|
|
325
339
|
const mapAccessLevelToLocalizedString = (level) => {
|
|
@@ -67,6 +67,18 @@ export declare class ThemeSettings {
|
|
|
67
67
|
export declare const DEFAULT_PREVIEW_THRESHOLD = 500;
|
|
68
68
|
export declare const DEFAULT_PAGE_SIZE = 100;
|
|
69
69
|
export declare const DEFAULT_MAX_DCMTS_TO_BE_RETURNED = 200;
|
|
70
|
+
export declare const DEFAULT_QUICK_SEARCH_MAX_DCMTS = 1000;
|
|
71
|
+
/** Ricerca rapida configurata come sorgente alternativa dei valori distinti di un campo */
|
|
72
|
+
export declare class DistinctValuesQuickSearchSettings {
|
|
73
|
+
/** Tipo documento su cui viene eseguita la ricerca rapida */
|
|
74
|
+
tid: number | undefined;
|
|
75
|
+
/** ID della ricerca salvata (SavedQueryDescriptor) da eseguire; SYS_ALL_DCMTS_SQD_ID per la ricerca di sistema con tutti i documenti del tipo documento */
|
|
76
|
+
sqdId: number | undefined;
|
|
77
|
+
/** Metadato del risultato da cui viene prelevato il valore */
|
|
78
|
+
mid: number | undefined;
|
|
79
|
+
/** Numero massimo di documenti restituiti dalla ricerca rapida */
|
|
80
|
+
maxDcmtsToBeReturned: number;
|
|
81
|
+
}
|
|
70
82
|
export declare class SearchSettings {
|
|
71
83
|
autoFindReferences: ObjectClasses[];
|
|
72
84
|
invoiceRetrieveFormat: InvoiceRetrieveFormats;
|
|
@@ -86,7 +98,15 @@ export declare class SearchSettings {
|
|
|
86
98
|
};
|
|
87
99
|
relationExpandLevel: number;
|
|
88
100
|
relationShowZeroDcmts: boolean;
|
|
101
|
+
/** Ricerche rapide configurate per i valori distinti: una per ogni campo, con chiave "tid_mid" */
|
|
102
|
+
distinctValuesQuickSearches: {
|
|
103
|
+
[fieldKey: string]: DistinctValuesQuickSearchSettings;
|
|
104
|
+
};
|
|
89
105
|
}
|
|
106
|
+
/** Ricerca rapida configurata per il campo, se presente */
|
|
107
|
+
export declare const getDistinctValuesQuickSearch: (tid: number | undefined, mid: number | undefined) => DistinctValuesQuickSearchSettings | undefined;
|
|
108
|
+
export declare const saveDistinctValuesQuickSearch: (tid: number | undefined, mid: number | undefined, settings: DistinctValuesQuickSearchSettings) => void;
|
|
109
|
+
export declare const removeDistinctValuesQuickSearch: (tid: number | undefined, mid: number | undefined) => void;
|
|
90
110
|
export declare class FloatingMenuBarSettings {
|
|
91
111
|
orientation?: 'horizontal' | 'vertical';
|
|
92
112
|
itemIds?: string[];
|
|
@@ -130,6 +130,20 @@ export class ThemeSettings {
|
|
|
130
130
|
export const DEFAULT_PREVIEW_THRESHOLD = 500; // KB
|
|
131
131
|
export const DEFAULT_PAGE_SIZE = 100;
|
|
132
132
|
export const DEFAULT_MAX_DCMTS_TO_BE_RETURNED = 200;
|
|
133
|
+
export const DEFAULT_QUICK_SEARCH_MAX_DCMTS = 1000;
|
|
134
|
+
/** Ricerca rapida configurata come sorgente alternativa dei valori distinti di un campo */
|
|
135
|
+
export class DistinctValuesQuickSearchSettings {
|
|
136
|
+
constructor() {
|
|
137
|
+
/** Tipo documento su cui viene eseguita la ricerca rapida */
|
|
138
|
+
this.tid = undefined;
|
|
139
|
+
/** ID della ricerca salvata (SavedQueryDescriptor) da eseguire; SYS_ALL_DCMTS_SQD_ID per la ricerca di sistema con tutti i documenti del tipo documento */
|
|
140
|
+
this.sqdId = undefined;
|
|
141
|
+
/** Metadato del risultato da cui viene prelevato il valore */
|
|
142
|
+
this.mid = undefined;
|
|
143
|
+
/** Numero massimo di documenti restituiti dalla ricerca rapida */
|
|
144
|
+
this.maxDcmtsToBeReturned = DEFAULT_QUICK_SEARCH_MAX_DCMTS;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
133
147
|
export class SearchSettings {
|
|
134
148
|
constructor() {
|
|
135
149
|
this.autoFindReferences = [];
|
|
@@ -144,8 +158,38 @@ export class SearchSettings {
|
|
|
144
158
|
this.panelLayout = {};
|
|
145
159
|
this.relationExpandLevel = 4; // Livello di espansione predefinito per le correlazioni
|
|
146
160
|
this.relationShowZeroDcmts = false;
|
|
161
|
+
/** Ricerche rapide configurate per i valori distinti: una per ogni campo, con chiave "tid_mid" */
|
|
162
|
+
this.distinctValuesQuickSearches = {};
|
|
147
163
|
}
|
|
148
164
|
}
|
|
165
|
+
/** Il campo da cui viene aperto il pannello dei valori distinti è identificato dalla coppia (tid, mid) */
|
|
166
|
+
const distinctValuesFieldKey = (tid, mid) => `${tid}_${mid}`;
|
|
167
|
+
/** Ricerca rapida configurata per il campo, se presente */
|
|
168
|
+
export const getDistinctValuesQuickSearch = (tid, mid) => {
|
|
169
|
+
if (!tid || !mid)
|
|
170
|
+
return undefined;
|
|
171
|
+
// Le impostazioni salvate da versioni precedenti non hanno la mappa: va gestita l'assenza
|
|
172
|
+
return SDKUI_Globals.userSettings.searchSettings.distinctValuesQuickSearches?.[distinctValuesFieldKey(tid, mid)];
|
|
173
|
+
};
|
|
174
|
+
export const saveDistinctValuesQuickSearch = (tid, mid, settings) => {
|
|
175
|
+
if (!tid || !mid)
|
|
176
|
+
return;
|
|
177
|
+
// Il proxy delle impostazioni persiste solo intercettando l'assegnazione di una proprietà: la mappa va riassegnata per intero
|
|
178
|
+
SDKUI_Globals.userSettings.searchSettings.distinctValuesQuickSearches = {
|
|
179
|
+
...SDKUI_Globals.userSettings.searchSettings.distinctValuesQuickSearches,
|
|
180
|
+
[distinctValuesFieldKey(tid, mid)]: settings,
|
|
181
|
+
};
|
|
182
|
+
};
|
|
183
|
+
export const removeDistinctValuesQuickSearch = (tid, mid) => {
|
|
184
|
+
if (!tid || !mid)
|
|
185
|
+
return;
|
|
186
|
+
const fieldKey = distinctValuesFieldKey(tid, mid);
|
|
187
|
+
const quickSearches = SDKUI_Globals.userSettings.searchSettings.distinctValuesQuickSearches;
|
|
188
|
+
if (!quickSearches?.[fieldKey])
|
|
189
|
+
return;
|
|
190
|
+
const { [fieldKey]: removedSettings, ...remainingQuickSearches } = quickSearches;
|
|
191
|
+
SDKUI_Globals.userSettings.searchSettings.distinctValuesQuickSearches = remainingQuickSearches;
|
|
192
|
+
};
|
|
149
193
|
export class FloatingMenuBarSettings {
|
|
150
194
|
}
|
|
151
195
|
export class ArchivingSettings {
|
|
@@ -657,6 +657,7 @@ export declare class SDKUI_Localizator {
|
|
|
657
657
|
static get QueryDefine(): "Abfrage definieren" | "Query define" | "Definir consulta" | "Défine query" | "Definir query" | "Definisci query";
|
|
658
658
|
static get Query_EnterAlias(): "Geben Sie einen Alias ein" | "Enter an alias" | "Introducir un alias" | "Entrez un alias" | "Digite um nome de alias" | "Inserire un alias";
|
|
659
659
|
static get QueryParamBind(): "Query-Parameter erweitern" | "Fill query parameters" | "Valorizar parámetros consulta" | "Ajoute la valeur à les paramètres de la query" | "Melhora parâmetros de query" | "Valorizza parametri query";
|
|
660
|
+
static get QuickSearchSettings_DeleteConfirmFor1(): "Schnellsuche-Konfiguration für '{{0}}' löschen?" | "Delete the quick search setup for '{{0}}'?" | "¿Eliminar la configuración de la búsqueda rápida de '{{0}}'?" | "Supprimer la configuration de la recherche rapide de '{{0}}' ?" | "Eliminar a configuração da pesquisa rápida de '{{0}}'?" | "Eliminare la configurazione della ricerca rapida di '{{0}}'?";
|
|
660
661
|
static get ReadOnly(): "Nur Lesen" | "Read only" | "Solo lectura" | "En lecture seule" | "Somente leitura" | "Solo lettura";
|
|
661
662
|
static get Reassign(): "Neu zuweisen" | "Reassign" | "Reasignar" | "Réaffecter" | "Reatribuir" | "Riassegna";
|
|
662
663
|
static get RefersTo(): string;
|
|
@@ -6542,6 +6542,16 @@ export class SDKUI_Localizator {
|
|
|
6542
6542
|
default: return "Valorizza parametri query";
|
|
6543
6543
|
}
|
|
6544
6544
|
}
|
|
6545
|
+
static get QuickSearchSettings_DeleteConfirmFor1() {
|
|
6546
|
+
switch (this._cultureID) {
|
|
6547
|
+
case CultureIDs.De_DE: return "Schnellsuche-Konfiguration für '{{0}}' löschen?";
|
|
6548
|
+
case CultureIDs.En_US: return "Delete the quick search setup for '{{0}}'?";
|
|
6549
|
+
case CultureIDs.Es_ES: return "¿Eliminar la configuración de la búsqueda rápida de '{{0}}'?";
|
|
6550
|
+
case CultureIDs.Fr_FR: return "Supprimer la configuration de la recherche rapide de '{{0}}' ?";
|
|
6551
|
+
case CultureIDs.Pt_PT: return "Eliminar a configuração da pesquisa rápida de '{{0}}'?";
|
|
6552
|
+
default: return "Eliminare la configurazione della ricerca rapida di '{{0}}'?";
|
|
6553
|
+
}
|
|
6554
|
+
}
|
|
6545
6555
|
static get ReadOnly() {
|
|
6546
6556
|
switch (this._cultureID) {
|
|
6547
6557
|
case CultureIDs.De_DE: return "Nur Lesen";
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { MetadataFormats } from '@topconsultnpm/sdk-ts';
|
|
2
|
+
/** Testo mostrato in una colonna per una riga: è quello su cui va cercato ciò che l'utente digita */
|
|
3
|
+
export type SearchTextGetter = (rowData: any) => string;
|
|
4
|
+
/** Testo con cui una data viene mostrata nella griglia: è quello che l'utente vede e quindi quello che cerca */
|
|
5
|
+
export declare const getDateDisplayText: (value: any, format: MetadataFormats | undefined, formatCulture: string | undefined | null) => string;
|
|
6
|
+
/** Testo con cui un numero viene mostrato nella griglia (separatore delle migliaia, valuta, decimali) */
|
|
7
|
+
export declare const getNumberDisplayText: (value: any, format: MetadataFormats | undefined, formatCulture: string) => string;
|
|
8
|
+
/**
|
|
9
|
+
* Dirotta la ricerca della griglia sul testo mostrato, per le colonne indicate (chiave: il `dataField`).
|
|
10
|
+
*
|
|
11
|
+
* La ricerca di DevExtreme lavora sul valore archiviato, quindi non trova nulla dove il testo mostrato è
|
|
12
|
+
* diverso:
|
|
13
|
+
* - **date**, che nei dati sono stringhe ISO: DevExtreme prova a interpretare il testo digitato con il
|
|
14
|
+
* formato della colonna, non riesce (il formato è un formatter personalizzato, non un pattern) e scarta
|
|
15
|
+
* la colonna dalla ricerca — cercando `29/01/2023` non trova nulla;
|
|
16
|
+
* - **numeri formattati**, per lo stesso motivo (separatore delle migliaia, simbolo di valuta);
|
|
17
|
+
* - **liste dati e utenti**, dove viene confrontata la chiave archiviata invece della descrizione
|
|
18
|
+
* visualizzata — cercando `FATTURA` non trova nulla, cercando `TD01` sì.
|
|
19
|
+
*
|
|
20
|
+
* Da passare a `customizeColumns` della griglia. Va fatto lì perché solo lì è disponibile la
|
|
21
|
+
* `calculateFilterExpression` predefinita di DevExtreme, che resta in uso per il filtro di intestazione e
|
|
22
|
+
* per il generatore di filtri: quelli funzionano già correttamente e vanno lasciati intatti.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* const searchTextGetters = useMemo(() => new Map([['TM_1_2', (row) => getDateDisplayText(row?.TM_1_2, format, culture)]]), [...]);
|
|
26
|
+
* const customizeColumns = useMemo(() => customizeSearchByDisplayText(searchTextGetters), [searchTextGetters]);
|
|
27
|
+
*/
|
|
28
|
+
export declare const customizeSearchByDisplayText: (searchTextGetters: Map<string, SearchTextGetter>) => (columns: Array<any>) => void;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { formatDateTimeByMetadataFormat, formatNumberByMetadataFormat } from './TMUtils';
|
|
2
|
+
/** Testo con cui una data viene mostrata nella griglia: è quello che l'utente vede e quindi quello che cerca */
|
|
3
|
+
export const getDateDisplayText = (value, format, formatCulture) => {
|
|
4
|
+
if (value === undefined || value === null || value === '')
|
|
5
|
+
return '';
|
|
6
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
7
|
+
return Number.isNaN(date.getTime()) ? '' : formatDateTimeByMetadataFormat(date, format, formatCulture);
|
|
8
|
+
};
|
|
9
|
+
/** Testo con cui un numero viene mostrato nella griglia (separatore delle migliaia, valuta, decimali) */
|
|
10
|
+
export const getNumberDisplayText = (value, format, formatCulture) => {
|
|
11
|
+
if (value === undefined || value === null || value === '')
|
|
12
|
+
return '';
|
|
13
|
+
return formatNumberByMetadataFormat(value, format, formatCulture);
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Dirotta la ricerca della griglia sul testo mostrato, per le colonne indicate (chiave: il `dataField`).
|
|
17
|
+
*
|
|
18
|
+
* La ricerca di DevExtreme lavora sul valore archiviato, quindi non trova nulla dove il testo mostrato è
|
|
19
|
+
* diverso:
|
|
20
|
+
* - **date**, che nei dati sono stringhe ISO: DevExtreme prova a interpretare il testo digitato con il
|
|
21
|
+
* formato della colonna, non riesce (il formato è un formatter personalizzato, non un pattern) e scarta
|
|
22
|
+
* la colonna dalla ricerca — cercando `29/01/2023` non trova nulla;
|
|
23
|
+
* - **numeri formattati**, per lo stesso motivo (separatore delle migliaia, simbolo di valuta);
|
|
24
|
+
* - **liste dati e utenti**, dove viene confrontata la chiave archiviata invece della descrizione
|
|
25
|
+
* visualizzata — cercando `FATTURA` non trova nulla, cercando `TD01` sì.
|
|
26
|
+
*
|
|
27
|
+
* Da passare a `customizeColumns` della griglia. Va fatto lì perché solo lì è disponibile la
|
|
28
|
+
* `calculateFilterExpression` predefinita di DevExtreme, che resta in uso per il filtro di intestazione e
|
|
29
|
+
* per il generatore di filtri: quelli funzionano già correttamente e vanno lasciati intatti.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* const searchTextGetters = useMemo(() => new Map([['TM_1_2', (row) => getDateDisplayText(row?.TM_1_2, format, culture)]]), [...]);
|
|
33
|
+
* const customizeColumns = useMemo(() => customizeSearchByDisplayText(searchTextGetters), [searchTextGetters]);
|
|
34
|
+
*/
|
|
35
|
+
export const customizeSearchByDisplayText = (searchTextGetters) => (columns) => {
|
|
36
|
+
columns.forEach((column) => {
|
|
37
|
+
const getSearchText = column?.dataField ? searchTextGetters.get(column.dataField) : undefined;
|
|
38
|
+
// customizeColumns viene richiamata a ogni aggiornamento delle colonne: senza il marcatore i wrapper si anniderebbero
|
|
39
|
+
if (!getSearchText || column.tmSearchByDisplayText)
|
|
40
|
+
return;
|
|
41
|
+
const defaultCalculateFilterExpression = column.calculateFilterExpression;
|
|
42
|
+
// Senza questo DevExtreme prova a convertire il testo digitato nel tipo della colonna e, non riuscendo, la scarta
|
|
43
|
+
column.parseValue = (text) => text;
|
|
44
|
+
column.calculateFilterExpression = function (filterValue, selectedFilterOperation, target) {
|
|
45
|
+
if (target === 'search')
|
|
46
|
+
return [getSearchText, 'contains', filterValue];
|
|
47
|
+
return defaultCalculateFilterExpression?.apply(this, arguments);
|
|
48
|
+
};
|
|
49
|
+
column.tmSearchByDisplayText = true;
|
|
50
|
+
});
|
|
51
|
+
};
|
package/lib/helper/index.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export * from './Globalization';
|
|
|
5
5
|
export * from './TMIcons';
|
|
6
6
|
export * from './TMImageLibrary';
|
|
7
7
|
export * from './helpers';
|
|
8
|
+
export * from './dataGridSearchHelper';
|
|
8
9
|
export * from './queryHelper';
|
|
9
10
|
export * from './TMUtils';
|
|
10
11
|
export * from './TMCommandsContextMenu';
|
package/lib/helper/index.js
CHANGED
|
@@ -5,6 +5,7 @@ export * from './Globalization';
|
|
|
5
5
|
export * from './TMIcons';
|
|
6
6
|
export * from './TMImageLibrary';
|
|
7
7
|
export * from './helpers';
|
|
8
|
+
export * from './dataGridSearchHelper';
|
|
8
9
|
export * from './queryHelper';
|
|
9
10
|
export * from './TMUtils';
|
|
10
11
|
export * from './TMCommandsContextMenu';
|
|
@@ -14,6 +14,8 @@ export declare function addWhereClausesForConnect(qd: QueryDescriptor): void;
|
|
|
14
14
|
export declare function getDefaultOperator(dataDomain: MetadataDataDomains | undefined, dataType: MetadataDataTypes | undefined): QueryOperators.Equal | QueryOperators.Contain | QueryOperators.In;
|
|
15
15
|
export declare const getQD: (tid: number | undefined, easyOr: boolean, newMaxDcmtsToBeReturned: number) => Promise<QueryDescriptor | undefined>;
|
|
16
16
|
export declare const getWorkItemSetIDAsync: (vid: number, did: number) => Promise<string | undefined>;
|
|
17
|
+
/** Id assegnato alla ricerca di sistema con tutti i documenti: non è in cache, va ricostruita a runtime con getSysAllDcmtsSQD */
|
|
18
|
+
export declare const SYS_ALL_DCMTS_SQD_ID = 1;
|
|
17
19
|
export declare const getSysAllDcmtsSQD: (tid: number | undefined, easyOr: boolean) => Promise<SavedQueryDescriptor>;
|
|
18
20
|
export declare const searchResultToMetadataValues: (tid: number | undefined, dtd: DataTableDescriptor | undefined, rows: string[], mids: number[], metadata: MetadataDescriptor[], layoutMode: LayoutModes, isReadOnlyOrigin?: boolean) => MetadataValueDescriptorEx[];
|
|
19
21
|
export declare const handleArchiveVisibility: (md: MetadataDescriptor) => boolean;
|