@topconsultnpm/sdkui-react 6.22.0-dev2.16 → 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.
Files changed (59) hide show
  1. package/lib/components/base/TMEditorBase.d.ts +2 -0
  2. package/lib/components/base/TMTooltip.d.ts +2 -1
  3. package/lib/components/base/TMTooltip.js +23 -6
  4. package/lib/components/choosers/TMDcmtTypeChooser.js +4 -3
  5. package/lib/components/choosers/TMDistinctValues.d.ts +9 -7
  6. package/lib/components/choosers/TMDistinctValues.js +147 -195
  7. package/lib/components/choosers/TMMetadataChooser.js +1 -1
  8. package/lib/components/choosers/TMQuickSearchInfo.d.ts +12 -0
  9. package/lib/components/choosers/TMQuickSearchInfo.js +279 -0
  10. package/lib/components/choosers/TMQuickSearchSettingsForm.d.ts +16 -0
  11. package/lib/components/choosers/TMQuickSearchSettingsForm.js +94 -0
  12. package/lib/components/choosers/TMSelectedValuesSummary.d.ts +13 -0
  13. package/lib/components/choosers/TMSelectedValuesSummary.js +91 -0
  14. package/lib/components/editors/TMDropDown.d.ts +3 -0
  15. package/lib/components/editors/TMDropDown.js +197 -5
  16. package/lib/components/features/documents/TMDcmtForm.js +10 -2
  17. package/lib/components/features/documents/TMDcmtFormActionButtons.d.ts +11 -0
  18. package/lib/components/features/documents/TMDcmtFormActionButtons.js +74 -7
  19. package/lib/components/features/search/TMSearchResult.js +55 -4
  20. package/lib/components/features/workflow/TMWorkflowPopup.d.ts +10 -0
  21. package/lib/components/features/workflow/TMWorkflowPopup.js +8 -4
  22. package/lib/components/viewers/TMMidViewer.js +4 -1
  23. package/lib/components/viewers/TMTidViewer.js +25 -11
  24. package/lib/helper/SDKUI_Globals.d.ts +20 -0
  25. package/lib/helper/SDKUI_Globals.js +44 -0
  26. package/lib/helper/SDKUI_Localizator.d.ts +1 -0
  27. package/lib/helper/SDKUI_Localizator.js +10 -0
  28. package/lib/helper/dataGridSearchHelper.d.ts +28 -0
  29. package/lib/helper/dataGridSearchHelper.js +51 -0
  30. package/lib/helper/index.d.ts +1 -0
  31. package/lib/helper/index.js +1 -0
  32. package/lib/helper/queryHelper.d.ts +2 -0
  33. package/lib/helper/queryHelper.js +3 -1
  34. package/lib/hooks/tmDistinctValuesGridHelper.d.ts +16 -0
  35. package/lib/hooks/tmDistinctValuesGridHelper.js +31 -0
  36. package/lib/hooks/useCaseFlowApprove.d.ts +56 -0
  37. package/lib/hooks/useCaseFlowApprove.js +157 -0
  38. package/lib/hooks/useTMDistinctValuesMetadataDisplay.d.ts +18 -0
  39. package/lib/hooks/useTMDistinctValuesMetadataDisplay.js +103 -0
  40. package/lib/hooks/useTMDistinctValuesQuickSearch.d.ts +49 -0
  41. package/lib/hooks/useTMDistinctValuesQuickSearch.js +307 -0
  42. package/lib/hooks/useTMDistinctValuesSelection.d.ts +42 -0
  43. package/lib/hooks/useTMDistinctValuesSelection.js +103 -0
  44. package/lib/hooks/useTMDistinctValuesSource.d.ts +29 -0
  45. package/lib/hooks/useTMDistinctValuesSource.js +105 -0
  46. package/lib/index.d.ts +1 -0
  47. package/lib/index.js +1 -0
  48. package/lib/services/caseflow/CaseFlowService.d.ts +208 -0
  49. package/lib/services/caseflow/CaseFlowService.js +623 -0
  50. package/lib/services/caseflow/CaseFlowTemplateCacheService.d.ts +28 -0
  51. package/lib/services/caseflow/CaseFlowTemplateCacheService.js +121 -0
  52. package/lib/services/caseflow/caseFlowNameCache.d.ts +32 -0
  53. package/lib/services/caseflow/caseFlowNameCache.js +116 -0
  54. package/lib/services/caseflow/caseFlowWorkItemUtils.d.ts +61 -0
  55. package/lib/services/caseflow/caseFlowWorkItemUtils.js +113 -0
  56. package/lib/services/index.d.ts +4 -0
  57. package/lib/services/index.js +4 -0
  58. package/lib/services/platform_services.d.ts +1 -1
  59. 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 styled from "styled-components";
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
- const TMDropDown = ({ validationItems = [], disabled = false, elementStyle, isModifiedWhen, labelPosition = 'left', label, icon, width = '100%', fontSize = FontSize.defaultFontSize, dataSource, value, onValueChanged, buttons = [], placeHolder, }) => {
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 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, { "$width": width, children: [_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)))] }), buttons.length > 0 && (_jsx(ButtonsContainer, { children: buttons.map((buttonItem) => (_jsx(StyledDropDownEditorButton, { onClick: buttonItem.onClick, children: _jsx(TMTooltip, { content: buttonItem.text, children: buttonItem.icon }) }, buttonItem.text))) })), _jsx(CustomArrow, {})] }), _jsx(TMVilViewer, { vil: validationItems })] }) })] }));
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;
@@ -26,6 +26,7 @@ import { useTMPanelManagerContext } from '../../layout/panelManager/TMPanelManag
26
26
  import TMPanelManagerContainer from '../../layout/panelManager/TMPanelManagerContainer';
27
27
  import { TMPanelManagerWithPersistenceProvider } from '../../layout/panelManager/TMPanelManagerWithPersistenceProvider';
28
28
  import { useWorkflowApprove } from '../../../hooks/useWorkflowApprove';
29
+ import { useCaseFlowApprove } from '../../../hooks/useCaseFlowApprove';
29
30
  import TMBlogCommentForm from '../blog/TMBlogCommentForm';
30
31
  import WFDiagram from '../workflow/diagram/WFDiagram';
31
32
  import TMTooltip from '../../base/TMTooltip';
@@ -116,6 +117,10 @@ const TMDcmtForm = ({ TID, DID, groupId, layoutMode = LayoutModes.Update, formMo
116
117
  const [wfError, setWfError] = useState(null);
117
118
  const [metadataDcmtOrigin, setMetadataDcmtOrigin] = useState(null);
118
119
  const { workflowApproveData, getWorkItemsByDID } = useWorkflowApprove();
120
+ // Attività delle PRATICHE (CaseFlow) su questo documento. Come per il workflow
121
+ // la scoperta avviene QUI: da qualunque punto si apra il form, l'utente trova
122
+ // le sue azioni nel Toppy.
123
+ const { caseFlowWorkItems, completeCaseFlowWorkItem, rejectCaseFlowWorkItem } = useCaseFlowApprove(TID, DID);
119
124
  // Get the current device type (e.g., mobile, tablet, desktop) using a custom hook.
120
125
  const deviceType = useDeviceType();
121
126
  // This avoids unnecessary re-renders by only recalculating when deviceType changes.
@@ -804,7 +809,10 @@ const TMDcmtForm = ({ TID, DID, groupId, layoutMode = LayoutModes.Update, formMo
804
809
  // Verifica che ci siano riferimenti renderizzabili (solo Dossier o WorkingGroup)
805
810
  return dcmtReferences.some(ref => ref.objClass === ObjectClasses.Dossier || ref.objClass === ObjectClasses.WorkingGroup);
806
811
  }, [allowButtonsRefs, layoutMode, dcmtReferences, isOpenDetails, isOpenMaster]);
807
- const isToppyVisible = useMemo(() => Boolean((showToppyForApprove || showToppyForCompleteMoreInfo || showToppyForReferences) && !openS4TViewer && !showTodoDcmtForm && !editPdfForm), [showToppyForApprove, showToppyForCompleteMoreInfo, showToppyForReferences, openS4TViewer, showTodoDcmtForm, editPdfForm]);
812
+ // Attività di pratica: stesse condizioni dell'approvazione workflow (documento
813
+ // in consultazione, senza errori, non dentro master/detail).
814
+ const showToppyForCaseFlow = useMemo(() => layoutMode === LayoutModes.Update && !fetchError && caseFlowWorkItems.length > 0 && !isOpenDetails && !isOpenMaster, [layoutMode, fetchError, caseFlowWorkItems.length, isOpenDetails, isOpenMaster]);
815
+ const isToppyVisible = useMemo(() => Boolean((showToppyForApprove || showToppyForCompleteMoreInfo || showToppyForReferences || showToppyForCaseFlow) && !openS4TViewer && !showTodoDcmtForm && !editPdfForm), [showToppyForApprove, showToppyForCompleteMoreInfo, showToppyForReferences, showToppyForCaseFlow, openS4TViewer, showTodoDcmtForm, editPdfForm]);
808
816
  const isModified = useMemo(() => calcIsModified(formData, formDataOrig), [formData, formDataOrig]);
809
817
  const formToolbar = useMemo(() => _jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: '10px' }, children: [allowNavigation && canPrev != undefined && _jsx("p", { style: { textAlign: 'center', padding: '1px 4px', display: 'flex' }, children: `${itemIndex}/${count}` }), allowNavigation && canPrev != undefined && _jsx(TMSaveFormButtonPrevious, { btnStyle: 'icon', iconColor: 'white', isModified: isModified, formMode: formMode, canPrev: canPrev, onPrev: onPrev }), allowNavigation && canNext != undefined && _jsx(TMSaveFormButtonNext, { btnStyle: 'icon', iconColor: 'white', isModified: isModified, formMode: formMode, canNext: canNext, onNext: onNext }), layoutMode === LayoutModes.Update &&
810
818
  _jsx(ContextMenu, { items: operationItems, trigger: "left", children: _jsx(IconMenuVertical, { color: 'white', cursor: 'pointer' }) }), layoutMode === LayoutModes.Ark &&
@@ -1571,7 +1579,7 @@ const TMDcmtForm = ({ TID, DID, groupId, layoutMode = LayoutModes.Update, formMo
1571
1579
  isEditable: true,
1572
1580
  value: FormulaHelper.addFormulaTag(newFormula.expression)
1573
1581
  }));
1574
- } }), (isModal && onClose) && _jsx("div", { id: "TMDcmtFormShowConfirmForClose-" + id })] }) }), _jsx(TMToppyDraggableHelpCenter, { isVisible: isToppyVisible, content: _jsx(TMDcmtFormActionButtons, { showToppyForApprove: showToppyForApprove, workItems: workItems, deviceType: deviceType, isMobile: isMobile, handleSignApprove: handleSignApprove, updateShowApprovePopup: updateShowApprovePopup, updateShowRejectPopup: updateShowRejectPopup, updateShowReAssignPopup: updateShowReAssignPopup, updateShowMoreInfoPopup: updateShowMoreInfoPopup, fromDTD: fromDTD, showToppyForCompleteMoreInfo: showToppyForCompleteMoreInfo, moreInfoTasks: moreInfoTasks, setShowCommentForm: setShowCommentForm, showToppyForReferences: showToppyForReferences, dcmtReferences: dcmtReferences, referenceActionMap: referenceActionMap, handleNavigateToReference: handleNavigateToReference, setShowMoreInfoTaskPopup: setShowMoreInfoTaskPopup, setShowMoreInfoTaskTask: setShowMoreInfoTaskTask }) })] }), (showCommentForm && TID && DID) &&
1582
+ } }), (isModal && onClose) && _jsx("div", { id: "TMDcmtFormShowConfirmForClose-" + id })] }) }), _jsx(TMToppyDraggableHelpCenter, { isVisible: isToppyVisible, content: _jsx(TMDcmtFormActionButtons, { showToppyForApprove: showToppyForApprove, workItems: workItems, caseFlowWorkItems: showToppyForCaseFlow ? caseFlowWorkItems : [], onCompleteCaseFlowWorkItem: completeCaseFlowWorkItem, onRejectCaseFlowWorkItem: rejectCaseFlowWorkItem, deviceType: deviceType, isMobile: isMobile, handleSignApprove: handleSignApprove, updateShowApprovePopup: updateShowApprovePopup, updateShowRejectPopup: updateShowRejectPopup, updateShowReAssignPopup: updateShowReAssignPopup, updateShowMoreInfoPopup: updateShowMoreInfoPopup, fromDTD: fromDTD, showToppyForCompleteMoreInfo: showToppyForCompleteMoreInfo, moreInfoTasks: moreInfoTasks, setShowCommentForm: setShowCommentForm, showToppyForReferences: showToppyForReferences, dcmtReferences: dcmtReferences, referenceActionMap: referenceActionMap, handleNavigateToReference: handleNavigateToReference, setShowMoreInfoTaskPopup: setShowMoreInfoTaskPopup, setShowMoreInfoTaskTask: setShowMoreInfoTaskTask }) })] }), (showCommentForm && TID && DID) &&
1575
1583
  _jsx(TMBlogCommentForm, { maxLength: 500, context: blogCommentFormContext, onClose: handleBlogCommentFormClose, onCustomSave: onBlogCommentFormCustomSave, participants: emptyParticipants, showAttachmentsSection: false, allArchivedDocumentsFileItems: emptyArchivedDocumentsFileItems })] }), renderDcmtOperations, renderFloatingBar] }));
1576
1584
  };
1577
1585
  return (_jsxs("div", { style: { width: '100%', height: '100%' }, children: [(isModal && onClose)
@@ -2,9 +2,20 @@ import React from "react";
2
2
  import { DcmtTypeDescriptor, TaskDescriptor, ObjectRef } from "@topconsultnpm/sdk-ts";
3
3
  import { IWorkItemData } from "../../../ts";
4
4
  import { DeviceType } from "../../base/TMDeviceProvider";
5
+ import { CaseFlowDcmtWorkItem } from "../../../hooks/useCaseFlowApprove";
5
6
  interface TMDcmtFormActionButtonsProps {
6
7
  showToppyForApprove: boolean;
7
8
  workItems: Array<IWorkItemData>;
9
+ /** Attività delle PRATICHE (CaseFlow) pendenti su QUESTO documento. */
10
+ caseFlowWorkItems?: Array<CaseFlowDcmtWorkItem>;
11
+ /**
12
+ * Completa l'attività di pratica. NON usato per la FIRMA: là si apre il viewer
13
+ * di firma ed è quello, a firma riuscita, a completare — esattamente come per
14
+ * i work item del workflow.
15
+ */
16
+ onCompleteCaseFlowWorkItem?: (item: CaseFlowDcmtWorkItem, comment?: string) => Promise<void>;
17
+ /** Rifiuta l'attività di pratica con un motivo. */
18
+ onRejectCaseFlowWorkItem?: (item: CaseFlowDcmtWorkItem, reason: string) => Promise<void>;
8
19
  deviceType: DeviceType | undefined;
9
20
  isMobile: boolean;
10
21
  handleSignApprove: () => void;
@@ -1,18 +1,34 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useCallback, useMemo, useRef, useState } from "react";
3
3
  import { ObjectClasses, SDK_Globals } from "@topconsultnpm/sdk-ts";
4
- import { TASK_MORE_INFO_PREFIX_NAME, SDKUI_Localizator, PDGS_COLORS, calcResponsiveSizes } from "../../../helper";
4
+ import { TASK_MORE_INFO_PREFIX_NAME, SDKUI_Localizator, PDGS_COLORS, calcResponsiveSizes, isSign4TopEnabled } from "../../../helper";
5
5
  import { TMColors } from "../../../utils/theme";
6
6
  import { StyledReferenceButton } from "../../base/Styled";
7
7
  import TMButton from "../../base/TMButton";
8
8
  import TMModal from "../../base/TMModal";
9
9
  import TMTooltip from "../../base/TMTooltip";
10
10
  import { WorkFlowOperationButtons } from "../workflow/TMWorkflowPopup";
11
+ import { getWorkItemActionName, isSignatureWorkItem } from "../../../services/caseflow/caseFlowWorkItemUtils";
11
12
  const TMDcmtFormActionButtons = (props) => {
12
- const { showToppyForApprove, workItems, deviceType, isMobile, handleSignApprove, updateShowApprovePopup, updateShowRejectPopup, updateShowReAssignPopup, updateShowMoreInfoPopup, fromDTD, showToppyForCompleteMoreInfo, moreInfoTasks, setShowCommentForm, showToppyForReferences, dcmtReferences, referenceActionMap, handleNavigateToReference, setShowMoreInfoTaskPopup, setShowMoreInfoTaskTask } = props;
13
+ const { showToppyForApprove, workItems, caseFlowWorkItems, onCompleteCaseFlowWorkItem, onRejectCaseFlowWorkItem, deviceType, isMobile, handleSignApprove, updateShowApprovePopup, updateShowRejectPopup, updateShowReAssignPopup, updateShowMoreInfoPopup, fromDTD, showToppyForCompleteMoreInfo, moreInfoTasks, setShowCommentForm, showToppyForReferences, dcmtReferences, referenceActionMap, handleNavigateToReference, setShowMoreInfoTaskPopup, setShowMoreInfoTaskTask } = props;
13
14
  const [showAllMoreInfoModal, setShowAllMoreInfoModal] = useState(false);
14
15
  const [showAllDossiersModal, setShowAllDossiersModal] = useState(false);
15
16
  const [showAllWorkingGroupsModal, setShowAllWorkingGroupsModal] = useState(false);
17
+ // Rifiuto di un'attività di pratica: il motivo è obbligatorio, quindi si passa
18
+ // da un dialogo. `busyItemId` disabilita i pulsanti dell'attività in corso.
19
+ const [rejectingItem, setRejectingItem] = useState(null);
20
+ const [rejectReason, setRejectReason] = useState('');
21
+ const [busyItemId, setBusyItemId] = useState(null);
22
+ const cfItems = useMemo(() => caseFlowWorkItems ?? [], [caseFlowWorkItems]);
23
+ const runCaseFlowAction = useCallback(async (item, action) => {
24
+ setBusyItemId(item.workItem.id ?? null);
25
+ try {
26
+ await action();
27
+ }
28
+ finally {
29
+ setBusyItemId(null);
30
+ }
31
+ }, []);
16
32
  const tasksNumber = useMemo(() => moreInfoTasks?.length ?? 0, [moreInfoTasks]);
17
33
  const firstTask = useMemo(() => {
18
34
  if (!moreInfoTasks || moreInfoTasks.length === 0)
@@ -31,19 +47,63 @@ const TMDcmtFormActionButtons = (props) => {
31
47
  return false;
32
48
  return true;
33
49
  }, [firstTask]);
34
- const { hasMoreInfo, hasApprove, hasReferences } = useMemo(() => {
50
+ const { hasMoreInfo, hasApprove, hasReferences, hasCaseFlow } = useMemo(() => {
35
51
  const referencesExist = showToppyForReferences && dcmtReferences?.some(ref => ref.objClass === ObjectClasses.Dossier || ref.objClass === ObjectClasses.WorkingGroup);
36
52
  return {
37
53
  hasMoreInfo: Boolean(showToppyForCompleteMoreInfo),
38
54
  hasApprove: Boolean(showToppyForApprove),
39
- hasReferences: Boolean(referencesExist)
55
+ hasReferences: Boolean(referencesExist),
56
+ hasCaseFlow: cfItems.length > 0
40
57
  };
41
58
  }, [
42
59
  showToppyForCompleteMoreInfo,
43
60
  showToppyForApprove,
44
61
  showToppyForReferences,
45
- dcmtReferences
62
+ dcmtReferences,
63
+ cfItems
46
64
  ]);
65
+ /**
66
+ * Attività di pratica: contesto (pratica + testo dell'attività) e gli STESSI
67
+ * pulsanti dei work item del workflow (`WorkFlowOperationButtons`), così le due
68
+ * provenienze si comportano allo stesso modo davanti all'utente.
69
+ *
70
+ * La forma dei pulsanti la decide la CATEGORIA dell'attività, non il tipo
71
+ * documento: un'approvazione su un tipo abilitato alla firma non deve mostrare
72
+ * "Firma e approva" (per questo si passa `signFlow`). La firma richiede comunque
73
+ * il widget Sign4Top sul tipo, altrimenti il viewer non avrebbe come aprirsi.
74
+ *
75
+ * Per la FIRMA il pulsante apre il viewer (`handleSignApprove`, lo stesso del
76
+ * workflow) e il completamento avviene lì a firma riuscita: il form non sa
77
+ * firmare, e completare qui "a fiducia" chiuderebbe l'attività su un documento
78
+ * non firmato.
79
+ */
80
+ const renderCaseFlowWorkItemCard = (item) => {
81
+ const { caseflow, workItem } = item;
82
+ const isBusy = busyItemId === workItem.id;
83
+ const canSign = !!fromDTD?.widgets && isSign4TopEnabled(fromDTD.widgets);
84
+ const requiresSignature = isSignatureWorkItem(workItem) && canSign;
85
+ const description = workItem.description || getWorkItemActionName(caseflow, workItem);
86
+ return (_jsxs("div", { style: {
87
+ display: 'flex',
88
+ flexDirection: 'column',
89
+ alignItems: 'center',
90
+ textAlign: 'center',
91
+ gap: '10px',
92
+ cursor: 'default',
93
+ }, children: [_jsx("div", { style: {
94
+ padding: '8px 10px',
95
+ maxWidth: '240px',
96
+ color: '#FFFFFF',
97
+ background: 'linear-gradient(135deg, #7E57C2 0%, #5E35B1 60%, #4527A0 100%)',
98
+ border: '1px solid rgba(255,255,255,0.15)',
99
+ borderRadius: 10,
100
+ boxShadow: '0 8px 20px rgba(0, 0, 50, 0.4)',
101
+ }, children: description && _jsx("div", { style: { whiteSpace: 'pre-line' }, children: description }) }), _jsx(WorkFlowOperationButtons, { dtd: fromDTD, deviceType: deviceType, signFlow: requiresSignature, approveDisable: isBusy, signApproveDisable: isBusy, rejectDisable: isBusy, onSignApprove: handleSignApprove, onApprove: onCompleteCaseFlowWorkItem
102
+ ? () => void runCaseFlowAction(item, () => onCompleteCaseFlowWorkItem(item))
103
+ : undefined, onReject: onRejectCaseFlowWorkItem
104
+ ? () => { setRejectReason(''); setRejectingItem(item); }
105
+ : undefined })] }, workItem.id));
106
+ };
47
107
  // Raggruppa references per categoria
48
108
  const { dossierRefs, workingGroupRefs } = useMemo(() => {
49
109
  const dossiers = dcmtReferences?.filter(ref => ref.objClass === ObjectClasses.Dossier) ?? [];
@@ -171,10 +231,17 @@ const TMDcmtFormActionButtons = (props) => {
171
231
  e.currentTarget.style.transform = 'translateY(-50%)';
172
232
  }, children: formatBadgeCount(othersCount) }))] }));
173
233
  };
174
- return (_jsxs(_Fragment, { children: [_jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: '10px' }, children: [hasMoreInfo && firstTask && isMoreInfoCardVisible && (_jsx("div", { style: { display: 'flex', gap: "10px", flexDirection: 'column', alignItems: 'center', paddingRight: tasksNumber > 1 ? '36px' : '0' }, children: renderMoreInfoCard(firstTask, tasksNumber > 1) })), hasMoreInfo && isMoreInfoCardVisible && hasApprove && _jsx(Divider, {}), hasApprove && (workItems.length === 1 ?
234
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: '10px' }, children: [hasMoreInfo && firstTask && isMoreInfoCardVisible && (_jsx("div", { style: { display: 'flex', gap: "10px", flexDirection: 'column', alignItems: 'center', paddingRight: tasksNumber > 1 ? '36px' : '0' }, children: renderMoreInfoCard(firstTask, tasksNumber > 1) })), hasMoreInfo && isMoreInfoCardVisible && hasCaseFlow && _jsx(Divider, {}), hasCaseFlow && (_jsx("div", { style: { display: 'flex', flexDirection: 'column', gap: '10px', alignItems: 'center' }, children: cfItems.map(item => renderCaseFlowWorkItemCard(item)) })), hasCaseFlow && hasApprove && _jsx(Divider, {}), hasMoreInfo && isMoreInfoCardVisible && !hasCaseFlow && hasApprove && _jsx(Divider, {}), hasApprove && (workItems.length === 1 ?
175
235
  _jsx(WorkFlowOperationButtons, { dtd: fromDTD, deviceType: deviceType, onApprove: () => updateShowApprovePopup(true), onSignApprove: handleSignApprove, onReject: () => updateShowRejectPopup(true), onReAssign: () => updateShowReAssignPopup(true), onMoreInfo: () => updateShowMoreInfoPopup(true) })
176
236
  :
177
- _jsxs("div", { style: { padding: 10, color: 'white', maxWidth: '180px', borderRadius: 10, background: '#1B1464 0% 0% no-repeat padding-box', border: '1px solid #FFFFFF' }, children: [`Questo documento è associato a ${workItems.length} workitem.`, _jsx("br", {}), `Per approvare, vai alla pagina "Approvazione workflow".`] })), (hasApprove && hasReferences) || (hasMoreInfo && isMoreInfoCardVisible && !hasApprove && hasReferences) ? (_jsx(Divider, {})) : null, hasReferences && (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: '10px', alignItems: 'center' }, children: [dossierRefs.length > 0 && (_jsx("div", { style: { paddingRight: dossierRefs.length > 1 ? '36px' : '0' }, children: renderReferenceCard(dossierRefs[0], dossierRefs, dossierRefs.length > 1, () => setShowAllDossiersModal(true)) })), workingGroupRefs.length > 0 && (_jsx("div", { style: { paddingRight: workingGroupRefs.length > 1 ? '36px' : '0' }, children: renderReferenceCard(workingGroupRefs[0], workingGroupRefs, workingGroupRefs.length > 1, () => setShowAllWorkingGroupsModal(true)) }))] }))] }), showAllMoreInfoModal && moreInfoTasks && moreInfoTasks.length > 1 && (_jsx(TMModal, { title: `Altre richieste maggiori informazioni (${moreInfoTasks.length - 1})`, onClose: () => setShowAllMoreInfoModal(false), width: calcResponsiveSizes(deviceType, '450px', '450px', '95%'), height: 'auto', children: _jsx("div", { style: {
237
+ _jsxs("div", { style: { padding: 10, color: 'white', maxWidth: '180px', borderRadius: 10, background: '#1B1464 0% 0% no-repeat padding-box', border: '1px solid #FFFFFF' }, children: [`Questo documento è associato a ${workItems.length} workitem.`, _jsx("br", {}), `Per approvare, vai alla pagina "Approvazione workflow".`] })), (hasApprove && hasReferences) || (hasMoreInfo && isMoreInfoCardVisible && !hasApprove && hasReferences) ? (_jsx(Divider, {})) : null, hasReferences && (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: '10px', alignItems: 'center' }, children: [dossierRefs.length > 0 && (_jsx("div", { style: { paddingRight: dossierRefs.length > 1 ? '36px' : '0' }, children: renderReferenceCard(dossierRefs[0], dossierRefs, dossierRefs.length > 1, () => setShowAllDossiersModal(true)) })), workingGroupRefs.length > 0 && (_jsx("div", { style: { paddingRight: workingGroupRefs.length > 1 ? '36px' : '0' }, children: renderReferenceCard(workingGroupRefs[0], workingGroupRefs, workingGroupRefs.length > 1, () => setShowAllWorkingGroupsModal(true)) }))] }))] }), rejectingItem && onRejectCaseFlowWorkItem && (_jsx(TMModal, { title: rejectingItem.workItem.rejectLabel || SDKUI_Localizator.Reject, onClose: () => { setRejectingItem(null); setRejectReason(''); }, width: calcResponsiveSizes(deviceType, '450px', '450px', '95%'), height: 'auto', children: _jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 12, padding: 15 }, children: [_jsx("div", { style: { fontSize: 13, color: '#555' }, children: `${SDKUI_Localizator.Description}:` }), _jsx("textarea", { value: rejectReason, onChange: (e) => setRejectReason(e.target.value), rows: 4, style: { width: '100%', resize: 'vertical', fontFamily: 'inherit', fontSize: 13, padding: 8 } }), _jsxs("div", { style: { display: 'flex', justifyContent: 'flex-end', gap: 8 }, children: [_jsx(TMButton, { caption: SDKUI_Localizator.Cancel, showTooltip: false, onClick: () => { setRejectingItem(null); setRejectReason(''); } }), _jsx(TMButton, { caption: rejectingItem.workItem.rejectLabel || SDKUI_Localizator.Reject, color: 'error', showTooltip: false, disabled: !rejectReason.trim() || busyItemId === rejectingItem.workItem.id, onClick: () => {
238
+ const item = rejectingItem;
239
+ void runCaseFlowAction(item, async () => {
240
+ await onRejectCaseFlowWorkItem(item, rejectReason.trim());
241
+ setRejectingItem(null);
242
+ setRejectReason('');
243
+ });
244
+ } })] })] }) })), showAllMoreInfoModal && moreInfoTasks && moreInfoTasks.length > 1 && (_jsx(TMModal, { title: `Altre richieste maggiori informazioni (${moreInfoTasks.length - 1})`, onClose: () => setShowAllMoreInfoModal(false), width: calcResponsiveSizes(deviceType, '450px', '450px', '95%'), height: 'auto', children: _jsx("div", { style: {
178
245
  display: 'flex',
179
246
  flexDirection: 'column',
180
247
  gap: '8px',
@@ -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([
@@ -3,6 +3,16 @@ import { DeviceType } from "../../base/TMDeviceProvider";
3
3
  import { WorkItemDetail } from "../../../ts";
4
4
  interface IWorkflowOperationButtonsProps {
5
5
  dtd: DcmtTypeDescriptor | undefined;
6
+ /**
7
+ * Forza la forma dei pulsanti: `true` = flusso di FIRMA (Firma e approva),
8
+ * `false` = flusso di APPROVAZIONE. Se omesso si deduce dal tipo documento
9
+ * (widget Sign4Top), che è il comportamento del workflow.
10
+ *
11
+ * Serve a chi conosce la natura dell'attività meglio del tipo documento: un
12
+ * work item di sola approvazione su un tipo ABILITATO alla firma non deve
13
+ * mostrare "Firma e approva".
14
+ */
15
+ signFlow?: boolean;
6
16
  approveDisable?: boolean;
7
17
  signApproveDisable?: boolean;
8
18
  rejectDisable?: boolean;
@@ -53,16 +53,20 @@ const StyledHorizontalContainer = styled.div `
53
53
  /* align-items: flex-end; */
54
54
  `;
55
55
  export const WorkFlowOperationButtons = (props) => {
56
- const { dtd = undefined, deviceType = DeviceType.DESKTOP, approveDisable = false, signApproveDisable = false, rejectDisable = false, reassignDisable = false, infoDisable = false, onApprove, onSignApprove, onReject, onReAssign, onMoreInfo, } = props;
56
+ const { dtd = undefined, signFlow = undefined, deviceType = DeviceType.DESKTOP, approveDisable = false, signApproveDisable = false, rejectDisable = false, reassignDisable = false, infoDisable = false, onApprove, onSignApprove, onReject, onReAssign, onMoreInfo, } = props;
57
57
  const isMobile = deviceType === DeviceType.MOBILE;
58
- // Calcola direttamente se è un workflow di firma per evitare flickering durante il render
58
+ // Calcola direttamente se è un workflow di firma per evitare flickering durante il render.
59
+ // `signFlow`, se fornito, ha la precedenza: chi lo passa conosce la natura
60
+ // dell'attività (es. categoria del work item) meglio del tipo documento.
59
61
  const isSignWorkflow = useMemo(() => {
62
+ if (signFlow !== undefined)
63
+ return signFlow;
60
64
  if (dtd && dtd.widgets && dtd.widgets.length > 0) {
61
65
  return isSign4TopEnabled(dtd.widgets);
62
66
  }
63
67
  return false;
64
- }, [dtd]);
65
- return (_jsx(StyledWorkFlowOperationButtonsContainer, { "$isMobile": isMobile, children: isSignWorkflow ? (_jsxs(_Fragment, { children: [_jsx(TMButton, { btnStyle: isMobile ? 'toolbar' : 'advanced', showTooltip: isMobile, icon: _jsx(IconSignaturePencil, {}), caption: SDKUI_Localizator.SignatureAndApprove, width: "160px", disabled: signApproveDisable, onClick: () => !signApproveDisable && onSignApprove?.(), onMouseDown: e => e.stopPropagation(), advancedColor: "#1a9a49", color: "success" }), _jsx(TMButton, { btnStyle: isMobile ? 'toolbar' : 'advanced', showTooltip: isMobile, icon: _jsx(IconCloseOutline, {}), caption: SDKUI_Localizator.Reject, disabled: rejectDisable, onClick: () => !rejectDisable && onReject?.(), onMouseDown: e => e.stopPropagation(), advancedColor: TMColors.error, color: "error" }), _jsx(TMButton, { btnStyle: isMobile ? 'toolbar' : 'advanced', showTooltip: isMobile, icon: _jsx(IconInfo, { fontSize: 16 }), caption: SDKUI_Localizator.MoreInformation, width: "180px", disabled: infoDisable, onClick: () => !infoDisable && onMoreInfo?.(), onMouseDown: e => e.stopPropagation(), advancedColor: TMColors.info, color: "info" })] })) : (_jsxs(_Fragment, { children: [_jsx(TMButton, { btnStyle: isMobile ? 'toolbar' : 'advanced', showTooltip: isMobile, icon: _jsx(IconApply, {}), caption: SDKUI_Localizator.Approve, disabled: approveDisable, onClick: () => !approveDisable && onApprove?.(), advancedColor: "#1a9a49", color: "success" }), _jsx(TMButton, { btnStyle: isMobile ? 'toolbar' : 'advanced', showTooltip: isMobile, icon: _jsx(IconCloseOutline, {}), caption: SDKUI_Localizator.Reject, disabled: rejectDisable, onClick: () => !rejectDisable && onReject?.(), advancedColor: TMColors.error, color: "error" }), _jsx(TMButton, { btnStyle: isMobile ? 'toolbar' : 'advanced', showTooltip: isMobile, icon: _jsx(IconUser, { fontSize: 16 }), caption: SDKUI_Localizator.Reassign, disabled: reassignDisable, onClick: () => !reassignDisable && onReAssign?.(), advancedColor: TMColors.tertiary, color: "tertiary" }), _jsx(TMButton, { btnStyle: isMobile ? 'toolbar' : 'advanced', showTooltip: isMobile, icon: _jsx(IconInfo, { fontSize: 16 }), caption: SDKUI_Localizator.MoreInformation, width: "180px", disabled: infoDisable, onClick: () => !infoDisable && onMoreInfo?.(), advancedColor: TMColors.info, color: "info" })] })) }));
68
+ }, [dtd, signFlow]);
69
+ return (_jsx(StyledWorkFlowOperationButtonsContainer, { "$isMobile": isMobile, children: isSignWorkflow ? (_jsxs(_Fragment, { children: [_jsx(TMButton, { btnStyle: isMobile ? 'toolbar' : 'advanced', showTooltip: isMobile, icon: _jsx(IconSignaturePencil, {}), caption: SDKUI_Localizator.SignatureAndApprove, width: "160px", disabled: signApproveDisable, onClick: () => !signApproveDisable && onSignApprove?.(), onMouseDown: e => e.stopPropagation(), advancedColor: "#1a9a49", color: "success" }), _jsx(TMButton, { btnStyle: isMobile ? 'toolbar' : 'advanced', showTooltip: isMobile, icon: _jsx(IconCloseOutline, {}), caption: SDKUI_Localizator.Reject, disabled: rejectDisable, onClick: () => !rejectDisable && onReject?.(), onMouseDown: e => e.stopPropagation(), advancedColor: TMColors.error, color: "error" }), onMoreInfo && (_jsx(TMButton, { btnStyle: isMobile ? 'toolbar' : 'advanced', showTooltip: isMobile, icon: _jsx(IconInfo, { fontSize: 16 }), caption: SDKUI_Localizator.MoreInformation, width: "180px", disabled: infoDisable, onClick: () => !infoDisable && onMoreInfo?.(), onMouseDown: e => e.stopPropagation(), advancedColor: TMColors.info, color: "info" }))] })) : (_jsxs(_Fragment, { children: [_jsx(TMButton, { btnStyle: isMobile ? 'toolbar' : 'advanced', showTooltip: isMobile, icon: _jsx(IconApply, {}), caption: SDKUI_Localizator.Approve, disabled: approveDisable, onClick: () => !approveDisable && onApprove?.(), advancedColor: "#1a9a49", color: "success" }), _jsx(TMButton, { btnStyle: isMobile ? 'toolbar' : 'advanced', showTooltip: isMobile, icon: _jsx(IconCloseOutline, {}), caption: SDKUI_Localizator.Reject, disabled: rejectDisable, onClick: () => !rejectDisable && onReject?.(), advancedColor: TMColors.error, color: "error" }), onReAssign && (_jsx(TMButton, { btnStyle: isMobile ? 'toolbar' : 'advanced', showTooltip: isMobile, icon: _jsx(IconUser, { fontSize: 16 }), caption: SDKUI_Localizator.Reassign, disabled: reassignDisable, onClick: () => !reassignDisable && onReAssign?.(), advancedColor: TMColors.tertiary, color: "tertiary" })), onMoreInfo && (_jsx(TMButton, { btnStyle: isMobile ? 'toolbar' : 'advanced', showTooltip: isMobile, icon: _jsx(IconInfo, { fontSize: 16 }), caption: SDKUI_Localizator.MoreInformation, width: "180px", disabled: infoDisable, onClick: () => !infoDisable && onMoreInfo?.(), advancedColor: TMColors.info, color: "info" }))] })) }));
66
70
  };
67
71
  export const WorkFlowApproveRejectPopUp = ({ TID = 0, DID = 0, deviceType = DeviceType.DESKTOP, isReject, selectedItems = [], onClose, onCompleted }) => {
68
72
  const { refreshWorkflowApprove } = useWorkflowApprove();
@@ -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: color, children: icon })
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 }) => {