@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.
- 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/documents/TMDcmtForm.js +10 -2
- package/lib/components/features/documents/TMDcmtFormActionButtons.d.ts +11 -0
- package/lib/components/features/documents/TMDcmtFormActionButtons.js +74 -7
- package/lib/components/features/search/TMSearchResult.js +55 -4
- package/lib/components/features/workflow/TMWorkflowPopup.d.ts +10 -0
- package/lib/components/features/workflow/TMWorkflowPopup.js +8 -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/useCaseFlowApprove.d.ts +56 -0
- package/lib/hooks/useCaseFlowApprove.js +157 -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/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/services/caseflow/CaseFlowService.d.ts +208 -0
- package/lib/services/caseflow/CaseFlowService.js +623 -0
- package/lib/services/caseflow/CaseFlowTemplateCacheService.d.ts +28 -0
- package/lib/services/caseflow/CaseFlowTemplateCacheService.js +121 -0
- package/lib/services/caseflow/caseFlowNameCache.d.ts +32 -0
- package/lib/services/caseflow/caseFlowNameCache.js +116 -0
- package/lib/services/caseflow/caseFlowWorkItemUtils.d.ts +61 -0
- package/lib/services/caseflow/caseFlowWorkItemUtils.js +113 -0
- package/lib/services/index.d.ts +4 -0
- package/lib/services/index.js +4 -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;
|
|
@@ -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
|
-
|
|
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)) }))] }))] }),
|
|
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:
|
|
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 }) => {
|