@topconsultnpm/sdkui-react 6.22.0-dev2.29 → 6.22.0-dev2.30
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/TMFileManagerUtils.d.ts +0 -7
- package/lib/components/base/TMFileManagerUtils.js +1 -7
- package/lib/components/features/documents/TMDcmtBlog.d.ts +3 -0
- package/lib/components/features/documents/TMDcmtBlog.js +4 -4
- package/lib/components/features/documents/TMDcmtForm.js +8 -3
- package/lib/components/features/documents/TMDcmtPreview.js +10 -6
- package/lib/components/features/search/TMSearchResult.js +100 -20
- package/lib/components/layout/panelManager/TMPanelManagerContext.js +68 -92
- package/lib/components/layout/panelManager/utils.js +13 -11
- package/lib/helper/SDKUI_Globals.d.ts +22 -0
- package/lib/helper/SDKUI_Globals.js +38 -0
- package/package.json +1 -1
|
@@ -26,13 +26,6 @@ export interface FileItem {
|
|
|
26
26
|
version?: number;
|
|
27
27
|
isSigned?: number;
|
|
28
28
|
}
|
|
29
|
-
/**
|
|
30
|
-
* Completa i nomi utente (autore e utente di check-out) di un FileItem quando mancano, risolvendoli dagli ID.
|
|
31
|
-
* buildFolderHierarchy popola i nomi solo se riceve la lista dei partecipanti: nei refresh parziali
|
|
32
|
-
* (es. dopo la condivisione di un nuovo file) i partecipanti possono non essere disponibili e i nomi
|
|
33
|
-
* arrivano vuoti, svuotando la colonna Autore fino al ricaricamento completo.
|
|
34
|
-
* Restituisce lo stesso oggetto se non c'è nulla da risolvere, per non invalidare le reference inutilmente.
|
|
35
|
-
*/
|
|
36
29
|
export declare const resolveFileItemUserNames: (item: FileItem, users: Array<UserDescriptor>) => FileItem;
|
|
37
30
|
export interface TMFileManagerTreeViewDirectory {
|
|
38
31
|
id: number;
|
|
@@ -12,13 +12,7 @@ export const isSigned = (ext) => {
|
|
|
12
12
|
const parts = normalized.split('.');
|
|
13
13
|
return parts.some(part => SIGNATURE_EXTENSIONS.has(part));
|
|
14
14
|
};
|
|
15
|
-
|
|
16
|
-
* Completa i nomi utente (autore e utente di check-out) di un FileItem quando mancano, risolvendoli dagli ID.
|
|
17
|
-
* buildFolderHierarchy popola i nomi solo se riceve la lista dei partecipanti: nei refresh parziali
|
|
18
|
-
* (es. dopo la condivisione di un nuovo file) i partecipanti possono non essere disponibili e i nomi
|
|
19
|
-
* arrivano vuoti, svuotando la colonna Autore fino al ricaricamento completo.
|
|
20
|
-
* Restituisce lo stesso oggetto se non c'è nulla da risolvere, per non invalidare le reference inutilmente.
|
|
21
|
-
*/
|
|
15
|
+
// Completa i nomi utente (autore e utente di check-out) di un FileItem quando mancano, risolvendoli dagli ID.
|
|
22
16
|
export const resolveFileItemUserNames = (item, users) => {
|
|
23
17
|
if (!users || users.length === 0)
|
|
24
18
|
return item;
|
|
@@ -3,7 +3,10 @@ import { HomeBlogPost, TaskDescriptor } from '@topconsultnpm/sdk-ts';
|
|
|
3
3
|
interface ITMDcmtBlogProps {
|
|
4
4
|
tid: number | undefined;
|
|
5
5
|
did: number | undefined;
|
|
6
|
+
/** Il pannello è aperto. La inietta TMPanelWrapper, non va passata dal chiamante */
|
|
6
7
|
isVisible?: boolean;
|
|
8
|
+
/** La pagina che contiene il pannello è a video: quelle in secondo piano restano montate e non devono caricare */
|
|
9
|
+
isPageVisible?: boolean;
|
|
7
10
|
fetchBlogDataTrigger?: number;
|
|
8
11
|
onRefreshBlogDatagrid?: () => Promise<void>;
|
|
9
12
|
showFloatingCommentButton?: boolean;
|
|
@@ -8,7 +8,7 @@ import TMBlogCommentForm from '../blog/TMBlogCommentForm';
|
|
|
8
8
|
import TMBlogsPost from '../../grids/TMBlogsPost';
|
|
9
9
|
import TMSpinner from '../../base/TMSpinner';
|
|
10
10
|
import { TMExceptionBoxManager } from '../../base/TMPopUp';
|
|
11
|
-
const TMDcmtBlog = ({ tid, did, isVisible, fetchBlogDataTrigger, onRefreshBlogDatagrid, showFloatingCommentButton = true, allTasks = [], getAllTasks, deleteTaskByIdsCallback, addTaskCallback, editTaskCallback, handleNavigateToWGs, handleNavigateToDossiers }) => {
|
|
11
|
+
const TMDcmtBlog = ({ tid, did, isVisible, isPageVisible = true, fetchBlogDataTrigger, onRefreshBlogDatagrid, showFloatingCommentButton = true, allTasks = [], getAllTasks, deleteTaskByIdsCallback, addTaskCallback, editTaskCallback, handleNavigateToWGs, handleNavigateToDossiers }) => {
|
|
12
12
|
const [blogsDatasource, setBlogsDatasource] = useState([]);
|
|
13
13
|
const [hasLoadedDataOnce, setHasLoadedDataOnce] = useState(false); //traccia se *qualsiasi* dato è stato caricato per la prima volta
|
|
14
14
|
const [lastLoadedDid, setLastLoadedDid] = useState(undefined); // `lastLoadedDid` tiene traccia dell'ultimo `did` per cui abbiamo caricato i dati
|
|
@@ -47,15 +47,15 @@ const TMDcmtBlog = ({ tid, did, isVisible, fetchBlogDataTrigger, onRefreshBlogDa
|
|
|
47
47
|
return;
|
|
48
48
|
}
|
|
49
49
|
// Condizione per eseguire il fetch:
|
|
50
|
-
// 1. Il pannello è visibile
|
|
50
|
+
// 1. Il pannello è visibile in una pagina a video
|
|
51
51
|
// 2. E (non abbiamo ancora caricato dati o il `did` è cambiato rispetto all'ultima volta)
|
|
52
|
-
const shouldFetch = isVisible && (!hasLoadedDataOnce || did !== lastLoadedDid);
|
|
52
|
+
const shouldFetch = isVisible && isPageVisible && (!hasLoadedDataOnce || did !== lastLoadedDid);
|
|
53
53
|
// Esegui la chiamata API solo se il pannello è visibile E i dati non sono già stati caricati
|
|
54
54
|
// O, se vuoi ricaricare ogni volta che diventa visibile (ma è meno efficiente per "pesante")
|
|
55
55
|
if (shouldFetch) {
|
|
56
56
|
fetchBlogDataAsync(tid, did);
|
|
57
57
|
}
|
|
58
|
-
}, [tid, did, isVisible, hasLoadedDataOnce, lastLoadedDid]);
|
|
58
|
+
}, [tid, did, isVisible, isPageVisible, hasLoadedDataOnce, lastLoadedDid]);
|
|
59
59
|
const refreshCallback = async () => {
|
|
60
60
|
await fetchBlogDataAsync(tid, did);
|
|
61
61
|
await onRefreshBlogDatagrid?.();
|
|
@@ -1504,7 +1504,7 @@ const TMDcmtForm = ({ TID, DID, groupId, layoutMode = LayoutModes.Update, formMo
|
|
|
1504
1504
|
for (const key of Object.keys(persistedState)) {
|
|
1505
1505
|
if (key === 'tmWF') {
|
|
1506
1506
|
// Hide tmWF
|
|
1507
|
-
newState[key] = { ...persistedState[key], visible: false };
|
|
1507
|
+
newState[key] = { ...persistedState[key], visible: false, width: '0%' };
|
|
1508
1508
|
}
|
|
1509
1509
|
else if (otherVisiblePanels.includes(key)) {
|
|
1510
1510
|
// Add extra width to visible panels
|
|
@@ -1531,6 +1531,8 @@ const TMDcmtForm = ({ TID, DID, groupId, layoutMode = LayoutModes.Update, formMo
|
|
|
1531
1531
|
const persistedState = invocationContext === InvocationContext.Todo ? settings?.layoutToDo : settings?.layout;
|
|
1532
1532
|
return persistedState?.['tmWF']?.visible ?? false;
|
|
1533
1533
|
};
|
|
1534
|
+
// Persisted tmWF visibility, read once at mount
|
|
1535
|
+
const persistedWFVisibleRef = useRef(getPersistedWFVisible());
|
|
1534
1536
|
const onBlogCommentFormCustomSave = useCallback(async (blogPost) => {
|
|
1535
1537
|
try {
|
|
1536
1538
|
if (!moreInfoTasks || moreInfoTasks.length === 0) {
|
|
@@ -1618,7 +1620,7 @@ const TMDcmtForm = ({ TID, DID, groupId, layoutMode = LayoutModes.Update, formMo
|
|
|
1618
1620
|
overflow: 'hidden'
|
|
1619
1621
|
}, children: [_jsxs("div", { style: { width: '100%', height: '100%', display: isOpenDetails || isOpenMaster ? 'none' : 'flex' }, children: [isNavigating && _jsx(Spinner, { description: SDKUI_Localizator.Loading, flat: false }), (fromDTD) && _jsx(TMLayoutWaitingContainer, { direction: 'vertical', showWaitPanel: useWaitPanelLocalState ? showWaitPanelLocal : showWaitPanel, showWaitPanelPrimary: useWaitPanelLocalState ? showPrimaryLocal : showPrimary, showWaitPanelSecondary: useWaitPanelLocalState ? showSecondaryLocal : showSecondary, waitPanelTitle: useWaitPanelLocalState ? waitPanelTitleLocal : waitPanelTitle, waitPanelTextPrimary: useWaitPanelLocalState ? waitPanelTextPrimaryLocal : waitPanelTextPrimary, waitPanelValuePrimary: useWaitPanelLocalState ? waitPanelValuePrimaryLocal : waitPanelValuePrimary, waitPanelMaxValuePrimary: useWaitPanelLocalState ? waitPanelMaxValuePrimaryLocal : waitPanelMaxValuePrimary, waitPanelTextSecondary: useWaitPanelLocalState ? waitPanelTextSecondaryLocal : waitPanelTextSecondary, waitPanelValueSecondary: useWaitPanelLocalState ? waitPanelValueSecondaryLocal : waitPanelValueSecondary, waitPanelMaxValueSecondary: useWaitPanelLocalState ? waitPanelMaxValueSecondaryLocal : waitPanelMaxValueSecondary, isCancelable: useWaitPanelLocalState ? dcmtFile ? dcmtFile.size >= 1000000 : false : true, abortController: useWaitPanelLocalState ? abortControllerLocal : abortController, children: _jsxs(TMLayoutWaitingContainer, { direction: 'vertical', showWaitPanel: showCicoWaitPanel, showWaitPanelPrimary: showCicoPrimaryProgress, waitPanelTitle: cicoWaitPanelTitle, waitPanelTextPrimary: cicoPrimaryProgressText, waitPanelValuePrimary: cicoPrimaryProgressValue, waitPanelMaxValuePrimary: cicoPrimaryProgressMax, isCancelable: true, abortController: abortControllerLocal, children: [(groupId && groupId.length > 0)
|
|
1620
1622
|
? _jsxs(_Fragment, { children: [_jsx(PanelDisabledStateHandler, { isWFDisabled: isWFDisabled, isSysMetadataDisabled: isSysMetadataDisabled, isBoardDisabled: isBoardDisabled, isDcmtTasksDisabled: isDcmtTasksDisabled, isPreviewDisabled: isPreviewDisabled }), _jsx(TMPanelManagerContainer, { panels: initialPanels, direction: "horizontal", parentId: groupId, showToolbar: showDcmtFormSidebar })] })
|
|
1621
|
-
: _jsxs(TMPanelManagerWithPersistenceProvider, { panels: initialPanels, initialVisibility: allInitialPanelVisibility, defaultDimensions: defaultPanelDimensions, initialDimensions: defaultPanelDimensions, initialMobilePanelId: 'tmDcmtForm', isPersistenceEnabled: !isMobile && layoutMode !== LayoutModes.Ark ? hasSavedLayout() : false, persistPanelStates: !isMobile && layoutMode !== LayoutModes.Ark ? (state) => persistPanelStates(state) : undefined, persistedPanelStates: layoutMode !== LayoutModes.Ark ? getPersistedPanelStates() : undefined, children: [_jsx(PanelDisabledStateHandler, { isWFDisabled: isWFDisabled, isSysMetadataDisabled: isSysMetadataDisabled, isBoardDisabled: isBoardDisabled, isDcmtTasksDisabled: isDcmtTasksDisabled, isPreviewDisabled: isPreviewDisabled, persistedWFVisible:
|
|
1623
|
+
: _jsxs(TMPanelManagerWithPersistenceProvider, { panels: initialPanels, initialVisibility: allInitialPanelVisibility, defaultDimensions: defaultPanelDimensions, initialDimensions: defaultPanelDimensions, initialMobilePanelId: 'tmDcmtForm', isPersistenceEnabled: !isMobile && layoutMode !== LayoutModes.Ark ? hasSavedLayout() : false, persistPanelStates: !isMobile && layoutMode !== LayoutModes.Ark ? (state) => persistPanelStates(state) : undefined, persistedPanelStates: layoutMode !== LayoutModes.Ark ? getPersistedPanelStates() : undefined, children: [_jsx(PanelDisabledStateHandler, { isWFDisabled: isWFDisabled, isSysMetadataDisabled: isSysMetadataDisabled, isBoardDisabled: isBoardDisabled, isDcmtTasksDisabled: isDcmtTasksDisabled, isPreviewDisabled: isPreviewDisabled, persistedWFVisible: persistedWFVisibleRef.current }), _jsx(TMPanelManagerContainer, { panels: initialPanels, direction: "horizontal", parentId: groupId, showToolbar: showDcmtFormSidebar })] }), isOpenDistinctValues &&
|
|
1622
1624
|
_jsx(TMDistinctValues, { tid: TID, mid: focusedMetadataValue?.mid, isModal: true, showHeader: false, layoutMode: layoutMode, onClosePanelCallback: () => setIsOpenDistinctValues(false), onSelectionChanged: (e) => {
|
|
1623
1625
|
if (!e)
|
|
1624
1626
|
return;
|
|
@@ -1781,6 +1783,8 @@ const PanelDisabledStateHandler = ({ isWFDisabled, isSysMetadataDisabled, isBoar
|
|
|
1781
1783
|
setToolbarButtonDisabled('tmBlog', false);
|
|
1782
1784
|
}
|
|
1783
1785
|
}, [isBoardDisabled]);
|
|
1786
|
+
// Tracks whether tmWF has already been restored from the persisted state
|
|
1787
|
+
const didRestoreWF = useRef(false);
|
|
1784
1788
|
useEffect(() => {
|
|
1785
1789
|
if (isWFDisabled) {
|
|
1786
1790
|
setToolbarButtonDisabled('tmWF', true);
|
|
@@ -1789,7 +1793,8 @@ const PanelDisabledStateHandler = ({ isWFDisabled, isSysMetadataDisabled, isBoar
|
|
|
1789
1793
|
else {
|
|
1790
1794
|
setToolbarButtonDisabled('tmWF', false);
|
|
1791
1795
|
// Restore persisted visibility when WF becomes enabled
|
|
1792
|
-
if (persistedWFVisible) {
|
|
1796
|
+
if (persistedWFVisible && !didRestoreWF.current) {
|
|
1797
|
+
didRestoreWF.current = true;
|
|
1793
1798
|
setPanelVisibilityById('tmWF', true);
|
|
1794
1799
|
}
|
|
1795
1800
|
}
|
|
@@ -50,13 +50,17 @@ const TMDcmtPreview = ({ dcmtData, isResizingActive, isVisible, canNext, canPrev
|
|
|
50
50
|
return;
|
|
51
51
|
}
|
|
52
52
|
const currentCacheKey = isBasketMode ? `basket-${dcmtData.btid}-${dcmtData.bid}-${dcmtData.bfid}` : `${dcmtData.tid}-${dcmtData.did}`;
|
|
53
|
-
|
|
53
|
+
// Fuori campo non si carica: l'anteprima nativa creata in una pagina non a video resta rotta
|
|
54
|
+
if (!isVisible)
|
|
55
|
+
return;
|
|
56
|
+
// Documento già caricato: ricaricarlo ricreerebbe il visualizzatore senza motivo
|
|
57
|
+
if (hasLoadedDataOnce && currentCacheKey === lastLoadedDid)
|
|
58
|
+
return;
|
|
54
59
|
if (isDcmtFileInCache(currentCacheKey)) {
|
|
55
60
|
isBasketMode ? loadBasketFile() : loadDocumentWithCache();
|
|
56
61
|
setShowPreview(true);
|
|
57
|
-
return;
|
|
58
62
|
}
|
|
59
|
-
|
|
63
|
+
else {
|
|
60
64
|
setDcmtBlob(undefined);
|
|
61
65
|
setError('');
|
|
62
66
|
setIsAbortError(false);
|
|
@@ -71,10 +75,10 @@ const TMDcmtPreview = ({ dcmtData, isResizingActive, isVisible, canNext, canPrev
|
|
|
71
75
|
else {
|
|
72
76
|
setShowPreview(false);
|
|
73
77
|
}
|
|
74
|
-
setHasLoadedDataOnce(true);
|
|
75
|
-
setLastLoadedDid(currentCacheKey);
|
|
76
78
|
}
|
|
77
|
-
|
|
79
|
+
setHasLoadedDataOnce(true);
|
|
80
|
+
setLastLoadedDid(currentCacheKey);
|
|
81
|
+
}, [dcmtData?.tid, dcmtData?.did, isVisible, hasLoadedDataOnce, lastLoadedDid]);
|
|
78
82
|
const loadBasketFile = async () => {
|
|
79
83
|
try {
|
|
80
84
|
// Check cache first
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } 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 { customizeSearchByDisplayText, deepCompare, formatDateTimeByMetadataFormat, formatNumberByMetadataFormat, generateUniqueColumnKeys, genUniqueId, getColumnFormatInfo, getDateDisplayText, getNumberDisplayText, getSearchToolbarVisibility, IconAll, IconBoard, IconDcmtTypeSys, IconDelete, IconDuplicate, IconEdit, IconMenuVertical, IconPlatform, IconRefresh, IconSearchCheck, IconShow, isApprovalWorkflowView, isSign4TopEnabled, searchResultDescriptorToSimpleArray, searchResultToMetadataValues, SDKUI_Globals, SDKUI_Localizator, IconCloseOutline } from '../../../helper';
|
|
6
|
+
import { customizeSearchByDisplayText, deepCompare, formatDateTimeByMetadataFormat, formatNumberByMetadataFormat, generateUniqueColumnKeys, genUniqueId, getColumnFormatInfo, getDateDisplayText, getNumberDisplayText, getSearchToolbarVisibility, IconAll, IconBoard, IconDcmtTypeSys, IconDelete, IconDuplicate, IconEdit, IconMenuVertical, IconPlatform, IconRefresh, IconSearchCheck, IconShow, isApprovalWorkflowView, isSign4TopEnabled, searchResultDescriptorToSimpleArray, searchResultToMetadataValues, SDKUI_Globals, SDKUI_Localizator, IconCloseOutline, getSearchResultPanelLayout, getSearchResultPanelLayoutSignature, hasSearchResultPanelLayout, saveSearchResultPanelLayout, subscribeSearchResultPanelLayout } from '../../../helper';
|
|
7
7
|
import { CounterItemKey } from '../../base/TMCounterContainer';
|
|
8
8
|
import { getDcmtCicoStatus } from '../../../helper/checkinCheckoutManager';
|
|
9
9
|
import { DcmtOperationTypes, SearchResultContext, } from '../../../ts';
|
|
@@ -23,7 +23,8 @@ import { StyledMultiViewPanel } from '../../base/Styled';
|
|
|
23
23
|
import { TMLayoutWaitingContainer } from '../../base/TMWaitPanel';
|
|
24
24
|
import TMMetadataValues from '../../editors/TMMetadataValues';
|
|
25
25
|
import TMTidViewer from '../../viewers/TMTidViewer';
|
|
26
|
-
import {
|
|
26
|
+
import { useTMPanelManagerContext } from '../../layout/panelManager/TMPanelManagerContext';
|
|
27
|
+
import { TMPanelManagerWithPersistenceProvider } from '../../layout/panelManager/TMPanelManagerWithPersistenceProvider';
|
|
27
28
|
import TMPanelManagerContainer from '../../layout/panelManager/TMPanelManagerContainer';
|
|
28
29
|
import TMContextMenu from '../../NewComponents/ContextMenu/TMContextMenu';
|
|
29
30
|
import TMToppyDraggableHelpCenter from '../assistant/TMToppyDraggableHelpCenter';
|
|
@@ -51,6 +52,16 @@ const orderByName = (array) => {
|
|
|
51
52
|
return 1;
|
|
52
53
|
} return 0; });
|
|
53
54
|
};
|
|
55
|
+
//#endregion Helper Methods
|
|
56
|
+
/**
|
|
57
|
+
* Contesti che ricordano il layout dei pannelli, condividendone uno solo.
|
|
58
|
+
* Gli altri sono esclusi di proposito: FREE_SEARCH ha un pannello in più, quindi il suo layout non vale
|
|
59
|
+
* per gli altri, e i restanti mostrano il risultato dentro contenitori con esigenze di spazio loro.
|
|
60
|
+
*/
|
|
61
|
+
const PERSISTED_LAYOUT_CONTEXTS = [
|
|
62
|
+
SearchResultContext.METADATA_SEARCH,
|
|
63
|
+
SearchResultContext.WORKFLOW_APPROVE,
|
|
64
|
+
];
|
|
54
65
|
const TMSearchResult = ({
|
|
55
66
|
// Data
|
|
56
67
|
groupId, searchResults = [], context = SearchResultContext.METADATA_SEARCH, title, selectedSearchResultTID, floatingActionConfig, workingGroupContext = undefined, inputDID,
|
|
@@ -794,9 +805,14 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
|
|
|
794
805
|
editPdfForm,
|
|
795
806
|
showNoDcmtFoundMessage
|
|
796
807
|
]);
|
|
797
|
-
|
|
808
|
+
/**
|
|
809
|
+
* Il risultato è davvero a video: le pagine restano montate anche in secondo piano, e senza questo
|
|
810
|
+
* bacheca e anteprima scaricherebbero il documento fuori campo.
|
|
811
|
+
*/
|
|
812
|
+
const isSearchResultOnScreen = isVisible && !isOpenDcmtForm && !isOpenDetails && !isOpenMaster;
|
|
813
|
+
const tmBlog = useMemo(() => _jsx(TMDcmtBlog, { tid: focusedItem?.TID, did: focusedItem?.DID, isPageVisible: isSearchResultOnScreen, fetchBlogDataTrigger: refreshBlogTrigger, allTasks: allTasks, getAllTasks: getAllTasks, deleteTaskByIdsCallback: deleteTaskByIdsCallback, addTaskCallback: addTaskCallback, editTaskCallback: editTaskCallback, handleNavigateToWGs: handleNavigateToWGs, handleNavigateToDossiers: handleNavigateToDossiers }), [focusedItem, allTasks, refreshBlogTrigger, isSearchResultOnScreen, handleNavigateToWGs, handleNavigateToDossiers, onRefreshBlogDatagrid]);
|
|
798
814
|
const tmSysMetadata = useMemo(() => _jsx(TMMetadataValues, { layoutMode: LayoutModes.Update, openChooserBySingleClick: true, TID: focusedItem?.TID, isReadOnly: true, deviceType: deviceType, metadataValues: currentMetadataValues.filter(o => (o.mid != undefined && o.mid <= 100)), metadataValuesOrig: currentMetadataValues.filter(o => (o.mid != undefined && o.mid <= 100)), validationItems: [] }), [focusedItem, currentMetadataValues, deviceType]);
|
|
799
|
-
const tmDcmtPreview = useMemo(() => _jsx(TMDcmtPreviewWrapper, { refreshPreviewTrigger: refreshPreviewTrigger, currentDcmt: currentDcmt, onBack: backHandlerSecondary }), [currentDcmt, refreshPreviewTrigger, backHandlerSecondary]);
|
|
815
|
+
const tmDcmtPreview = useMemo(() => _jsx(TMDcmtPreviewWrapper, { refreshPreviewTrigger: refreshPreviewTrigger, currentDcmt: currentDcmt, isPageVisible: isSearchResultOnScreen, onBack: backHandlerSecondary }), [currentDcmt, refreshPreviewTrigger, isSearchResultOnScreen, backHandlerSecondary]);
|
|
800
816
|
// Auto-fetch indexing info when drawer is open and focusedItem changes
|
|
801
817
|
useEffect(() => {
|
|
802
818
|
if (!focusedItem || !showIndexingInfo)
|
|
@@ -853,6 +869,22 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
|
|
|
853
869
|
}
|
|
854
870
|
return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', height: '100%', overflow: 'hidden', width: '100%' }, children: [_jsx("div", { style: { padding: '10px', overflow: 'auto', flex: 1 }, children: _jsx("div", { dangerouslySetInnerHTML: { __html: ftExplanation } }) }), _jsxs(StyledIndexingInfoSection, { children: [_jsxs(StyledIndexingToggle, { onClick: handleToggleIndexingInfo, disabled: loadingIndexingInfo, children: [_jsx(StyledLeftContent, { children: _jsx("span", { children: SDKUI_Localizator.IndexingInformation }) }), _jsx(StyledRightContent, { children: _jsx(StyledChevron, { "$isOpen": showIndexingInfo, children: "\u25BC" }) })] }), showIndexingInfo && indexingInfo && (_jsxs(StyledIndexingInfoBox, { children: [_jsx("div", { dangerouslySetInnerHTML: { __html: indexingInfo } }), loadingIndexingInfo && (_jsxs("div", { style: { position: 'absolute', top: '50%', left: '50%', transform: 'translate(-50%, -50%)', background: 'rgba(255, 255, 255, 0.9)', padding: '10px', borderRadius: '4px', boxShadow: '0 2px 8px rgba(0,0,0,0.15)' }, children: [SDKUI_Localizator.Loading, "..."] }))] }))] })] }));
|
|
855
871
|
}, [selectedSearchResult, focusedItem, indexingInfo, showIndexingInfo, loadingIndexingInfo]);
|
|
872
|
+
/**
|
|
873
|
+
* Su mobile i pannelli si mostrano uno per volta, quindi il layout non va ricordato; con groupId
|
|
874
|
+
* i pannelli sono di un panel manager esterno, che ha un layout suo. Senza la barra laterale
|
|
875
|
+
* l'utente non può aprire/chiudere i pannelli: non ha senso applicargli il layout salvato (li
|
|
876
|
+
* troverebbe aperti senza poterli chiudere) né lasciargli sporcare quello condiviso.
|
|
877
|
+
* Il layout salvato vale solo per il login con Surfer: gli altri moduli hanno layout propri.
|
|
878
|
+
*/
|
|
879
|
+
const isLayoutPersisted = SDK_Globals.tmSession?.SessionDescr?.appModuleID === AppModules.SURFER && !isMobile && !groupId?.length && showSearchResultSidebar && PERSISTED_LAYOUT_CONTEXTS.includes(context);
|
|
880
|
+
/**
|
|
881
|
+
* Pannelli che questo risultato non può mostrare (la bacheca è l'unico, vedi PanelDisabledStateHandler).
|
|
882
|
+
* Il layout è condiviso con gli altri risultati aperti: qui non vanno aperti quando il layout lo chiede,
|
|
883
|
+
* e il loro stato salvato non va toccato, altrimenti li si chiuderebbe dove invece sono attivi.
|
|
884
|
+
*/
|
|
885
|
+
const disabledLayoutPanelIds = isBoardDisabled ? ['tmBlog'] : [];
|
|
886
|
+
/** Bacheca aperta nel layout salvato: da riaprire quando il tipo documento torna a prevederla */
|
|
887
|
+
const savedBlogVisible = isLayoutPersisted && (getSearchResultPanelLayout()['tmBlog']?.visible ?? false);
|
|
856
888
|
const allInitialPanelVisibility = {
|
|
857
889
|
'tmSearchResult': true,
|
|
858
890
|
'tmBlog': false,
|
|
@@ -935,6 +967,8 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
|
|
|
935
967
|
toolbarOptions: { icon: _jsx(IconShow, { fontSize: 24 }), visible: getSearchToolbarVisibility(SDK_Globals.tmSession?.SessionDescr?.appModuleID ?? AppModules.SURFER).tmDcmtPreview, orderNumber: context === SearchResultContext.FREE_SEARCH ? 5 : 4, isActive: allInitialPanelVisibility['tmDcmtPreview'] }
|
|
936
968
|
}
|
|
937
969
|
], [tmSearchResult, tmBlog, tmSysMetadata, tmDcmtPreview, tmFullTextSearch, showToolbarHeader, context, isMobile, backHandler, backHandlerSecondary, isClosable, onBack]);
|
|
970
|
+
/** Contenuto del panel manager: con groupId sta nel provider del contenitore, altrimenti in quello qui sotto */
|
|
971
|
+
const panelManagerContent = (_jsxs(_Fragment, { children: [_jsx(PanelDisabledStateHandler, { isBoardDisabled: isBoardDisabled, savedBlogVisible: savedBlogVisible }), isLayoutPersisted && _jsx(SavedLayoutSyncHandler, { disabledPanelIds: disabledLayoutPanelIds }), _jsx(ShowMainPanelBridge, { showMainPanelRef: showMainPanelRef }), _jsx(TMPanelManagerContainer, { panels: initialPanels, direction: "horizontal", parentId: groupId, showToolbar: showSearchResultSidebar })] }));
|
|
938
972
|
return (_jsxs(StyledMultiViewPanel, { "$isVisible": isVisible, children: [_jsx(StyledMultiViewPanel, { "$isVisible": !isOpenDcmtForm && !isOpenDetails && !isOpenMaster, style: {
|
|
939
973
|
display: 'flex',
|
|
940
974
|
flexDirection: isMobile ? 'column' : 'row',
|
|
@@ -942,10 +976,10 @@ handleNavigateToWGs, handleNavigateToDossiers, fetchRemoteCertificates, graphome
|
|
|
942
976
|
gap: Gutters.getGutters(),
|
|
943
977
|
width: '100%',
|
|
944
978
|
height: '100%',
|
|
945
|
-
}, children: _jsx(TMLayoutWaitingContainer, { direction: 'vertical', showWaitPanel: showWaitPanel, showWaitPanelPrimary: showPrimary, showWaitPanelSecondary: showSecondary, waitPanelTitle: waitPanelTitle, waitPanelTextPrimary: waitPanelTextPrimary, waitPanelValuePrimary: waitPanelValuePrimary, waitPanelMaxValuePrimary: waitPanelMaxValuePrimary, waitPanelTextSecondary: waitPanelTextSecondary, waitPanelValueSecondary: waitPanelValueSecondary, waitPanelMaxValueSecondary: waitPanelMaxValueSecondary, isCancelable: true, abortController: abortController, children: _jsx(TMLayoutWaitingContainer, { direction: 'vertical', showWaitPanel: showCicoWaitPanel, showWaitPanelPrimary: showCicoPrimaryProgress, waitPanelTitle: cicoWaitPanelTitle, waitPanelTextPrimary: cicoPrimaryProgressText, waitPanelValuePrimary: cicoPrimaryProgressValue, waitPanelMaxValuePrimary: cicoPrimaryProgressMax, isCancelable: true, abortController: abortControllerLocal, children: (groupId && groupId.length > 0) ?
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
979
|
+
}, children: _jsx(TMLayoutWaitingContainer, { direction: 'vertical', showWaitPanel: showWaitPanel, showWaitPanelPrimary: showPrimary, showWaitPanelSecondary: showSecondary, waitPanelTitle: waitPanelTitle, waitPanelTextPrimary: waitPanelTextPrimary, waitPanelValuePrimary: waitPanelValuePrimary, waitPanelMaxValuePrimary: waitPanelMaxValuePrimary, waitPanelTextSecondary: waitPanelTextSecondary, waitPanelValueSecondary: waitPanelValueSecondary, waitPanelMaxValueSecondary: waitPanelMaxValueSecondary, isCancelable: true, abortController: abortController, children: _jsx(TMLayoutWaitingContainer, { direction: 'vertical', showWaitPanel: showCicoWaitPanel, showWaitPanelPrimary: showCicoPrimaryProgress, waitPanelTitle: cicoWaitPanelTitle, waitPanelTextPrimary: cicoPrimaryProgressText, waitPanelValuePrimary: cicoPrimaryProgressValue, waitPanelMaxValuePrimary: cicoPrimaryProgressMax, isCancelable: true, abortController: abortControllerLocal, children: (groupId && groupId.length > 0) ? panelManagerContent
|
|
980
|
+
// Senza le prop di persistenza il provider si comporta come quello base:
|
|
981
|
+
// i contesti che non ricordano il layout restano come prima
|
|
982
|
+
: _jsx(TMPanelManagerWithPersistenceProvider, { panels: initialPanels, initialVisibility: allInitialPanelVisibility, defaultDimensions: initialPanelDimensions, initialDimensions: initialPanelDimensions, initialMobilePanelId: 'tmSearchResult', isPersistenceEnabled: isLayoutPersisted && hasSearchResultPanelLayout(), persistPanelStates: isLayoutPersisted ? (state) => saveSearchResultPanelLayout(state, disabledLayoutPanelIds) : undefined, persistedPanelStates: isLayoutPersisted ? getSearchResultPanelLayout() : undefined, children: panelManagerContent }) }) }) }), renderDcmtOperations] }));
|
|
949
983
|
};
|
|
950
984
|
export default TMSearchResult;
|
|
951
985
|
/** Cerca una voce di operazione per id, anche dentro i sottomenu */
|
|
@@ -1608,24 +1642,70 @@ const ShowMainPanelBridge = ({ showMainPanelRef }) => {
|
|
|
1608
1642
|
}, [setPanelVisibilityById, showMainPanelRef]);
|
|
1609
1643
|
return null;
|
|
1610
1644
|
};
|
|
1611
|
-
|
|
1612
|
-
|
|
1645
|
+
/** Senza UI: allinea i pannelli quando il layout viene salvato da un altro risultato di ricerca già aperto */
|
|
1646
|
+
const SavedLayoutSyncHandler = ({ disabledPanelIds }) => {
|
|
1647
|
+
const { panelVisibility, panelDimensions, maximizedPanels, isResizingActive, setPanelVisibilityById, setPanelDimensionsById } = useTMPanelManagerContext();
|
|
1648
|
+
// Il layout è condiviso fra i risultati aperti: si sottoscrive la firma, che è una stringa, perché
|
|
1649
|
+
// React confronta le letture per riferimento e su un oggetto ri-renderizzerebbe all'infinito
|
|
1650
|
+
const savedLayoutSignature = useSyncExternalStore(subscribeSearchResultPanelLayout, getSearchResultPanelLayoutSignature);
|
|
1613
1651
|
useEffect(() => {
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1652
|
+
// Con un pannello massimizzato o un separatore in movimento comanda l'utente di questa istanza
|
|
1653
|
+
if (maximizedPanels.length > 0 || isResizingActive)
|
|
1654
|
+
return;
|
|
1655
|
+
// Al mount panelVisibility è ancora vuota: lo stato iniziale lo applica il provider.
|
|
1656
|
+
// Un pannello disabilitato qui non va aperto, gli altri si allineano comunque
|
|
1657
|
+
const panelsToApply = Object.entries(getSearchResultPanelLayout())
|
|
1658
|
+
.filter(([id, state]) => panelVisibility[id] !== undefined && !(state?.visible && disabledPanelIds.includes(id)));
|
|
1659
|
+
panelsToApply.forEach(([id, state]) => {
|
|
1660
|
+
const isVisible = state?.visible ?? false;
|
|
1661
|
+
if (panelVisibility[id] !== isVisible)
|
|
1662
|
+
setPanelVisibilityById(id, isVisible);
|
|
1663
|
+
});
|
|
1664
|
+
// Le larghezze salvate valgono come pesi, non come percentuali: qui si normalizzano sui soli pannelli
|
|
1665
|
+
// che questo risultato apre davvero, così lo spazio di quelli scartati non resta vuoto
|
|
1666
|
+
const panelsToShow = panelsToApply.filter(([, state]) => state?.visible && parseFloat(state.width) > 0 && parseFloat(state.height) > 0);
|
|
1667
|
+
const weightTotal = panelsToShow.reduce((total, [, state]) => total + parseFloat(state.width), 0);
|
|
1668
|
+
if (weightTotal <= 0)
|
|
1669
|
+
return;
|
|
1670
|
+
// Dopo la visibilità, che accoda un suo ricalcolo delle quote: così restano quelle salvate.
|
|
1671
|
+
// Le quote sono arrotondate, altrimenti due risultati si rincorrono sugli ultimi decimali
|
|
1672
|
+
panelsToShow.forEach(([id, state]) => {
|
|
1673
|
+
const width = `${Math.round(parseFloat(state.width) / weightTotal * 10000) / 100}%`;
|
|
1674
|
+
if (panelDimensions[id]?.width === width && panelDimensions[id]?.height === state.height)
|
|
1675
|
+
return;
|
|
1676
|
+
setPanelDimensionsById(id, width, state.height);
|
|
1677
|
+
});
|
|
1678
|
+
}, [savedLayoutSignature]);
|
|
1679
|
+
return null;
|
|
1680
|
+
};
|
|
1681
|
+
const PanelDisabledStateHandler = ({ isBoardDisabled, savedBlogVisible = false }) => {
|
|
1682
|
+
const { panelVisibility, setPanelVisibilityById, setToolbarButtonDisabled } = useTMPanelManagerContext();
|
|
1683
|
+
const isBlogVisible = panelVisibility['tmBlog'] ?? false;
|
|
1684
|
+
useEffect(() => {
|
|
1685
|
+
setToolbarButtonDisabled('tmBlog', isBoardDisabled);
|
|
1621
1686
|
}, [isBoardDisabled]);
|
|
1687
|
+
/**
|
|
1688
|
+
* Senza bacheca nel tipo documento il pannello va chiuso ogni volta che ricompare, non solo al cambio
|
|
1689
|
+
* di stato: il layout salvato viene applicato dopo questo effetto e può riportarlo a video.
|
|
1690
|
+
* È un effetto di layout per chiuderlo prima che venga disegnato, altrimenti lampeggia.
|
|
1691
|
+
*/
|
|
1692
|
+
useLayoutEffect(() => {
|
|
1693
|
+
if (isBoardDisabled && isBlogVisible)
|
|
1694
|
+
setPanelVisibilityById('tmBlog', false);
|
|
1695
|
+
}, [isBoardDisabled, isBlogVisible]);
|
|
1696
|
+
// Bacheca di nuovo prevista: si riapre come da layout salvato. isBlogVisible non è fra le dipendenze,
|
|
1697
|
+
// altrimenti riaprirebbe il pannello appena chiuso dall'utente
|
|
1698
|
+
useEffect(() => {
|
|
1699
|
+
if (!isBoardDisabled && savedBlogVisible && !isBlogVisible)
|
|
1700
|
+
setPanelVisibilityById('tmBlog', true);
|
|
1701
|
+
}, [isBoardDisabled, savedBlogVisible]);
|
|
1622
1702
|
return null;
|
|
1623
1703
|
};
|
|
1624
|
-
const TMDcmtPreviewWrapper = ({ refreshPreviewTrigger, currentDcmt, isVisible, onBack }) => {
|
|
1704
|
+
const TMDcmtPreviewWrapper = ({ refreshPreviewTrigger, currentDcmt, isVisible, isPageVisible = true, onBack }) => {
|
|
1625
1705
|
const { setPanelVisibilityById, toggleMaximize, isResizingActive, countVisibleLeafPanels } = useTMPanelManagerContext();
|
|
1626
1706
|
const deviceType = useDeviceType();
|
|
1627
1707
|
const isMobile = deviceType === DeviceType.MOBILE;
|
|
1628
|
-
return (_jsx(TMDcmtPreview, { dcmtData: currentDcmt, onClosePanel: (!isMobile && countVisibleLeafPanels() > 1) ? () => setPanelVisibilityById('tmDcmtPreview', false) : undefined, onBack: onBack, allowMaximize: !isMobile && countVisibleLeafPanels() > 1, onMaximizePanel: (!isMobile && countVisibleLeafPanels() > 1) ? () => toggleMaximize("tmDcmtPreview") : undefined, isResizingActive: isResizingActive, isVisible: isVisible }, refreshPreviewTrigger));
|
|
1708
|
+
return (_jsx(TMDcmtPreview, { dcmtData: currentDcmt, onClosePanel: (!isMobile && countVisibleLeafPanels() > 1) ? () => setPanelVisibilityById('tmDcmtPreview', false) : undefined, onBack: onBack, allowMaximize: !isMobile && countVisibleLeafPanels() > 1, onMaximizePanel: (!isMobile && countVisibleLeafPanels() > 1) ? () => toggleMaximize("tmDcmtPreview") : undefined, isResizingActive: isResizingActive, isVisible: isVisible && isPageVisible }, refreshPreviewTrigger));
|
|
1629
1709
|
};
|
|
1630
1710
|
// Styled Components
|
|
1631
1711
|
const StyledPlaceholder = styled.div `
|
|
@@ -14,12 +14,14 @@ export const TMPanelManagerProvider = (props) => {
|
|
|
14
14
|
const defaultPanelDimensionsRef = useRef(defaultDimensions);
|
|
15
15
|
// Memoize the panel hierarchy map to avoid re-generating it on every render unless `panels` change
|
|
16
16
|
const hierarchyMap = useMemo(() => generatePanelHierarchyMap(panels), [panels]);
|
|
17
|
-
// State to track the dimensions (width and height) of each panel, initialized with props
|
|
18
|
-
const [
|
|
17
|
+
// State to track the visibility and the dimensions (width and height) of each panel, initialized with props
|
|
18
|
+
const [panelLayout, setPanelLayout] = useState({ visibility: {}, dimensions: initialDimensions });
|
|
19
|
+
// Visibility of each panel, keyed by panel ID
|
|
20
|
+
const panelVisibility = panelLayout.visibility;
|
|
21
|
+
// Dimensions of each panel, keyed by panel ID
|
|
22
|
+
const panelDimensions = panelLayout.dimensions;
|
|
19
23
|
// IDs of maximized panels, including their parents if present
|
|
20
24
|
const [maximizedPanels, setMaximizedPanels] = useState([]);
|
|
21
|
-
// State to track the visibility of each panel, keyed by panel ID
|
|
22
|
-
const [panelVisibility, setPanelVisibility] = useState({});
|
|
23
25
|
// State to track visibility of toolbar buttons, keyed by panel ID
|
|
24
26
|
const [toolbarButtonsVisibility, setToolbarButtonsVisibility] = useState({});
|
|
25
27
|
// State to track disabled status of toolbar buttons, keyed by panel ID
|
|
@@ -31,93 +33,73 @@ export const TMPanelManagerProvider = (props) => {
|
|
|
31
33
|
setToolbarButtonsVisibility(visibilityMap);
|
|
32
34
|
setToolbarButtonsDisabled(disabledMap);
|
|
33
35
|
}, []);
|
|
34
|
-
// Callback to update the visibility state of a specific panel and its related hierarchy
|
|
35
|
-
const adjustPanelVisibilityAndSize = useCallback((id, isVisible,
|
|
36
|
+
// Callback to update the visibility state of a specific panel and its related hierarchy, redistributing the dimensions accordingly
|
|
37
|
+
const adjustPanelVisibilityAndSize = useCallback((id, isVisible, prevLayout) => {
|
|
36
38
|
// Clone previous visibility state to work with
|
|
37
|
-
let updatedVisibility = { ...
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const parentsToShow = showParentRecursively(id, hierarchyMap);
|
|
44
|
-
parentsToShow.forEach(([pid, visible]) => { updatedVisibility[pid] = visible; });
|
|
45
|
-
// Show the target panel
|
|
46
|
-
updatedVisibility[id] = true;
|
|
47
|
-
// Redistribute dimensions to account for newly shown panel
|
|
48
|
-
setPanelDimensions(prev => redistributeDimensionsOnShow(id, prev, defaultPanelDimensionsRef.current, hierarchyMap, updatedVisibility));
|
|
49
|
-
}
|
|
50
|
-
else {
|
|
51
|
-
// Hide the panel
|
|
52
|
-
updatedVisibility[id] = false;
|
|
53
|
-
// Recursively hide parents
|
|
54
|
-
const parentsToHide = hideParentRecursively(id, hierarchyMap, updatedVisibility);
|
|
55
|
-
parentsToHide.forEach(([pid, visible]) => { updatedVisibility[pid] = visible; });
|
|
56
|
-
// Redistribute dimensions after hiding
|
|
57
|
-
setPanelDimensions(prev => redistributeDimensionsOnHide(id, prev, hierarchyMap, updatedVisibility));
|
|
39
|
+
let updatedVisibility = { ...prevLayout.visibility };
|
|
40
|
+
let updatedDimensions;
|
|
41
|
+
if (isVisible) {
|
|
42
|
+
// On mobile, showing one panel hides all others first
|
|
43
|
+
if (isMobile) {
|
|
44
|
+
updatedVisibility = Object.keys(prevLayout.visibility).reduce((acc, key) => { acc[key] = false; return acc; }, {});
|
|
58
45
|
}
|
|
46
|
+
// Recursively determine and show parent panels
|
|
47
|
+
const parentsToShow = showParentRecursively(id, hierarchyMap);
|
|
48
|
+
parentsToShow.forEach(([pid, visible]) => { updatedVisibility[pid] = visible; });
|
|
49
|
+
// Show the target panel
|
|
50
|
+
updatedVisibility[id] = true;
|
|
51
|
+
// Redistribute dimensions to account for newly shown panel
|
|
52
|
+
updatedDimensions = redistributeDimensionsOnShow(id, prevLayout.dimensions, defaultPanelDimensionsRef.current, hierarchyMap, updatedVisibility);
|
|
59
53
|
}
|
|
60
54
|
else {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
setPanelDimensions(prev => redistributeDimensionsOnShow(id, prev, defaultPanelDimensionsRef.current, hierarchyMap, updatedVisibility));
|
|
69
|
-
}
|
|
70
|
-
else {
|
|
71
|
-
// Hide the panel
|
|
72
|
-
updatedVisibility[id] = false;
|
|
73
|
-
// Recursively hide parent panels
|
|
74
|
-
const parentsToHide = hideParentRecursively(id, hierarchyMap, updatedVisibility);
|
|
75
|
-
parentsToHide.forEach(([pid, visible]) => { updatedVisibility[pid] = visible; });
|
|
76
|
-
// Adjust dimensions after hiding
|
|
77
|
-
setPanelDimensions(prev => redistributeDimensionsOnHide(id, prev, hierarchyMap, updatedVisibility));
|
|
78
|
-
}
|
|
55
|
+
// Hide the panel
|
|
56
|
+
updatedVisibility[id] = false;
|
|
57
|
+
// Recursively hide parents
|
|
58
|
+
const parentsToHide = hideParentRecursively(id, hierarchyMap, updatedVisibility);
|
|
59
|
+
parentsToHide.forEach(([pid, visible]) => { updatedVisibility[pid] = visible; });
|
|
60
|
+
// Redistribute dimensions after hiding
|
|
61
|
+
updatedDimensions = redistributeDimensionsOnHide(id, prevLayout.dimensions, hierarchyMap, updatedVisibility);
|
|
79
62
|
}
|
|
80
|
-
return updatedVisibility;
|
|
63
|
+
return { visibility: updatedVisibility, dimensions: updatedDimensions };
|
|
81
64
|
}, [hierarchyMap, isMobile]);
|
|
82
65
|
// On initial mount: initialize panel visibility using the provided `initialVisibility` config
|
|
83
66
|
// Each panel's visibility is processed through `updatePanelVisibility` to ensure hierarchy logic is applied
|
|
84
67
|
useEffect(() => {
|
|
85
68
|
if (isPersistenceEnabled) {
|
|
86
69
|
// If persistence is enabled, visibility and dimensions are already synced from persisted state, so just set them directly
|
|
87
|
-
|
|
88
|
-
setPanelDimensions(initialDimensions);
|
|
70
|
+
setPanelLayout({ visibility: initialVisibility, dimensions: initialDimensions });
|
|
89
71
|
}
|
|
90
72
|
else {
|
|
91
73
|
// If persistence is not enabled, recalculate visibility and dimensions based on the initial visibility, applying the hierarchy logic
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
74
|
+
setPanelLayout(prev => {
|
|
75
|
+
let updated = { visibility: { ...initialVisibility }, dimensions: prev.dimensions };
|
|
76
|
+
Object.entries(initialVisibility).forEach(([id, isVisible]) => {
|
|
77
|
+
updated = adjustPanelVisibilityAndSize(id, isVisible, updated);
|
|
78
|
+
});
|
|
79
|
+
return updated;
|
|
95
80
|
});
|
|
96
|
-
// Update panel visibility state with recalculated values
|
|
97
|
-
setPanelVisibility(updated);
|
|
98
81
|
}
|
|
99
82
|
}, []);
|
|
100
83
|
// On mobile devices: automatically show the initial mobile panel when layout switches to mobile
|
|
101
84
|
// This ensures the correct panel is visible by default on smaller screens
|
|
102
85
|
useEffect(() => {
|
|
103
86
|
if (isMobile) {
|
|
104
|
-
|
|
105
|
-
return adjustPanelVisibilityAndSize(initialMobilePanelId, true, prev);
|
|
106
|
-
});
|
|
87
|
+
setPanelLayout(prev => adjustPanelVisibilityAndSize(initialMobilePanelId, true, prev));
|
|
107
88
|
}
|
|
108
89
|
else {
|
|
109
90
|
if (isPersistenceEnabled) {
|
|
110
91
|
// If persistence is enabled, visibility and dimensions are already synced from persisted state, so just set them directly
|
|
111
|
-
|
|
92
|
+
setPanelLayout(prev => ({ ...prev, visibility: initialVisibility }));
|
|
112
93
|
}
|
|
113
94
|
else {
|
|
114
95
|
// If persistence is not enabled, recalculate visibility and dimensions based on the initial visibility, applying the hierarchy logic
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
96
|
+
setPanelLayout(prev => {
|
|
97
|
+
let updated = { visibility: { ...initialVisibility }, dimensions: prev.dimensions };
|
|
98
|
+
Object.entries(initialVisibility).forEach(([id, isVisible]) => {
|
|
99
|
+
updated = adjustPanelVisibilityAndSize(id, isVisible, updated);
|
|
100
|
+
});
|
|
101
|
+
return updated;
|
|
118
102
|
});
|
|
119
|
-
// Update panel visibility state with recalculated values
|
|
120
|
-
setPanelVisibility(updated);
|
|
121
103
|
}
|
|
122
104
|
}
|
|
123
105
|
}, [isMobile]);
|
|
@@ -130,9 +112,9 @@ export const TMPanelManagerProvider = (props) => {
|
|
|
130
112
|
// Update state to track which panels are maximized
|
|
131
113
|
setMaximizedPanels(toMaximize);
|
|
132
114
|
// Update panel dimensions accordingly
|
|
133
|
-
|
|
115
|
+
setPanelLayout(prev => {
|
|
134
116
|
const newDimensions = {};
|
|
135
|
-
Object.keys(prev).forEach(pid => {
|
|
117
|
+
Object.keys(prev.dimensions).forEach(pid => {
|
|
136
118
|
if (toMaximize.includes(pid)) {
|
|
137
119
|
// For maximized panels (target + parents), set width and height to 100%
|
|
138
120
|
newDimensions[pid] = { width: '100%', height: '100%' };
|
|
@@ -142,31 +124,31 @@ export const TMPanelManagerProvider = (props) => {
|
|
|
142
124
|
newDimensions[pid] = { width: '0%', height: '0%' };
|
|
143
125
|
}
|
|
144
126
|
});
|
|
145
|
-
return newDimensions;
|
|
127
|
+
return { ...prev, dimensions: newDimensions };
|
|
146
128
|
});
|
|
147
129
|
}, [hierarchyMap]);
|
|
148
130
|
// Restore all panels to their original dimensions
|
|
149
131
|
const resetMaximization = useCallback(() => {
|
|
150
132
|
// Clear the list of maximized panels to exit maximize mode
|
|
151
133
|
setMaximizedPanels([]);
|
|
152
|
-
// Clone the current visibility state of panels
|
|
153
|
-
let updatedVisibility = { ...panelVisibility };
|
|
154
134
|
// Start with the initial panel dimensions as the base for restoration
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
135
|
+
setPanelLayout(prev => {
|
|
136
|
+
const updatedVisibility = prev.visibility;
|
|
137
|
+
const nextDimensions = Object.entries(updatedVisibility).reduce((acc, [id, isVisible]) => {
|
|
138
|
+
if (isVisible) {
|
|
139
|
+
// If the panel is visible, adjust dimensions to show it properly
|
|
140
|
+
acc = redistributeDimensionsOnShow(id, acc, defaultPanelDimensionsRef.current, hierarchyMap, updatedVisibility);
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
// If the panel is hidden, adjust dimensions to hide it
|
|
144
|
+
acc = redistributeDimensionsOnHide(id, acc, hierarchyMap, updatedVisibility);
|
|
145
|
+
}
|
|
146
|
+
return acc;
|
|
147
|
+
}, defaultPanelDimensionsRef.current);
|
|
148
|
+
// Apply the recalculated dimensions to the panel state
|
|
149
|
+
return { ...prev, dimensions: nextDimensions };
|
|
150
|
+
});
|
|
151
|
+
}, [hierarchyMap]);
|
|
170
152
|
// Toggle the maximized state of a panel by its ID
|
|
171
153
|
const toggleMaximize = useCallback((id) => {
|
|
172
154
|
if (maximizedPanels.includes(id)) {
|
|
@@ -185,17 +167,11 @@ export const TMPanelManagerProvider = (props) => {
|
|
|
185
167
|
// If it is maximized, first reset all maximized panels to their original state
|
|
186
168
|
resetMaximization();
|
|
187
169
|
// Then update the visibility of the panel, toggling its current state
|
|
188
|
-
|
|
189
|
-
const isCurrentlyVisible = prev[id];
|
|
190
|
-
return adjustPanelVisibilityAndSize(id, !isCurrentlyVisible, prev);
|
|
191
|
-
});
|
|
170
|
+
setPanelLayout(prev => adjustPanelVisibilityAndSize(id, !prev.visibility[id], prev));
|
|
192
171
|
}
|
|
193
172
|
else {
|
|
194
173
|
// If the panel is not maximized, simply toggle its visibility
|
|
195
|
-
|
|
196
|
-
const isCurrentlyVisible = prev[id];
|
|
197
|
-
return adjustPanelVisibilityAndSize(id, !isCurrentlyVisible, prev);
|
|
198
|
-
});
|
|
174
|
+
setPanelLayout(prev => adjustPanelVisibilityAndSize(id, !prev.visibility[id], prev));
|
|
199
175
|
}
|
|
200
176
|
}, [maximizedPanels, resetMaximization, adjustPanelVisibilityAndSize]);
|
|
201
177
|
// Sets the visibility of a panel by its ID to a specific value (true = show, false = hide)
|
|
@@ -205,14 +181,14 @@ export const TMPanelManagerProvider = (props) => {
|
|
|
205
181
|
resetMaximization();
|
|
206
182
|
}
|
|
207
183
|
// Then update the visibility state of the panel to the given value
|
|
208
|
-
|
|
184
|
+
setPanelLayout(prev => adjustPanelVisibilityAndSize(id, isVisible, prev));
|
|
209
185
|
}, [maximizedPanels, resetMaximization, adjustPanelVisibilityAndSize]);
|
|
210
186
|
// Sets the dimensions (width and height) of a specific panel by its ID
|
|
211
187
|
const setPanelDimensionsById = useCallback((id, width, height) => {
|
|
212
188
|
// Update the ref holding the initial dimensions
|
|
213
189
|
defaultPanelDimensionsRef.current = { ...defaultPanelDimensionsRef.current, [id]: { width, height } };
|
|
214
190
|
// Update the panel dimensions state
|
|
215
|
-
|
|
191
|
+
setPanelLayout(prev => ({ ...prev, dimensions: { ...prev.dimensions, [id]: { width, height } } }));
|
|
216
192
|
}, []);
|
|
217
193
|
// Checks if there is at least one panel currently visible
|
|
218
194
|
const hasVisiblePanels = useCallback(() => {
|
|
@@ -97,18 +97,20 @@ export const redistributeDimensionsOnHide = (hiddenPanelId, currentDimensions, h
|
|
|
97
97
|
const hiddenWidthNum = parseFloat(hiddenDims.width);
|
|
98
98
|
const hiddenHeightNum = parseFloat(hiddenDims.height);
|
|
99
99
|
const visibleSiblings = siblings.filter(siblingId => panelVisibility[siblingId]);
|
|
100
|
-
const widthIncrement = hiddenWidthNum / visibleSiblings.length;
|
|
101
|
-
const heightIncrement = hiddenHeightNum / visibleSiblings.length;
|
|
102
100
|
let newDimensions = { ...currentDimensions };
|
|
103
|
-
visibleSiblings.
|
|
104
|
-
const
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
101
|
+
if (visibleSiblings.length > 0) {
|
|
102
|
+
const widthIncrement = hiddenWidthNum / visibleSiblings.length;
|
|
103
|
+
const heightIncrement = hiddenHeightNum / visibleSiblings.length;
|
|
104
|
+
visibleSiblings.forEach(siblingId => {
|
|
105
|
+
const siblingDims = newDimensions[siblingId] || { width: '0%', height: '0%' };
|
|
106
|
+
const siblingWidthNum = parseFloat(siblingDims.width) || 0;
|
|
107
|
+
const siblingHeightNum = parseFloat(siblingDims.height) || 0;
|
|
108
|
+
const newWidthNum = Math.min(siblingWidthNum + widthIncrement, 100);
|
|
109
|
+
const newHeightNum = Math.min(siblingHeightNum + heightIncrement, 100);
|
|
110
|
+
newDimensions[siblingId] = { width: `${newWidthNum}%`, height: `${newHeightNum}%` };
|
|
111
|
+
});
|
|
112
|
+
newDimensions[hiddenPanelId] = { width: '0%', height: '0%' };
|
|
113
|
+
}
|
|
112
114
|
// Ora controllo se il parent non ha più figli visibili e chiamo ricorsivamente
|
|
113
115
|
if (parentId) {
|
|
114
116
|
const parentInfo = hierarchyMap.get(parentId);
|
|
@@ -98,11 +98,33 @@ export declare class SearchSettings {
|
|
|
98
98
|
};
|
|
99
99
|
relationExpandLevel: number;
|
|
100
100
|
relationShowZeroDcmts: boolean;
|
|
101
|
+
/** Layout dei pannelli del risultato di ricerca, condiviso dai contesti che lo gestiscono */
|
|
102
|
+
resultPanelLayout: SearchResultPanelStates;
|
|
101
103
|
/** Ricerche rapide configurate per i valori distinti: una per ogni campo, con chiave "tid_mid" */
|
|
102
104
|
distinctValuesQuickSearches: {
|
|
103
105
|
[fieldKey: string]: DistinctValuesQuickSearchSettings;
|
|
104
106
|
};
|
|
105
107
|
}
|
|
108
|
+
/** Visibilità e dimensioni dei pannelli, per id pannello */
|
|
109
|
+
export type SearchResultPanelStates = Record<string, {
|
|
110
|
+
visible: boolean;
|
|
111
|
+
width: string;
|
|
112
|
+
height: string;
|
|
113
|
+
}>;
|
|
114
|
+
/** Avvisa i risultati di ricerca già montati quando un altro salva il layout; ritorna la disiscrizione */
|
|
115
|
+
export declare const subscribeSearchResultPanelLayout: (onLayoutChanged: () => void) => (() => void);
|
|
116
|
+
/** Layout salvato; vuoto se non ne è stato salvato nessuno (anche per le impostazioni di versioni precedenti) */
|
|
117
|
+
export declare const getSearchResultPanelLayout: () => SearchResultPanelStates;
|
|
118
|
+
/** Il panel manager applica il layout salvato solo se esiste, altrimenti riparte dalle quote di default */
|
|
119
|
+
export declare const hasSearchResultPanelLayout: () => boolean;
|
|
120
|
+
/** Firma del layout salvato: un primitivo confrontabile, come richiede useSyncExternalStore */
|
|
121
|
+
export declare const getSearchResultPanelLayoutSignature: () => string;
|
|
122
|
+
/**
|
|
123
|
+
* @param disabledPanelIds Pannelli che il risultato chiamante non può mostrare: il loro stato resta quello
|
|
124
|
+
* già salvato, perché sono chiusi per via del tipo documento e non per scelta
|
|
125
|
+
* dell'utente. Sovrascriverlo li chiuderebbe anche dove sono attivi.
|
|
126
|
+
*/
|
|
127
|
+
export declare const saveSearchResultPanelLayout: (panelStates: SearchResultPanelStates, disabledPanelIds?: Array<string>) => void;
|
|
106
128
|
/** Ricerca rapida configurata per il campo, se presente */
|
|
107
129
|
export declare const getDistinctValuesQuickSearch: (tid: number | undefined, mid: number | undefined) => DistinctValuesQuickSearchSettings | undefined;
|
|
108
130
|
export declare const saveDistinctValuesQuickSearch: (tid: number | undefined, mid: number | undefined, settings: DistinctValuesQuickSearchSettings) => void;
|
|
@@ -158,10 +158,48 @@ export class SearchSettings {
|
|
|
158
158
|
this.panelLayout = {};
|
|
159
159
|
this.relationExpandLevel = 4; // Livello di espansione predefinito per le correlazioni
|
|
160
160
|
this.relationShowZeroDcmts = false;
|
|
161
|
+
/** Layout dei pannelli del risultato di ricerca, condiviso dai contesti che lo gestiscono */
|
|
162
|
+
this.resultPanelLayout = {};
|
|
161
163
|
/** Ricerche rapide configurate per i valori distinti: una per ogni campo, con chiave "tid_mid" */
|
|
162
164
|
this.distinctValuesQuickSearches = {};
|
|
163
165
|
}
|
|
164
166
|
}
|
|
167
|
+
const resultPanelLayoutSubscribers = new Set();
|
|
168
|
+
/** Avvisa i risultati di ricerca già montati quando un altro salva il layout; ritorna la disiscrizione */
|
|
169
|
+
export const subscribeSearchResultPanelLayout = (onLayoutChanged) => {
|
|
170
|
+
resultPanelLayoutSubscribers.add(onLayoutChanged);
|
|
171
|
+
return () => { resultPanelLayoutSubscribers.delete(onLayoutChanged); };
|
|
172
|
+
};
|
|
173
|
+
/** Layout salvato; vuoto se non ne è stato salvato nessuno (anche per le impostazioni di versioni precedenti) */
|
|
174
|
+
export const getSearchResultPanelLayout = () => SDKUI_Globals.userSettings.searchSettings?.resultPanelLayout ?? {};
|
|
175
|
+
/** Il panel manager applica il layout salvato solo se esiste, altrimenti riparte dalle quote di default */
|
|
176
|
+
export const hasSearchResultPanelLayout = () => Object.keys(getSearchResultPanelLayout()).length > 0;
|
|
177
|
+
/** Riassume un layout in una stringa: ordinata per id, così due layout uguali danno la stessa firma */
|
|
178
|
+
const searchResultPanelLayoutSignature = (panelStates) => Object.entries(panelStates)
|
|
179
|
+
.sort(([firstId], [secondId]) => firstId.localeCompare(secondId))
|
|
180
|
+
.map(([id, state]) => `${id}:${state?.visible ? 1 : 0}:${state?.width ?? ''}:${state?.height ?? ''}`)
|
|
181
|
+
.join('|');
|
|
182
|
+
/** Firma del layout salvato: un primitivo confrontabile, come richiede useSyncExternalStore */
|
|
183
|
+
export const getSearchResultPanelLayoutSignature = () => searchResultPanelLayoutSignature(getSearchResultPanelLayout());
|
|
184
|
+
/**
|
|
185
|
+
* @param disabledPanelIds Pannelli che il risultato chiamante non può mostrare: il loro stato resta quello
|
|
186
|
+
* già salvato, perché sono chiusi per via del tipo documento e non per scelta
|
|
187
|
+
* dell'utente. Sovrascriverlo li chiuderebbe anche dove sono attivi.
|
|
188
|
+
*/
|
|
189
|
+
export const saveSearchResultPanelLayout = (panelStates, disabledPanelIds = []) => {
|
|
190
|
+
if (!panelStates || Object.keys(panelStates).length === 0)
|
|
191
|
+
return;
|
|
192
|
+
const savedLayout = getSearchResultPanelLayout();
|
|
193
|
+
const layout = { ...panelStates };
|
|
194
|
+
disabledPanelIds.forEach(id => { if (savedLayout[id])
|
|
195
|
+
layout[id] = { ...savedLayout[id] }; });
|
|
196
|
+
// Layout identico: senza questo controllo ogni scrittura avviserebbe a vuoto gli altri risultati aperti
|
|
197
|
+
if (searchResultPanelLayoutSignature(layout) === getSearchResultPanelLayoutSignature())
|
|
198
|
+
return;
|
|
199
|
+
// Il proxy persiste solo intercettando l'assegnazione di una proprietà: va riassegnata l'intera mappa
|
|
200
|
+
SDKUI_Globals.userSettings.searchSettings.resultPanelLayout = layout;
|
|
201
|
+
resultPanelLayoutSubscribers.forEach(onLayoutChanged => onLayoutChanged());
|
|
202
|
+
};
|
|
165
203
|
/** Il campo da cui viene aperto il pannello dei valori distinti è identificato dalla coppia (tid, mid) */
|
|
166
204
|
const distinctValuesFieldKey = (tid, mid) => `${tid}_${mid}`;
|
|
167
205
|
/** Ricerca rapida configurata per il campo, se presente */
|