@topconsultnpm/sdkui-react 6.22.0-dev2.4 → 6.22.0-dev2.5
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/features/search/TMSearch.js +29 -16
- package/lib/components/features/search/TMSearchQueryPanel.d.ts +1 -1
- package/lib/components/features/search/TMSearchQueryPanel.js +28 -7
- package/lib/helper/SDKUI_Localizator.d.ts +2 -0
- package/lib/helper/SDKUI_Localizator.js +20 -0
- package/lib/helper/helpers.d.ts +11 -0
- package/lib/helper/helpers.js +32 -0
- package/package.json +1 -1
|
@@ -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;
|
|
@@ -5,7 +5,7 @@ import TMSavedQuerySelector from './TMSavedQuerySelector';
|
|
|
5
5
|
import TMTreeSelector from './TMTreeSelector';
|
|
6
6
|
import { TabPanel, Item } from 'devextreme-react/tab-panel';
|
|
7
7
|
import TMSearchQueryPanel, { refreshLastSearch } from './TMSearchQueryPanel';
|
|
8
|
-
import { getSysAllDcmtsSQD, IconFilter, IconRecentlyViewed, IconSavedQuery, IconTree, removeMruTid, SDKUI_Globals, SDKUI_Localizator, updateMruTids } from '../../../helper';
|
|
8
|
+
import { getSysAllDcmtsSQD, IconFilter, IconRecentlyViewed, IconSavedQuery, IconTree, mergeSearchResultsAppend, removeMruTid, SDKUI_Globals, SDKUI_Localizator, updateMruTids } from '../../../helper';
|
|
9
9
|
import TMSearchResult from './TMSearchResult';
|
|
10
10
|
import TMRecentsManager from '../../grids/TMRecentsManager';
|
|
11
11
|
import { SearchResultContext } from '../../../ts';
|
|
@@ -164,6 +164,32 @@ const TMSearch = ({ allTasks = [], getAllTasks, deleteTaskByIdsCallback, addTask
|
|
|
164
164
|
console.error("Error refreshing search:", error);
|
|
165
165
|
}
|
|
166
166
|
};
|
|
167
|
+
const handleSearchCompleted = useCallback((searchResult, qd, append) => {
|
|
168
|
+
if (searchResult.length <= 0) {
|
|
169
|
+
// In ricerca normale un array vuoto resetta i risultati; in accoda si mantiene il set accumulato
|
|
170
|
+
if (!append)
|
|
171
|
+
setSearchResult(searchResult);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
const newResult = searchResult[0];
|
|
175
|
+
// In modalità accoda unisce il nuovo risultato a quello precedente (più recenti in cima).
|
|
176
|
+
setSearchResult(prev => {
|
|
177
|
+
if (append && prev.length > 0 && prev[0]?.fromTID === newResult?.fromTID) {
|
|
178
|
+
const merged = mergeSearchResultsAppend(prev[0], newResult);
|
|
179
|
+
return merged ? [merged] : searchResult;
|
|
180
|
+
}
|
|
181
|
+
return searchResult;
|
|
182
|
+
});
|
|
183
|
+
setLastQdSearched(qd);
|
|
184
|
+
setCurrentSearchView(TMSearchViews.Result);
|
|
185
|
+
// Salvataggio ultimi 10 TIDs
|
|
186
|
+
let fromTID = searchResult?.[0].fromTID;
|
|
187
|
+
let newMruTIDS = updateMruTids(SDKUI_Globals.userSettings.searchSettings.mruTIDs, fromTID);
|
|
188
|
+
SDKUI_Globals.userSettings.searchSettings.mruTIDs = newMruTIDS;
|
|
189
|
+
setMruTIDs(newMruTIDS);
|
|
190
|
+
setCurrentMruTID(fromTID);
|
|
191
|
+
setShowSearchResults(true);
|
|
192
|
+
}, []);
|
|
167
193
|
const isMobile = deviceType === DeviceType.MOBILE;
|
|
168
194
|
const isTabletOrMobile = deviceType === DeviceType.TABLET || deviceType === DeviceType.MOBILE;
|
|
169
195
|
// --- JSX WRAPPERS ---
|
|
@@ -182,23 +208,10 @@ const TMSearch = ({ allTasks = [], getAllTasks, deleteTaskByIdsCallback, addTask
|
|
|
182
208
|
SDKUI_Globals.userSettings.searchSettings.mruTIDs = newMruTIDS;
|
|
183
209
|
setMruTIDs(newMruTIDS);
|
|
184
210
|
} }), [mruTIDs, currentMruTID, deviceType]);
|
|
185
|
-
const tmSearchQueryPanelElement = useMemo(() => _jsx(TMSearchQueryPanelWrapper, { passToArchiveCallback: passToArchiveCallback, isExpertMode: isExpertMode, showBackToResultButton: searchResult.length > 0, fromDTD: fromDTD, SQD: currentSQD, inputMids: inputMids, maxDcmtsToBeReturned: maxDcmtsToBeReturned, onBackToResult: () => { setCurrentSearchView(TMSearchViews.Result); }, onSearchCompleted:
|
|
186
|
-
setSearchResult(searchResult);
|
|
187
|
-
if (searchResult.length <= 0)
|
|
188
|
-
return;
|
|
189
|
-
setLastQdSearched(qd);
|
|
190
|
-
setCurrentSearchView(TMSearchViews.Result);
|
|
191
|
-
// Salvataggio ultimi 10 TIDs
|
|
192
|
-
let fromTID = searchResult?.[0].fromTID;
|
|
193
|
-
let newMruTIDS = updateMruTids(SDKUI_Globals.userSettings.searchSettings.mruTIDs, fromTID);
|
|
194
|
-
SDKUI_Globals.userSettings.searchSettings.mruTIDs = newMruTIDS;
|
|
195
|
-
setMruTIDs(newMruTIDS);
|
|
196
|
-
setCurrentMruTID(fromTID);
|
|
197
|
-
setShowSearchResults(true);
|
|
198
|
-
}, onSqdSaved: async (newSqd) => {
|
|
211
|
+
const tmSearchQueryPanelElement = useMemo(() => _jsx(TMSearchQueryPanelWrapper, { passToArchiveCallback: passToArchiveCallback, isExpertMode: isExpertMode, showBackToResultButton: searchResult.length > 0, fromDTD: fromDTD, SQD: currentSQD, inputMids: inputMids, maxDcmtsToBeReturned: maxDcmtsToBeReturned, onBackToResult: () => { setCurrentSearchView(TMSearchViews.Result); }, onSearchCompleted: handleSearchCompleted, onSqdSaved: async (newSqd) => {
|
|
199
212
|
await loadDataSQDsAsync(true, newSqd.masterTID);
|
|
200
213
|
await setSQDAsync(newSqd);
|
|
201
|
-
} }), [fromDTD, showSearchResults, setShowSearchResults, currentSQD, isExpertMode, mruTIDs, searchResult, passToArchiveCallback, inputMids, maxDcmtsToBeReturned]);
|
|
214
|
+
} }), [fromDTD, showSearchResults, setShowSearchResults, currentSQD, isExpertMode, mruTIDs, searchResult, passToArchiveCallback, inputMids, maxDcmtsToBeReturned, handleSearchCompleted]);
|
|
202
215
|
const tmSavedQuerySelectorElement = useMemo(() => _jsxs(TabPanel, { width: "100%", height: "100%", showNavButtons: true, repaintChangesOnly: true, selectedIndex: currentSQDMode, onSelectedIndexChange: (index) => setCurrentSQDMode(index), children: [(currentTID || currentSQD) ? _jsx(Item, { title: fromDTD?.nameLoc, children: _jsx(TMSavedQuerySelectorWrapper, { allowShowSearch: false, items: filteredByTIDSQDs, selectedId: currentSQD?.id, onRefreshData: () => { loadDataSQDsAsync(true); }, onItemClick: (sqd) => {
|
|
203
216
|
onSQDItemClick(sqd, setSQDAsync);
|
|
204
217
|
}, onDeleted: (sqd) => onSQDDeleted(sqd, sqd.id == currentSQD?.id ? filteredByTIDSQDs.find(o => o.id == 1) : currentSQD, setSQDAsync), refreshFavoriteSavedQueries: refreshFavoriteSavedQueries }) }) : _jsx(_Fragment, {}), _jsx(Item, { title: SDKUI_Localizator.AllFemale, children: _jsx(TMSavedQuerySelectorWrapper, { allowShowSearch: true, items: allSQDs, manageDefault: false, onItemClick: (sqd) => {
|
|
@@ -12,7 +12,7 @@ interface ITMSearchQueryPanelProps {
|
|
|
12
12
|
onBack?: () => void;
|
|
13
13
|
onBackToResult?: () => void;
|
|
14
14
|
onSqdSaved?: (newSqd: SavedQueryDescriptor) => void;
|
|
15
|
-
onSearchCompleted?: (searchResult: SearchResultDescriptor[], qd: QueryDescriptor | undefined) => void;
|
|
15
|
+
onSearchCompleted?: (searchResult: SearchResultDescriptor[], qd: QueryDescriptor | undefined, append?: boolean) => void;
|
|
16
16
|
onClosePanel?: () => void;
|
|
17
17
|
allowMaximize?: boolean;
|
|
18
18
|
onMaximizePanel?: () => void;
|
|
@@ -46,6 +46,8 @@ const TMSearchQueryPanel = ({ fromDTD, showBackToResultButton, isExpertMode = SD
|
|
|
46
46
|
let initialMaxItems = deviceType === DeviceType.MOBILE ? 8 : 12;
|
|
47
47
|
const appliedInputMidsRef = useRef(null);
|
|
48
48
|
const pendingMidsRef = useRef(null);
|
|
49
|
+
// Flag "accoda": impostato dall'azione secondaria del bottone Ricerca, consumato in executeSearch
|
|
50
|
+
const appendNextSearchRef = useRef(false);
|
|
49
51
|
useEffect(() => {
|
|
50
52
|
if (!SQD)
|
|
51
53
|
return;
|
|
@@ -122,7 +124,10 @@ const TMSearchQueryPanel = ({ fromDTD, showBackToResultButton, isExpertMode = SD
|
|
|
122
124
|
useEffect(() => {
|
|
123
125
|
if (shouldSearch && qd) {
|
|
124
126
|
const executeSearch = async () => {
|
|
125
|
-
|
|
127
|
+
// Legge e consuma il flag "accoda" impostato dall'azione secondaria del bottone Ricerca
|
|
128
|
+
const append = appendNextSearchRef.current;
|
|
129
|
+
appendNextSearchRef.current = false;
|
|
130
|
+
await searchAsync(qd, showAdvancedSearch, append);
|
|
126
131
|
setShouldSearch(false); // Resetta il trigger dopo la ricerca
|
|
127
132
|
};
|
|
128
133
|
executeSearch();
|
|
@@ -167,8 +172,9 @@ const TMSearchQueryPanel = ({ fromDTD, showBackToResultButton, isExpertMode = SD
|
|
|
167
172
|
});
|
|
168
173
|
setQd({ ...qd, where: newWhere, orderBy: [] });
|
|
169
174
|
};
|
|
170
|
-
const searchAsync = async (qdInput, isAdvancedSearch) => {
|
|
171
|
-
|
|
175
|
+
const searchAsync = async (qdInput, isAdvancedSearch, append = false) => {
|
|
176
|
+
if (!append)
|
|
177
|
+
onSearchCompleted?.([], undefined); // reset results (solo in ricerca normale, non in accoda)
|
|
172
178
|
let searchParams = { isAdvancedSearch: isAdvancedSearch, lastQdParams: lastQdParams, confirmQueryParams: confirmQueryParams, setLastQdParamsCallback: setLastQdParams };
|
|
173
179
|
let searchResult = await searchByQdAsync(qdInput, searchParams);
|
|
174
180
|
let dcmtsFound = searchResult?.result?.dcmtsFound ?? 0;
|
|
@@ -180,7 +186,7 @@ const TMSearchQueryPanel = ({ fromDTD, showBackToResultButton, isExpertMode = SD
|
|
|
180
186
|
results.push(searchResult?.result);
|
|
181
187
|
// Before notifying onSearchComplete, let's set the panel to NOT active
|
|
182
188
|
setIsQueryPanelActive(false);
|
|
183
|
-
onSearchCompleted?.(results, searchResult?.qd);
|
|
189
|
+
onSearchCompleted?.(results, searchResult?.qd, append);
|
|
184
190
|
}
|
|
185
191
|
};
|
|
186
192
|
const updateIsModalOpen = useCallback((isOpen) => {
|
|
@@ -214,9 +220,24 @@ const TMSearchQueryPanel = ({ fromDTD, showBackToResultButton, isExpertMode = SD
|
|
|
214
220
|
if (document.activeElement instanceof HTMLElement) {
|
|
215
221
|
document.activeElement.blur();
|
|
216
222
|
}
|
|
217
|
-
//
|
|
223
|
+
// Ricerca normale: azzera l'eventuale flag accoda e avvia
|
|
224
|
+
appendNextSearchRef.current = false;
|
|
218
225
|
setShouldSearch(true);
|
|
219
226
|
}, []);
|
|
227
|
+
const handleSearchAndAppendClick = useCallback(() => {
|
|
228
|
+
// Propagazione blur
|
|
229
|
+
if (document.activeElement instanceof HTMLElement) {
|
|
230
|
+
document.activeElement.blur();
|
|
231
|
+
}
|
|
232
|
+
// Ricerca e accoda: imposta il flag consumato da executeSearch
|
|
233
|
+
appendNextSearchRef.current = true;
|
|
234
|
+
setShouldSearch(true);
|
|
235
|
+
}, []);
|
|
236
|
+
const searchButtonSecondaryActions = useMemo(() => [{
|
|
237
|
+
name: SDKUI_Localizator.SearchAndAppend,
|
|
238
|
+
icon: _jsx(IconSearch, {}),
|
|
239
|
+
onClick: handleSearchAndAppendClick
|
|
240
|
+
}], [handleSearchAndAppendClick]);
|
|
220
241
|
const handleQdChanged = useCallback((newQd) => {
|
|
221
242
|
if (!deepCompare(qd, newQd)) {
|
|
222
243
|
setQd(newQd);
|
|
@@ -270,7 +291,7 @@ const TMSearchQueryPanel = ({ fromDTD, showBackToResultButton, isExpertMode = SD
|
|
|
270
291
|
}, [qd, fromDTD?.metadata, SQD?.masterTID]);
|
|
271
292
|
const handleCloseOutputConfig = useCallback(() => setShowOutputConfig(false), []);
|
|
272
293
|
const contextMenuItems = useMemo(() => [
|
|
273
|
-
...(showBackToResultButton ? [{ icon: _jsx(IconArrowRight, {}), name:
|
|
294
|
+
...(showBackToResultButton ? [{ icon: _jsx(IconArrowRight, {}), name: SDKUI_Localizator.BackToResult, onClick: () => { onBackToResult?.(); } }] : []),
|
|
274
295
|
{ icon: _jsx(IconAddCircleOutline, {}), name: SDKUI_Localizator.SavedQueryNew, beginGroup: showBackToResultButton, onClick: () => { openSqdForm(FormModes.Create); } },
|
|
275
296
|
{ icon: _jsx(IconEdit, {}), name: SDKUI_Localizator.SavedQueryUpdate, disabled: (SQD && SQD.id == 1), onClick: () => { openSqdForm(FormModes.Update); } },
|
|
276
297
|
{ icon: showAdvancedSearch ? _jsx(IconEasy, {}) : _jsx(IconAdvanced, {}), beginGroup: true, name: showAdvancedSearch ? SDKUI_Localizator.Search_Easy : SDKUI_Localizator.Search_Advanced, onClick: () => { changeAdvancedSearchAsync(!showAdvancedSearch); } },
|
|
@@ -353,7 +374,7 @@ const TMSearchQueryPanel = ({ fromDTD, showBackToResultButton, isExpertMode = SD
|
|
|
353
374
|
alignItems: 'center',
|
|
354
375
|
gap: '10px',
|
|
355
376
|
width: '100%'
|
|
356
|
-
}, children: [_jsx(TMButton, { btnStyle: 'advanced', icon: _jsx(IconSearch, {}), showTooltip: false, width: '
|
|
377
|
+
}, children: [_jsx(TMButton, { btnStyle: 'advanced', icon: _jsx(IconSearch, {}), showTooltip: false, width: '120px', caption: SDKUI_Localizator.Search, advancedColor: '#4A96D2', onClick: handleSearchButtonClick, secondaryActions: searchButtonSecondaryActions }), _jsx(TMButton, { width: '90px', btnStyle: 'advanced', advancedType: 'primary', showTooltip: false, caption: SDKUI_Localizator.Clear, icon: _jsx(IconClear, {}), advancedColor: 'white', color: 'primaryOutline', onClick: clearFilters }), (!showAdvancedSearch && qd?.where && qd.where.length > initialMaxItems) && (_jsx(TMButton, { width: '120px', btnStyle: isMobile ? 'icon' : 'advanced', advancedColor: TMColors.button_primary, caption: captionText, showTooltip: false, icon: isMobile ? (_jsx("div", { children: _jsx("p", { children: showAllMdWhere ? `-${diff}` : `+${diff}` }) })) : (_jsx("p", { children: showAllMdWhere ? `-${diff}` : `+${diff}` })), onClick: () => setShowAllMdWhere(!showAllMdWhere) }))] }), showFiltersConfig &&
|
|
357
378
|
_jsx(TMMetadataChooserForm, { allowMultipleSelection: true, height: '500px', width: '600px', allowSysMetadata: true, filterMetadata: (o => o.perm?.canSearch === AccessLevels.Yes), qd: qd, selectedIDs: qd?.where?.map((w) => ({ tid: w.tid, mid: w.mid })), onClose: handleCloseFiltersConfig, onChoose: handleChooseFilters }), showOutputConfig &&
|
|
358
379
|
_jsx(TMMetadataOutputForm, { qd: qd, selectedSelectItems: qd?.select, allowSysMetadata: true, filterMetadata: (o => o.perm?.canView === AccessLevels.Yes || o.perm?.canUpdate === AccessLevels.Yes), onClose: handleCloseOutputConfig, onChoose: (selectItems) => {
|
|
359
380
|
setQd({ ...qd, select: selectItems });
|
|
@@ -76,6 +76,7 @@ export declare class SDKUI_Localizator {
|
|
|
76
76
|
static get CustomButtonAction(): string;
|
|
77
77
|
static get CustomButtonActions(): string;
|
|
78
78
|
static get Back(): "Zurück" | "Back" | "Atrás" | "Dos" | "Voltar" | "Indietro";
|
|
79
|
+
static get BackToResult(): "Zum Ergebnis" | "Go to result" | "Ir al resultado" | "Aller au résultat" | "Ir para o resultado" | "Vai a risultato";
|
|
79
80
|
static get BatchUpdate(): "Mehrfachbearbeitung" | "Multiple modification" | "Modificación múltiple" | "Modifie multiple" | "Editar múltipla" | "Modifica multipla";
|
|
80
81
|
static get BlogCase(): "Anschlagbrett" | "Blog board" | "Tablón" | "Tableau d'affichage" | "Bakeca" | "Bacheca";
|
|
81
82
|
static get Blog_Read(): "Anzeigebrett lesen" | "Reading blog board" | "Lectura tablón" | "Lire le tableau d'affichage" | "Quadro de avisos leitura" | "Lettura bacheca";
|
|
@@ -717,6 +718,7 @@ export declare class SDKUI_Localizator {
|
|
|
717
718
|
static get ScanFeatureUnavailableInThisContext(): "Scanfunktionen sind in diesem Kontext nicht verfugbar." | "Scanning features are not available in this context." | "Las funciones de escaneo no estan disponibles en este contexto." | "Les fonctionnalites de numerisation ne sont pas disponibles dans ce contexte." | "Os recursos de digitalizacao nao estao disponiveis neste contexto." | "Funzionalita di scansione non disponibili in questo contesto.";
|
|
718
719
|
static get Search(): "Auf der Suche nach" | "Search" | "Búsqueda" | "Recherche" | "Pesquisa" | "Ricerca";
|
|
719
720
|
static get SearchAction(): "Search" | "Suche" | "Buscar" | "Chercher" | "Pesquisar" | "Cerca";
|
|
721
|
+
static get SearchAndAppend(): "Suchen und anhängen" | "Search and append" | "Buscar y añadir" | "Rechercher et ajouter" | "Pesquisar e acrescentar" | "Ricerca e accoda";
|
|
720
722
|
static get Search_Advanced(): "Erweiterte Suche" | "Advanced search" | "Búsqueda avanzada" | "Recherche avancée" | "Pesquisa Avançada" | "Ricerca avanzata";
|
|
721
723
|
static get Search_Easy(): "Einfache Suche" | "Easy search" | "Búsqueda fácil" | "Recherche facile" | "Pesquisa fácil" | "Ricerca facilitata";
|
|
722
724
|
static get Search_EnterValue(): "Geben Sie einen Wert ein, nach dem gesucht werden soll" | "Enter a value to search" | "Introducir un valor para buscar" | "Entrez une valeur à rechercher" | "Digite um valor para pesquisar" | "Inserire un valore da ricercare";
|
|
@@ -715,6 +715,16 @@ export class SDKUI_Localizator {
|
|
|
715
715
|
default: return "Indietro";
|
|
716
716
|
}
|
|
717
717
|
}
|
|
718
|
+
static get BackToResult() {
|
|
719
|
+
switch (this._cultureID) {
|
|
720
|
+
case CultureIDs.De_DE: return "Zum Ergebnis";
|
|
721
|
+
case CultureIDs.En_US: return "Go to result";
|
|
722
|
+
case CultureIDs.Es_ES: return "Ir al resultado";
|
|
723
|
+
case CultureIDs.Fr_FR: return "Aller au résultat";
|
|
724
|
+
case CultureIDs.Pt_PT: return "Ir para o resultado";
|
|
725
|
+
default: return "Vai a risultato";
|
|
726
|
+
}
|
|
727
|
+
}
|
|
718
728
|
static get BatchUpdate() {
|
|
719
729
|
switch (this._cultureID) {
|
|
720
730
|
case CultureIDs.De_DE: return "Mehrfachbearbeitung";
|
|
@@ -7149,6 +7159,16 @@ export class SDKUI_Localizator {
|
|
|
7149
7159
|
default: return "Cerca";
|
|
7150
7160
|
}
|
|
7151
7161
|
}
|
|
7162
|
+
static get SearchAndAppend() {
|
|
7163
|
+
switch (this._cultureID) {
|
|
7164
|
+
case CultureIDs.De_DE: return "Suchen und anhängen";
|
|
7165
|
+
case CultureIDs.En_US: return "Search and append";
|
|
7166
|
+
case CultureIDs.Es_ES: return "Buscar y añadir";
|
|
7167
|
+
case CultureIDs.Fr_FR: return "Rechercher et ajouter";
|
|
7168
|
+
case CultureIDs.Pt_PT: return "Pesquisar e acrescentar";
|
|
7169
|
+
default: return "Ricerca e accoda";
|
|
7170
|
+
}
|
|
7171
|
+
}
|
|
7152
7172
|
static get Search_Advanced() {
|
|
7153
7173
|
switch (this._cultureID) {
|
|
7154
7174
|
case CultureIDs.De_DE: return "Erweiterte Suche";
|
package/lib/helper/helpers.d.ts
CHANGED
|
@@ -35,6 +35,17 @@ export declare const getDataColumnName: (fromTID: number | undefined, dtColumn:
|
|
|
35
35
|
*/
|
|
36
36
|
export declare const generateUniqueColumnKeys: (columns: DataColumnDescriptor[] | undefined, fromTID: number | undefined) => string[];
|
|
37
37
|
export declare const searchResultDescriptorToSimpleArray: (searchResult: SearchResultDescriptor | undefined) => any[] | undefined;
|
|
38
|
+
/**
|
|
39
|
+
* Unisce ("accoda") il risultato di una nuova ricerca a quello precedente, sullo stesso tipo documento.
|
|
40
|
+
* - Le righe della nuova ricerca vengono messe in cima (più recenti in alto)
|
|
41
|
+
* - Le righe duplicate sono accettate: lo stesso documento può comparire più volte
|
|
42
|
+
* - Aggiorna i contatori globali (dcmtsFound / dcmtsReturned) sul totale unito
|
|
43
|
+
*
|
|
44
|
+
* @param previous Risultato attualmente in memoria (può essere undefined al primo giro)
|
|
45
|
+
* @param next Nuovo risultato appena ottenuto
|
|
46
|
+
* @returns Un nuovo SearchResultDescriptor con le righe unite, oppure il singolo risultato disponibile
|
|
47
|
+
*/
|
|
48
|
+
export declare const mergeSearchResultsAppend: (previous: SearchResultDescriptor | undefined, next: SearchResultDescriptor | undefined) => SearchResultDescriptor | undefined;
|
|
38
49
|
export declare const getCompleteMetadataName: (dcmtTypeName: string | undefined, metadataName: string | undefined) => string;
|
|
39
50
|
export declare const getQueryCountAsync: (qd: QueryDescriptor, showSpinner: boolean) => Promise<void>;
|
|
40
51
|
export declare function getTIDByMID(mid: number | undefined, defaultTid?: number): number;
|
package/lib/helper/helpers.js
CHANGED
|
@@ -239,6 +239,38 @@ export const searchResultDescriptorToSimpleArray = (searchResult) => {
|
|
|
239
239
|
});
|
|
240
240
|
return result;
|
|
241
241
|
};
|
|
242
|
+
/**
|
|
243
|
+
* Unisce ("accoda") il risultato di una nuova ricerca a quello precedente, sullo stesso tipo documento.
|
|
244
|
+
* - Le righe della nuova ricerca vengono messe in cima (più recenti in alto)
|
|
245
|
+
* - Le righe duplicate sono accettate: lo stesso documento può comparire più volte
|
|
246
|
+
* - Aggiorna i contatori globali (dcmtsFound / dcmtsReturned) sul totale unito
|
|
247
|
+
*
|
|
248
|
+
* @param previous Risultato attualmente in memoria (può essere undefined al primo giro)
|
|
249
|
+
* @param next Nuovo risultato appena ottenuto
|
|
250
|
+
* @returns Un nuovo SearchResultDescriptor con le righe unite, oppure il singolo risultato disponibile
|
|
251
|
+
*/
|
|
252
|
+
export const mergeSearchResultsAppend = (previous, next) => {
|
|
253
|
+
// Se manca uno dei due, non c'è nulla da unire
|
|
254
|
+
if (!next?.dtdResult?.rows?.length)
|
|
255
|
+
return previous;
|
|
256
|
+
if (!previous?.dtdResult?.rows?.length)
|
|
257
|
+
return next;
|
|
258
|
+
// L'accodamento ha senso solo sullo stesso tipo documento (colonne compatibili)
|
|
259
|
+
if (previous.fromTID !== next.fromTID)
|
|
260
|
+
return next;
|
|
261
|
+
const prevRows = previous.dtdResult.rows ?? [];
|
|
262
|
+
const nextRows = next.dtdResult.rows ?? [];
|
|
263
|
+
// Nessuna deduplica: le nuove righe in cima, poi le precedenti (i duplicati sono ammessi)
|
|
264
|
+
const mergedRows = [...nextRows, ...prevRows];
|
|
265
|
+
// Costruisce il nuovo descrittore basandosi sull'ultimo risultato (colonne/qd più recenti), con le righe unite
|
|
266
|
+
const merged = structuredClone(next);
|
|
267
|
+
const mergedDtd = structuredClone(next.dtdResult);
|
|
268
|
+
mergedDtd.rows = mergedRows;
|
|
269
|
+
merged.dtdResult = mergedDtd;
|
|
270
|
+
merged.dcmtsReturned = mergedRows.length;
|
|
271
|
+
merged.dcmtsFound = mergedRows.length;
|
|
272
|
+
return merged;
|
|
273
|
+
};
|
|
242
274
|
export const getCompleteMetadataName = (dcmtTypeName, metadataName) => `${dcmtTypeName}...${metadataName}`;
|
|
243
275
|
export const getQueryCountAsync = async (qd, showSpinner) => {
|
|
244
276
|
try {
|