@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,3 +1,4 @@
1
+ import React from "react";
1
2
  import { ValidationItem } from "@topconsultnpm/sdk-ts";
2
3
  export type Colors = 'error' | 'warning' | 'success' | 'info' | 'primary' | 'secondary' | 'tertiary' | 'standard';
3
4
  type IconModes = 'outside' | 'inside' | 'none';
@@ -6,6 +7,7 @@ export type Sizes = 'small' | 'medium' | 'large';
6
7
  export type TMEditorButtonData = {
7
8
  icon: any;
8
9
  text?: string;
10
+ tooltipContent?: React.ReactNode;
9
11
  onClick?: () => void;
10
12
  };
11
13
  export interface ITMEditorBase {
@@ -9,6 +9,7 @@ interface ITMTooltipProps extends ITooltipOptions {
9
9
  hideAfterDelay?: boolean;
10
10
  parentStyle?: React.CSSProperties;
11
11
  childStyle?: React.CSSProperties;
12
+ allowContentInteraction?: boolean;
12
13
  }
13
- declare const TMTooltip: ({ children, position, content, hideAfterDelay, parentStyle, childStyle }: ITMTooltipProps) => import("react/jsx-runtime").JSX.Element;
14
+ declare const TMTooltip: ({ children, position, content, hideAfterDelay, parentStyle, childStyle, allowContentInteraction }: ITMTooltipProps) => import("react/jsx-runtime").JSX.Element;
14
15
  export default TMTooltip;
@@ -1,20 +1,37 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useEffect, useState } from 'react';
2
+ import { useEffect, useRef, useState } from 'react';
3
3
  import { Tooltip } from 'devextreme-react/tooltip';
4
4
  import { genUniqueId } from '../../helper';
5
5
  import { DeviceType, useDeviceType } from './TMDeviceProvider';
6
- const TMTooltip = ({ children, position, content, hideAfterDelay, parentStyle, childStyle }) => {
6
+ /** Tempo per spostare il mouse dall'elemento al contenuto del tooltip, quando è interattivo */
7
+ const HIDE_DELAY_MS = 400;
8
+ const TMTooltip = ({ children, position, content, hideAfterDelay, parentStyle, childStyle, allowContentInteraction = false }) => {
7
9
  const [showTooltip, setShowTooltip] = useState(false);
8
10
  const [id, setID] = useState('');
11
+ const hideTimerRef = useRef();
9
12
  useEffect(() => { setID(genUniqueId()); }, [children]);
10
13
  useEffect(() => {
11
- window.addEventListener('click', () => setShowTooltip(false));
12
- return () => window.removeEventListener('click', () => setShowTooltip(false));
14
+ const hide = () => setShowTooltip(false);
15
+ window.addEventListener('click', hide);
16
+ return () => window.removeEventListener('click', hide);
13
17
  }, []);
18
+ useEffect(() => { return () => clearTimeout(hideTimerRef.current); }, []);
14
19
  const deviceType = useDeviceType();
15
- return (_jsxs("div", { style: parentStyle, children: [_jsx("div", { id: `idContainerTooltip${id}`, style: { display: 'flex', alignItems: 'center', height: 'max-content', width: 'max-content', ...childStyle }, onMouseEnter: () => { setShowTooltip(true); }, onMouseLeave: () => { setShowTooltip(false); }, onTouchStart: () => { setShowTooltip(true); }, onClick: () => { if (deviceType === DeviceType.DESKTOP || !hideAfterDelay)
20
+ const cancelHide = () => clearTimeout(hideTimerRef.current);
21
+ const show = () => { cancelHide(); setShowTooltip(true); };
22
+ const hide = () => {
23
+ cancelHide();
24
+ if (!allowContentInteraction) {
25
+ setShowTooltip(false);
26
+ return;
27
+ }
28
+ hideTimerRef.current = setTimeout(() => setShowTooltip(false), HIDE_DELAY_MS);
29
+ };
30
+ return (_jsxs("div", { style: parentStyle, children: [_jsx("div", { id: `idContainerTooltip${id}`, style: { display: 'flex', alignItems: 'center', height: 'max-content', width: 'max-content', ...childStyle }, onMouseEnter: show, onMouseLeave: hide, onTouchStart: show, onClick: () => { if (deviceType === DeviceType.DESKTOP || !hideAfterDelay)
16
31
  return; setTimeout(() => { setShowTooltip(false); }, 1500); }, onTouchEnd: () => { if (deviceType === DeviceType.DESKTOP || !hideAfterDelay)
17
32
  return; setTimeout(() => { setShowTooltip(false); }, 1500); }, children: children }), content &&
18
- _jsx(Tooltip, { target: `#idContainerTooltip${id}`, hideOnOutsideClick: true, visible: showTooltip, position: position ?? 'bottom', onHidden: () => setShowTooltip(false), children: content })] }));
33
+ _jsx(Tooltip, { target: `#idContainerTooltip${id}`, hideOnOutsideClick: true, visible: showTooltip, position: position ?? 'bottom', onHidden: () => setShowTooltip(false), children: allowContentInteraction
34
+ ? _jsx("div", { style: { pointerEvents: 'all' }, onMouseEnter: cancelHide, onMouseLeave: hide, children: content })
35
+ : content })] }));
19
36
  };
20
37
  export default TMTooltip;
@@ -3,11 +3,12 @@ import { useEffect, useState } from 'react';
3
3
  import { AccessLevelsEx, DcmtTypeListCacheService, SDK_Globals, SDK_Localizator } from '@topconsultnpm/sdk-ts';
4
4
  import TMSpinner from '../base/TMSpinner';
5
5
  import { IconSearch, SDKUI_Localizator } from '../../helper';
6
+ import { FontSize } from '../../utils/theme';
6
7
  import { StyledDivHorizontal } from '../base/Styled';
7
8
  import TMTidViewer, { TMDcmtTypeIcon } from '../viewers/TMTidViewer';
8
9
  import TMSummary from '../editors/TMSummary';
9
10
  import TMChooserForm from '../forms/TMChooserForm';
10
- const TMDcmtTypeChooser = ({ tmSession, dataSource, disabled, backgroundColor, filter, accessFilter = 'all', showEditButton = true, borderRadius = '4px', buttons = [], placeHolder = `<${SDKUI_Localizator.NoneSelection}>`, openEditorOnSummaryClick, showBorder = true, showId = false, elementStyle, allowMultipleSelection, ShowOnlyDcmtTypes, ShowOnlySAP, filterTemplateTID, values, isModifiedWhen, label, width, height, showClearButton = false, validationItems = [], onValueChanged, updateIsModalOpen }) => {
11
+ const TMDcmtTypeChooser = ({ tmSession, dataSource, disabled, backgroundColor, icon, filter, accessFilter = 'all', showEditButton = true, borderRadius = '4px', fontSize = FontSize.defaultFontSize, buttons = [], placeHolder = `<${SDKUI_Localizator.NoneSelection}>`, openEditorOnSummaryClick, showBorder = true, showId = false, elementStyle, allowMultipleSelection, ShowOnlyDcmtTypes, ShowOnlySAP, filterTemplateTID, values, isModifiedWhen, label, width, height, showClearButton = false, validationItems = [], onValueChanged, updateIsModalOpen }) => {
11
12
  const [showChooser, setShowChooser] = useState(false);
12
13
  useEffect(() => {
13
14
  TMSpinner.show({ description: `${SDKUI_Localizator.Loading} - ${SDK_Localizator.ListDcmtTypeOrView} ...` });
@@ -15,9 +16,9 @@ const TMDcmtTypeChooser = ({ tmSession, dataSource, disabled, backgroundColor, f
15
16
  DcmtTypeListCacheService.GetFromTIDsAsync(values, false, tms).then(() => { TMSpinner.hide(); });
16
17
  }, [values]);
17
18
  const renderTemplate = () => {
18
- return (_jsxs(StyledDivHorizontal, { style: { width: 'max-content', height: '100%' }, children: [values && _jsx(TMTidViewer, { tmSession: tmSession, tid: values[0], showIcon: true, showId: showId, noneSelectionText: placeHolder }), values && values.length > 1 && _jsx("p", { style: { marginLeft: '10px' }, children: `(+${values.length - 1} ${values.length == 2 ? 'altro' : 'altri'})` })] }));
19
+ return (_jsxs(StyledDivHorizontal, { style: { width: 'max-content', height: '100%' }, children: [values && _jsx(TMTidViewer, { tmSession: tmSession, tid: values[0], color: 'inherit', showIcon: true, showId: showId, noneSelectionText: placeHolder }), values && values.length > 1 && _jsx("p", { style: { margin: 0, marginLeft: '10px' }, children: `(+${values.length - 1} ${values.length == 2 ? 'altro' : 'altri'})` })] }));
19
20
  };
20
- return (_jsxs(_Fragment, { children: [_jsx(TMSummary, { backgroundColor: backgroundColor, buttons: buttons, showBorder: showBorder, borderRadius: borderRadius, hasValue: values && values.length > 0, showClearButton: showClearButton, showEditButton: showEditButton, iconEditButton: _jsx(IconSearch, { fontSize: 16 }), onEditorClick: () => {
21
+ return (_jsxs(_Fragment, { children: [_jsx(TMSummary, { backgroundColor: backgroundColor, icon: icon, disabled: disabled, fontSize: fontSize, buttons: buttons, showBorder: showBorder, borderRadius: borderRadius, hasValue: values && values.length > 0, showClearButton: showClearButton, showEditButton: showEditButton, iconEditButton: _jsx(IconSearch, { fontSize: 16 }), onEditorClick: () => {
21
22
  if (!disabled) {
22
23
  setShowChooser(true);
23
24
  updateIsModalOpen?.(true);
@@ -1,5 +1,6 @@
1
1
  import React from 'react';
2
2
  import { LayoutModes } from '@topconsultnpm/sdk-ts';
3
+ import { TMDistinctValuesEventArgs } from '../../hooks/useTMDistinctValuesSelection';
3
4
  interface ITMDistinctValues {
4
5
  tid: number | undefined;
5
6
  mid: number | undefined;
@@ -8,14 +9,15 @@ interface ITMDistinctValues {
8
9
  allowAppendMode?: boolean;
9
10
  showHeader?: boolean;
10
11
  isModal?: boolean;
11
- onSelectionChanged?: (e: DistinctValuesEventArgs) => void;
12
+ onSelectionChanged?: (e: TMDistinctValuesEventArgs) => void;
12
13
  onClosePanelCallback?: () => void;
13
14
  }
14
- type DistinctValuesEventArgs = {
15
- tid: number | undefined;
16
- mid: number | undefined;
17
- newValue: string | undefined;
18
- isAppendMode: boolean;
19
- };
15
+ /**
16
+ * Pannello da cui si preleva il valore di un campo del form scegliendolo fra quelli già archiviati.
17
+ *
18
+ * Le due sorgenti (valori distinti del metadato e ricerca rapida su una ricerca salvata) stanno in due hook
19
+ * dedicati che espongono la stessa interfaccia (righe, colonne, conteggi): qui restano la composizione,
20
+ * la scelta della sorgente da mostrare e il render.
21
+ */
20
22
  declare const TMDistinctValues: React.FC<ITMDistinctValues>;
21
23
  export default TMDistinctValues;
@@ -1,209 +1,161 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
3
- import { LayoutModes, MetadataDataDomains, AccessLevels, MetadataDataTypes, SDK_Globals, DcmtTypeListCacheService, DataColumnTypes, DataListViewModes } from '@topconsultnpm/sdk-ts';
2
+ import { useCallback, useEffect, useMemo, useRef } from 'react';
3
+ import { LayoutModes, SDK_Localizator } from '@topconsultnpm/sdk-ts';
4
4
  import styled from 'styled-components';
5
- import { SDKUI_Localizator, stringIsNullOrEmpty } from '../../helper';
6
- import TMDataGrid, { TMDataGridPageSize } from '../base/TMDataGrid';
7
- import { TMExceptionBoxManager } from '../base/TMPopUp';
8
- import TMSpinner from '../base/TMSpinner';
5
+ import { calcResponsiveSizes, IconAll, IconDelete, IconPencil, IconRefresh, SDKUI_Localizator } from '../../helper';
6
+ import TMButton from '../base/TMButton';
7
+ import TMDataGrid from '../base/TMDataGrid';
8
+ import { CounterItemKey } from '../base/TMCounterContainer';
9
+ import { useDeviceType } from '../base/TMDeviceProvider';
9
10
  import TMCheckBox from '../editors/TMCheckBox';
11
+ import TMDropDown from '../editors/TMDropDown';
10
12
  import TMTextBox from '../editors/TMTextBox';
11
13
  import TMPanel from '../base/TMPanel';
12
14
  import TMModal from '../base/TMModal';
13
- import { useDataListItem } from '../../hooks/useDataListItem';
14
- import { useDataUserIdItem } from '../../hooks/useDataUserIdItem';
15
- const StyledDistinctValues = styled.div `display: flex; flex-direction: column; height: 100%; overflow: hidden; gap: 10px; position: relative;`;
16
- const StyledAppendBar = styled.div `display: flex; flex-direction: row; align-items: center; gap: 5px; position: absolute; right: 15px; top: 15px; z-index: 10000;`;
15
+ import TMQuickSearchSettingsForm from './TMQuickSearchSettingsForm';
16
+ import TMSelectedValuesSummary from './TMSelectedValuesSummary';
17
+ import { useTMDistinctValuesMetadataDisplay } from '../../hooks/useTMDistinctValuesMetadataDisplay';
18
+ import { DistinctValuesModes, QUICK_SEARCH_VALUE_COLUMN_CLASS, useTMDistinctValuesQuickSearch } from '../../hooks/useTMDistinctValuesQuickSearch';
19
+ import { useTMDistinctValuesSelection } from '../../hooks/useTMDistinctValuesSelection';
20
+ import { useTMDistinctValuesSource } from '../../hooks/useTMDistinctValuesSource';
21
+ import { TMColors } from '../../utils/theme';
22
+ /* Il padding orizzontale stacca il contenuto dai bordi del pannello/modale che lo ospita */
23
+ const StyledDistinctValues = styled.div `display: flex; flex-direction: column; height: 100%; overflow: hidden; gap: 6px; padding: 4px 8px 6px; position: relative;`;
24
+ const StyledTopBar = styled.div `
25
+ display: flex;
26
+ flex-direction: row;
27
+ align-items: center;
28
+ justify-content: flex-start;
29
+ gap: 15px;
30
+ flex-wrap: wrap;
31
+ padding: 6px 10px;
32
+ background-color: ${TMColors.toolbar_background};
33
+ border: 1px solid ${TMColors.border_normal};
34
+ border-radius: 6px;
35
+ `;
36
+ /* Accodamento e separatore stanno con il resto della configurazione: il divisore li distingue dai comandi senza staccarli dal gruppo */
37
+ const StyledAppendBar = styled.div `
38
+ display: flex;
39
+ flex-direction: row;
40
+ align-items: center;
41
+ gap: 8px;
42
+ min-height: 30px;
43
+ padding-left: 10px;
44
+ border-left: 1px solid ${TMColors.border_normal};
45
+ `;
46
+ /* Modalità, comandi e opzioni di accodamento: tutta la configurazione del pannello su un'unica riga */
47
+ const StyledConfigBar = styled.div `display: flex; flex-direction: row; align-items: center; flex-wrap: wrap; gap: 4px;`;
48
+ const StyledGridContainer = styled.div `
49
+ flex: 1;
50
+ min-height: 0;
51
+ overflow: hidden;
52
+
53
+ /* Evidenzia intestazione e celle della colonna da cui viene prelevato il valore */
54
+ .${QUICK_SEARCH_VALUE_COLUMN_CLASS} {
55
+ background-color: ${TMColors.primary_container};
56
+ font-weight: 600;
57
+ }
58
+ `;
59
+ /* Quota di larghezza attribuita a ciascuna colonna della griglia, oltre a una base fissa per la barra dei comandi.
60
+ I limiti evitano i due estremi: sotto il 40% la barra dei comandi diventa illeggibile, oltre il 95% il modale esce dallo schermo */
61
+ const QUICK_SEARCH_BASE_WIDTH_PERC = 16;
62
+ const QUICK_SEARCH_COLUMN_WIDTH_PERC = 12;
63
+ const QUICK_SEARCH_MIN_WIDTH_PERC = 40;
64
+ const QUICK_SEARCH_MAX_WIDTH_PERC = 95;
65
+ /**
66
+ * Larghezza del modale della ricerca rapida, proporzionale al numero di colonne mostrate: con poche colonne
67
+ * un modale a tutta pagina le lascerebbe stirate e meno leggibili, con molte serve tutto lo spazio disponibile.
68
+ */
69
+ const calcQuickSearchModalWidth = (visibleColumnsCount) => {
70
+ const widthPerc = QUICK_SEARCH_BASE_WIDTH_PERC + visibleColumnsCount * QUICK_SEARCH_COLUMN_WIDTH_PERC;
71
+ return `${Math.min(QUICK_SEARCH_MAX_WIDTH_PERC, Math.max(QUICK_SEARCH_MIN_WIDTH_PERC, widthPerc))}%`;
72
+ };
73
+ /**
74
+ * Pannello da cui si preleva il valore di un campo del form scegliendolo fra quelli già archiviati.
75
+ *
76
+ * Le due sorgenti (valori distinti del metadato e ricerca rapida su una ricerca salvata) stanno in due hook
77
+ * dedicati che espongono la stessa interfaccia (righe, colonne, conteggi): qui restano la composizione,
78
+ * la scelta della sorgente da mostrare e il render.
79
+ */
17
80
  const TMDistinctValues = ({ tid, mid, layoutMode = LayoutModes.None, allowAppendMode = true, showHeader = true, isModal, separator = ",", onSelectionChanged, onClosePanelCallback }) => {
18
- const [focusedItem, setFocusedItem] = useState();
19
- const [dataSource, setDataSource] = useState([]);
20
- const [isAppendMode, setIsAppendMode] = useState(false);
21
- const [md, setMd] = useState();
22
- const [appendedValues, setAppendedValues] = useState(new Set());
23
- const { loadDataListsAsync, renderDataListCell, getDataListItem } = useDataListItem();
24
- const { loadUsersAsync, renderUserIdViewer, getUserItem, getCompleteUserName } = useDataUserIdItem();
25
- // Identifica la richiesta corrente: le risposte delle richieste obsolete vengono scartate
26
- const requestIdRef = useRef(0);
27
- useEffect(() => {
28
- getDistictValuesAsync();
29
- }, [tid, mid]);
30
- useEffect(() => {
31
- setIsAppendMode(false);
32
- setFocusedItem(undefined);
33
- setAppendedValues(new Set());
34
- }, [md]);
35
- useEffect(() => {
36
- if (!focusedItem)
37
- return;
38
- if (!md)
39
- return;
40
- if ((layoutMode === LayoutModes.Ark || layoutMode === LayoutModes.Update) && md.dataDomain === MetadataDataDomains.Computed)
41
- return;
42
- const permissionCheck = {
43
- [LayoutModes.Ark]: md.perm?.canArchive,
44
- [LayoutModes.Update]: md.perm?.canUpdate,
45
- [LayoutModes.None]: md.perm?.canSearch,
46
- };
47
- if (layoutMode in permissionCheck && permissionCheck[layoutMode] !== AccessLevels.Yes)
48
- return;
49
- if (stringIsNullOrEmpty(focusedItem.value)) {
50
- setAppendedValues(new Set());
51
- onSelectionChanged?.({ tid: tid, mid: mid, newValue: undefined, isAppendMode: isAppendMode });
52
- return;
53
- }
54
- let value = md.dataType === MetadataDataTypes.Number ? parseFloat(focusedItem.value.replace(',', '.')).toString() : focusedItem.value;
55
- // Fuori dalla modalità Accoda la selezione sostituisce sempre il valore precedente
56
- const newAppendedValues = isAppendMode ? new Set(appendedValues) : new Set();
57
- newAppendedValues.add(value);
58
- setAppendedValues(newAppendedValues);
59
- onSelectionChanged?.({ tid: tid, mid: mid, newValue: joinAppendedValues(newAppendedValues), isAppendMode: isAppendMode });
60
- }, [focusedItem]);
61
- const joinAppendedValues = (values) => Array.from(values).join(separator);
62
- const onAppendModeChanged = () => {
63
- const newIsAppendMode = !isAppendMode;
64
- // Disattivando l'accodamento si riparte da zero: il prossimo click sostituirà il valore
65
- if (!newIsAppendMode)
66
- setAppendedValues(new Set());
67
- setIsAppendMode(newIsAppendMode);
68
- };
69
- const getDistictValuesAsync = async () => {
70
- if (!tid)
81
+ const deviceType = useDeviceType();
82
+ const display = useTMDistinctValuesMetadataDisplay();
83
+ const distinctValues = useTMDistinctValuesSource({ tid, mid, display });
84
+ const quickSearch = useTMDistinctValuesQuickSearch({ tid, mid, md: distinctValues.md, display });
85
+ const { isQuickSearchMode, isQuickSearchGridVisible } = quickSearch;
86
+ // Il valore della riga sta in campi diversi nelle due sorgenti: nei valori distinti è sempre `value`,
87
+ // nella ricerca rapida è la colonna del metadato configurato
88
+ const getRowValue = useCallback((row) => {
89
+ if (!isQuickSearchGridVisible)
90
+ return row?.value;
91
+ return quickSearch.valueField ? row?.[quickSearch.valueField] : undefined;
92
+ }, [isQuickSearchGridVisible, quickSearch.valueField]);
93
+ const selection = useTMDistinctValuesSelection({
94
+ tid, mid, md: distinctValues.md, layoutMode, allowAppendMode, separator,
95
+ selectedValueMd: quickSearch.selectedValueMd,
96
+ getRowValue,
97
+ getMetadataDisplayValue: display.getMetadataDisplayValue,
98
+ onSelectionChanged
99
+ });
100
+ // Cambiando sorgente la riga focalizzata non esiste più: la griglia viene ricreata da zero
101
+ useEffect(() => { selection.setFocusedItem(undefined); }, [quickSearch.mode, quickSearch.settings]);
102
+ const refreshAsync = async () => {
103
+ if (!isQuickSearchMode) {
104
+ await distinctValues.loadAsync();
71
105
  return;
72
- const requestId = ++requestIdRef.current;
73
- // Svuota subito la griglia: evita di mostrare i valori del tid/mid precedente durante il caricamento
74
- setDataSource([]);
75
- try {
76
- TMSpinner.show({ description: 'Caricamento dei valori distinti...' });
77
- let dtd = await DcmtTypeListCacheService.GetAsync(tid, true);
78
- const currentMd = dtd?.metadata?.find(o => o.id === mid);
79
- let result = await SDK_Globals.tmSession?.NewSearchEngine().GetDistinctValuesAsync(tid, mid, 10000);
80
- if (requestId !== requestIdRef.current)
81
- return;
82
- // Load DataList or UserID cache based on metadata domain
83
- if (currentMd?.dataDomain === MetadataDataDomains.DataList && currentMd.dataListID) {
84
- await loadDataListsAsync(new Set([currentMd.dataListID]));
85
- }
86
- if (currentMd?.dataDomain === MetadataDataDomains.UserID && result?.dtdResult?.rows) {
87
- const userIDs = new Set();
88
- result.dtdResult.rows.forEach((row) => {
89
- const userId = Number(row[0]);
90
- if (userId && userId > 0) {
91
- userIDs.add(userId);
92
- }
93
- });
94
- await loadUsersAsync(userIDs);
95
- }
96
- // Nel frattempo tid/mid sono cambiati: questa risposta (e le cache appena caricate) non è più valida
97
- if (requestId !== requestIdRef.current)
98
- return;
99
- // md e dataSource vengono impostati insieme, dopo il popolamento delle cache,
100
- // così il render delle celle usa sempre metadato e cache coerenti con i dati
101
- setMd(currentMd);
102
- setDataSource(convertDataTableToObject(result?.dtdResult));
103
- }
104
- catch (e) {
105
- if (requestId !== requestIdRef.current)
106
- return;
107
- let err = e;
108
- TMExceptionBoxManager.show({ exception: err });
109
- }
110
- finally {
111
- TMSpinner.hide();
112
106
  }
107
+ await quickSearch.refreshAsync();
113
108
  };
114
- const onFocusedRowChanged = useCallback((e) => {
115
- setFocusedItem(e.row?.data);
116
- }, []);
117
- const valueCellRender = useCallback((cellData) => {
118
- if (!cellData || cellData.value === undefined)
119
- return null;
120
- if (md?.dataDomain === MetadataDataDomains.DataList && md.dataListID) {
121
- return renderDataListCell(cellData.value, md.dataListID, md.dataListViewMode ?? DataListViewModes.None);
122
- }
123
- if (md?.dataDomain === MetadataDataDomains.UserID) {
124
- return renderUserIdViewer(Number(cellData.value), true);
125
- }
126
- return _jsx("div", { children: cellData.text });
127
- }, [md, renderDataListCell, renderUserIdViewer]);
128
- // Per DataList e UserID il testo mostrato in cella (descrizione, nome utente) è diverso dal valore
129
- // grezzo del dataSource (codice, id): senza questa funzione la ricerca di DevExtreme filtrerebbe
130
- // sul valore grezzo e non troverebbe quello che l'utente vede scritto nella griglia
131
- const calculateValueDisplayValue = useCallback((rowData) => {
132
- const value = rowData?.value;
133
- if (stringIsNullOrEmpty(value))
134
- return value;
135
- if (md?.dataDomain === MetadataDataDomains.DataList && md.dataListID) {
136
- return getDataListItem(md.dataListID, value)?.name ?? value;
137
- }
138
- if (md?.dataDomain === MetadataDataDomains.UserID) {
139
- const userId = Number(value);
140
- if (userId === 0)
141
- return SDKUI_Localizator.SystemUser;
142
- const ud = getUserItem(userId);
143
- return ud ? getCompleteUserName(ud.domain, ud.name) ?? value : value;
144
- }
145
- return value;
146
- }, [md, getDataListItem, getUserItem, getCompleteUserName]);
147
- const customColumns = useMemo(() => {
148
- const needsCellRender = md?.dataDomain === MetadataDataDomains.DataList || md?.dataDomain === MetadataDataDomains.UserID;
149
- return ([
150
- { dataField: 'value', dataType: md?.dataType === MetadataDataTypes.DateTime ? 'date' : 'string', caption: md?.nameLoc, width: 'min-content', ...(needsCellRender && { cellRender: valueCellRender, calculateDisplayValue: calculateValueDisplayValue }) },
151
- { dataField: 'Count', dataType: 'number', caption: 'N°' }
152
- ]);
153
- }, [md, valueCellRender, calculateValueDisplayValue]);
154
- const customSummary = useMemo(() => {
155
- return ({
156
- totalItems: [
157
- { column: 'value', summaryType: 'count' },
158
- { column: 'Count', summaryType: 'sum' }
159
- ]
160
- });
161
- }, []);
162
- const convertDataTableToObject = (dtdResult) => {
163
- if (!dtdResult?.columns?.length || !dtdResult?.rows?.length)
164
- return [];
165
- // Descrittori di colonna calcolati una sola volta invece che per ogni riga
166
- const columns = [];
167
- dtdResult.columns.forEach((col, colIndex) => {
168
- if (col?.caption) {
169
- columns.push({ index: colIndex, key: colIndex === 0 ? "value" : col.caption, isNumber: col.dataType === DataColumnTypes.Number });
170
- }
171
- });
172
- // Il conteggio delle occorrenze è la seconda colonna del risultato:
173
- // la chiave di ordinamento viene calcolata una volta per riga, non a ogni confronto
174
- const COUNT_COL_INDEX = 1;
175
- const rows = dtdResult.rows.map((row) => {
176
- let rowObject = { rowIndex: 0, sortValue: 0 };
177
- for (let i = 0; i < columns.length; i++) {
178
- const col = columns[i];
179
- rowObject[col.key] = col.isNumber ? row?.[col.index]?.replace('.', ',') : row?.[col.index] ?? null;
180
- }
181
- rowObject.sortValue = Number(row?.[COUNT_COL_INDEX]) || 0;
182
- return rowObject;
183
- });
184
- rows.sort((a, b) => b.sortValue - a.sortValue);
185
- for (let i = 0; i < rows.length; i++)
186
- rows[i].rowIndex = i;
187
- return rows;
188
- };
189
- const isVisibleAppend = () => {
190
- if (!allowAppendMode)
191
- return false;
192
- if (layoutMode !== LayoutModes.Ark && layoutMode !== LayoutModes.Update)
193
- return true;
194
- if (!md)
195
- return true;
196
- if (md.dataType === MetadataDataTypes.DateTime)
197
- return false;
198
- if (md.dataType === MetadataDataTypes.Number)
199
- return false;
200
- return md.dataDomain === undefined || md.dataDomain === MetadataDataDomains.None;
201
- };
109
+ // Finché le colonne della ricerca rapida non sono pronte la griglia non va montata: nascerebbe senza campi
110
+ const isGridReady = !isQuickSearchGridVisible || quickSearch.columns.length > 0;
111
+ const counterConfig = useMemo(() => {
112
+ const counts = isQuickSearchGridVisible ? quickSearch.counts : distinctValues.counts;
113
+ // Senza il totale resta il conteggio predefinito della griglia: un rapporto con totale a zero sarebbe fuorviante
114
+ if (!counts?.found)
115
+ return { show: true };
116
+ return {
117
+ show: true,
118
+ items: new Map([
119
+ [CounterItemKey.allItems, {
120
+ key: CounterItemKey.allItems,
121
+ show: true,
122
+ caption: `${counts.returned} restituiti su ${counts.found} trovati`,
123
+ value: `${counts.returned}/${counts.found}`,
124
+ icon: _jsx(IconAll, {}),
125
+ order: -1
126
+ }]
127
+ ])
128
+ };
129
+ }, [isQuickSearchGridVisible, quickSearch.counts, distinctValues.counts]);
130
+ const modeDataSource = useMemo(() => ([
131
+ { value: DistinctValuesModes.DistinctValues, display: SDKUI_Localizator.DistinctValues },
132
+ { value: DistinctValuesModes.QuickSearch, display: SDK_Localizator.SavedQuery },
133
+ ]), []);
134
+ const panelTitle = (isQuickSearchGridVisible ? SDK_Localizator.SavedQuery : SDKUI_Localizator.DistinctValues) + (distinctValues.md?.nameLoc ? ` (${distinctValues.md?.nameLoc})` : '');
135
+ /* Le colonne nascoste (metadati di sistema della query) non occupano spazio: non vanno contate nella larghezza.
136
+ Durante un ricaricamento le colonne tornano momentaneamente vuote: si conserva l'ultima larghezza calcolata,
137
+ altrimenti il modale si restringerebbe per poi riallargarsi */
138
+ const lastQuickSearchModalWidthRef = useRef();
139
+ const quickSearchModalWidth = useMemo(() => {
140
+ const visibleColumnsCount = quickSearch.columns.filter(col => col.visible !== false).length;
141
+ if (visibleColumnsCount > 0)
142
+ lastQuickSearchModalWidthRef.current = calcQuickSearchModalWidth(visibleColumnsCount);
143
+ return lastQuickSearchModalWidthRef.current;
144
+ }, [quickSearch.columns]);
145
+ /* La larghezza è nota solo a ricerca conclusa: mostrare subito il modale lo farebbe nascere alla larghezza minima
146
+ per poi allargarlo sotto gli occhi dell'utente, quindi si attende (l'attesa è coperta dallo spinner della ricerca) */
147
+ const isModalReady = !isQuickSearchGridVisible || !!quickSearchModalWidth || !quickSearch.isFirstSearchPending;
202
148
  const renderContent = () => {
203
- return (_jsxs(StyledDistinctValues, { children: [isVisibleAppend() && _jsxs(StyledAppendBar, { children: [_jsx(TMCheckBox, { label: 'Accoda e separa con', value: isAppendMode, onValueChanged: onAppendModeChanged }), _jsx(TMTextBox, { width: '30px', readOnly: true, disabled: !isAppendMode, value: separator })] }), _jsx(TMDataGrid, { focusedRowKey: focusedItem ? focusedItem.rowIndex : undefined, selection: { showCheckBoxesMode: 'none' }, searchPanel: { highlightCaseSensitive: true, visible: true }, dataColumns: customColumns, dataSource: dataSource, keyExpr: 'rowIndex', height: 'calc(100%)', onFocusedRowChanged: onFocusedRowChanged, paging: { pageSize: 30 }, summary: customSummary, onRowDblClick: () => onClosePanelCallback?.(), pageSize: TMDataGridPageSize.Medium })] }));
149
+ return (_jsxs(StyledDistinctValues, { children: [_jsx(StyledTopBar, { children: _jsxs(StyledConfigBar, { children: [_jsx(TMDropDown, { width: '180px', value: quickSearch.mode, dataSource: modeDataSource, elementStyle: { display: 'flex', alignItems: 'center' }, onValueChanged: (e) => quickSearch.onModeChanged(Number(e.target.value)) }), isQuickSearchMode &&
150
+ _jsx(TMButton, { btnStyle: 'toolbar', caption: SDKUI_Localizator.Settings, icon: _jsx(IconPencil, {}), onClick: () => quickSearch.setShowSettingsForm(true) }), isQuickSearchMode && Boolean(quickSearch.settings) &&
151
+ _jsx(TMButton, { btnStyle: 'toolbar', color: 'error', caption: SDKUI_Localizator.Delete, icon: _jsx(IconDelete, {}), onClick: quickSearch.onDeleteSettingsClick }), _jsx(TMButton, { btnStyle: 'toolbar', caption: SDKUI_Localizator.Refresh, icon: _jsx(IconRefresh, {}), onClick: () => refreshAsync() }), selection.isAppendVisible && _jsxs(StyledAppendBar, { children: [_jsx(TMCheckBox, { label: 'Accoda e separa con', value: selection.isAppendMode, onValueChanged: selection.onAppendModeChanged }), _jsx(TMTextBox, { width: '30px', readOnly: true, disabled: !selection.isAppendMode, value: separator })] })] }) }), _jsx(TMSelectedValuesSummary, { items: selection.selectedValues, onRemove: selection.onRemoveSelectedValue, onClear: selection.onClearSelectedValues }), _jsx(StyledGridContainer, { children: isGridReady && _jsx(TMDataGrid
152
+ /* Le due modalità hanno colonne del tutto diverse: ognuna ha la propria istanza di griglia.
153
+ Nei valori distinti l'intestazione è il nome del metadato, che arriva insieme al primo caricamento */
154
+ , { focusedRowKey: selection.focusedItem ? selection.focusedItem.rowIndex : undefined, selection: { showCheckBoxesMode: 'none' }, searchPanel: { highlightCaseSensitive: true, visible: true }, dataColumns: isQuickSearchGridVisible ? quickSearch.columns : distinctValues.columns, dataSource: isQuickSearchGridVisible ? quickSearch.dataSource : distinctValues.dataSource, customizeColumns: isQuickSearchGridVisible ? quickSearch.customizeColumns : distinctValues.customizeColumns, keyExpr: 'rowIndex', height: 'calc(100%)', showHeaderColumnChooser: true, onFocusedRowChanged: selection.onFocusedRowChanged, onRowDblClick: () => onClosePanelCallback?.(), counterConfig: counterConfig }, isQuickSearchGridVisible ? 'quickSearch' : `distinctValues_${distinctValues.md?.id ?? 0}`) })] }));
204
155
  };
205
- return (_jsx(_Fragment, { children: isModal
206
- ? _jsx(TMModal, { title: SDKUI_Localizator.DistinctValues + (md?.nameLoc ? ` (${md?.nameLoc})` : ''), height: '600px', width: '500px', resizable: true, onClose: onClosePanelCallback, children: renderContent() })
207
- : _jsx(TMPanel, { title: SDKUI_Localizator.DistinctValues + (md?.nameLoc ? ` (${md?.nameLoc})` : ''), showHeader: showHeader, onClose: onClosePanelCallback, children: renderContent() }) }));
156
+ return (_jsxs(_Fragment, { children: [isModal
157
+ ? isModalReady && _jsx(TMModal, { title: panelTitle, width: isQuickSearchGridVisible ? calcResponsiveSizes(deviceType, quickSearchModalWidth ?? calcQuickSearchModalWidth(0), '95%', '95%') : calcResponsiveSizes(deviceType, '700px', '95%', '95%'), height: isQuickSearchGridVisible ? '95%' : calcResponsiveSizes(deviceType, '600px', '95%', '95%'), resizable: true, expandable: true, onClose: onClosePanelCallback, children: renderContent() }, isQuickSearchGridVisible ? 'quickSearch' : 'distinctValues')
158
+ : _jsx(TMPanel, { title: panelTitle, showHeader: showHeader, onClose: onClosePanelCallback, children: renderContent() }), quickSearch.showSettingsForm &&
159
+ _jsx(TMQuickSearchSettingsForm, { settings: quickSearch.settings, onSave: quickSearch.onSaveSettings, onDelete: quickSearch.onDeleteSettingsClick, onClose: quickSearch.onSettingsFormClose }), _jsx(quickSearch.ConfirmQueryParamsDialog, {})] }));
208
160
  };
209
161
  export default TMDistinctValues;
@@ -21,7 +21,7 @@ const TMMetadataChooser = ({ tmSession, dataSource, showEditButton = true, butto
21
21
  return undefined;
22
22
  };
23
23
  const renderTemplate = useMemo(() => {
24
- return (_jsxs(StyledDivHorizontal, { style: { width: 'max-content', height: '100%' }, children: [values && values.length > 0 && values[0].mid && values[0].mid > 0 && _jsx(TMMidViewer, { tmSession: tmSession, tid_mid: values[0], showIcon: true, showId: showId, showCompleteName: showCompleteMetadataName }), values && values.length > 0 && values[0].mid && values[0].mid < 0 && _jsx(TMMidViewer, { tmSession: tmSession, tid_mid: values[0], inputMd: getinputMd(), showIcon: true, showId: showId, showCompleteName: showCompleteMetadataName }), values && values.length > 1 && _jsx("p", { style: { marginLeft: '10px' }, children: `(+${values.length - 1} ${values.length == 2 ? 'altro' : 'altri'})` }), (values == undefined || values.length == 0) && _jsx("p", { children: placeHolder ?? SDKUI_Localizator.SelectMetadata })] }));
24
+ return (_jsxs(StyledDivHorizontal, { style: { width: 'max-content', height: '100%' }, children: [values && values.length > 0 && values[0].mid && values[0].mid > 0 && _jsx(TMMidViewer, { tmSession: tmSession, tid_mid: values[0], color: 'inherit', showIcon: true, showId: showId, showCompleteName: showCompleteMetadataName }), values && values.length > 0 && values[0].mid && values[0].mid < 0 && _jsx(TMMidViewer, { tmSession: tmSession, tid_mid: values[0], inputMd: getinputMd(), color: 'inherit', showIcon: true, showId: showId, showCompleteName: showCompleteMetadataName }), values && values.length > 1 && _jsx("p", { style: { margin: 0, marginLeft: '10px' }, children: `(+${values.length - 1} ${values.length == 2 ? 'altro' : 'altri'})` }), (values == undefined || values.length == 0) && _jsx("p", { style: { margin: 0 }, children: placeHolder ?? SDKUI_Localizator.SelectMetadata })] }));
25
25
  }, [values, tmSession, showId, showCompleteMetadataName, placeHolder]);
26
26
  return (_jsxs(_Fragment, { children: [_jsx(TMSummary, { label: label, width: width, height: height, disabled: disabled, validationItems: validationItems, backgroundColor: backgroundColor, buttons: buttons, placeHolder: placeHolder, fontSize: fontSize, showBorder: showBorder, borderRadius: borderRadius, hasValue: values && values.length > 0, showClearButton: showClearButton, showEditButton: showEditButton, iconEditButton: _jsx(IconSearch, { fontSize: 16 }), openEditorOnSummaryClick: openEditorOnSummaryClick, onEditorClick: () => {
27
27
  if (!disabled) {
@@ -0,0 +1,12 @@
1
+ import { TMEditorButtonData } from '../base/TMEditorBase';
2
+ interface ITMQuickSearchInfo {
3
+ /** Tipo documento della ricerca rapida: serve a ricostruire la ricerca di sistema con tutti i documenti */
4
+ tid: number | undefined;
5
+ /** Senza ricerca salvata non c'è nulla da spiegare e l'icona non viene mostrata */
6
+ sqdId: number | undefined;
7
+ }
8
+ export declare const useTMQuickSearchInfo: ({ tid, sqdId }: ITMQuickSearchInfo) => {
9
+ infoButton: TMEditorButtonData | undefined;
10
+ detailForm: import("react/jsx-runtime").JSX.Element | null;
11
+ };
12
+ export {};