8978 add navigation inside the command menu for showpage (#9103)

Closes #8978

- Added new options in the actions config files: `shortLabel`,
`availableOn`
- Added two actions: Navigate to previous records and Navigate to next
records
- Modified `useRecordShowPagePagination` to loop on records when we are
on first record and we hit previous or when we are on last record and we
hit next
- Introduced a new component state
`contextStoreCurrentViewTypeComponentState`
This commit is contained in:
Raphaël Bosi
2024-12-17 17:48:12 +01:00
committed by GitHub
parent bb8c763f9c
commit b033a50d7c
22 changed files with 529 additions and 147 deletions

View File

@ -23,7 +23,9 @@ import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTabl
import { SpreadsheetImportProvider } from '@/spreadsheet-import/provider/components/SpreadsheetImportProvider';
import { RecordIndexActionMenu } from '@/action-menu/components/RecordIndexActionMenu';
import { ContextStoreCurrentViewTypeEffect } from '@/context-store/components/ContextStoreCurrentViewTypeEffect';
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType';
import { useSetRecordGroup } from '@/object-record/record-group/hooks/useSetRecordGroup';
import { RecordIndexFiltersToContextStoreEffect } from '@/object-record/record-index/components/RecordIndexFiltersToContextStoreEffect';
import { recordIndexKanbanAggregateOperationState } from '@/object-record/record-index/states/recordIndexKanbanAggregateOperationState';
@ -164,89 +166,98 @@ export const RecordIndexContainer = () => {
);
return (
<StyledContainer>
<InformationBannerWrapper />
<RecordFieldValueSelectorContextProvider>
<SpreadsheetImportProvider>
<ViewBar
viewBarId={recordIndexId}
optionsDropdownButton={
<ObjectOptionsDropdown
recordIndexId={recordIndexId}
objectMetadataItem={objectMetadataItem}
viewType={recordIndexViewType ?? ViewType.Table}
/>
}
onCurrentViewChange={(view) => {
if (!view) {
return;
<>
<ContextStoreCurrentViewTypeEffect
viewType={
recordIndexViewType === ViewType.Table
? ContextStoreViewType.Table
: ContextStoreViewType.Kanban
}
/>
<StyledContainer>
<InformationBannerWrapper />
<RecordFieldValueSelectorContextProvider>
<SpreadsheetImportProvider>
<ViewBar
viewBarId={recordIndexId}
optionsDropdownButton={
<ObjectOptionsDropdown
recordIndexId={recordIndexId}
objectMetadataItem={objectMetadataItem}
viewType={recordIndexViewType ?? ViewType.Table}
/>
}
onCurrentViewChange={(view) => {
if (!view) {
return;
}
onViewFieldsChange(view.viewFields);
onViewGroupsChange(view.viewGroups);
setTableViewFilterGroups(view.viewFilterGroups ?? []);
setTableFilters(
mapViewFiltersToFilters(view.viewFilters, filterDefinitions),
);
setRecordIndexFilters(
mapViewFiltersToFilters(view.viewFilters, filterDefinitions),
);
setRecordIndexViewFilterGroups(view.viewFilterGroups ?? []);
setContextStoreTargetedRecordsRule((prev) => ({
...prev,
filters: mapViewFiltersToFilters(
view.viewFilters,
filterDefinitions,
),
}));
setTableSorts(
mapViewSortsToSorts(view.viewSorts, sortDefinitions),
);
setRecordIndexSorts(
mapViewSortsToSorts(view.viewSorts, sortDefinitions),
);
setRecordIndexViewType(view.type);
setRecordIndexViewKanbanFieldMetadataIdState(
view.kanbanFieldMetadataId,
);
setRecordIndexViewKanbanAggregateOperationState({
operation: view.kanbanAggregateOperation,
fieldMetadataId: view.kanbanAggregateOperationFieldMetadataId,
});
setRecordIndexIsCompactModeActive(view.isCompact);
}}
/>
<RecordIndexViewBarEffect
objectNamePlural={objectNamePlural}
viewBarId={recordIndexId}
/>
</SpreadsheetImportProvider>
<RecordIndexFiltersToContextStoreEffect />
{recordIndexViewType === ViewType.Table && (
<>
<RecordIndexTableContainer
recordTableId={recordIndexId}
onViewFieldsChange(view.viewFields);
onViewGroupsChange(view.viewGroups);
setTableViewFilterGroups(view.viewFilterGroups ?? []);
setTableFilters(
mapViewFiltersToFilters(view.viewFilters, filterDefinitions),
);
setRecordIndexFilters(
mapViewFiltersToFilters(view.viewFilters, filterDefinitions),
);
setRecordIndexViewFilterGroups(view.viewFilterGroups ?? []);
setContextStoreTargetedRecordsRule((prev) => ({
...prev,
filters: mapViewFiltersToFilters(
view.viewFilters,
filterDefinitions,
),
}));
setTableSorts(
mapViewSortsToSorts(view.viewSorts, sortDefinitions),
);
setRecordIndexSorts(
mapViewSortsToSorts(view.viewSorts, sortDefinitions),
);
setRecordIndexViewType(view.type);
setRecordIndexViewKanbanFieldMetadataIdState(
view.kanbanFieldMetadataId,
);
setRecordIndexViewKanbanAggregateOperationState({
operation: view.kanbanAggregateOperation,
fieldMetadataId: view.kanbanAggregateOperationFieldMetadataId,
});
setRecordIndexIsCompactModeActive(view.isCompact);
}}
/>
<RecordIndexViewBarEffect
objectNamePlural={objectNamePlural}
viewBarId={recordIndexId}
/>
<RecordIndexTableContainerEffect />
</>
)}
{recordIndexViewType === ViewType.Kanban && (
<StyledContainerWithPadding>
<RecordIndexBoardContainer
recordBoardId={recordIndexId}
viewBarId={recordIndexId}
objectNameSingular={objectNameSingular}
/>
<RecordIndexBoardDataLoader
objectNameSingular={objectNameSingular}
recordBoardId={recordIndexId}
/>
<RecordIndexBoardDataLoaderEffect recordBoardId={recordIndexId} />
</StyledContainerWithPadding>
)}
{!isPageHeaderV2Enabled && <RecordIndexActionMenu />}
</RecordFieldValueSelectorContextProvider>
</StyledContainer>
</SpreadsheetImportProvider>
<RecordIndexFiltersToContextStoreEffect />
{recordIndexViewType === ViewType.Table && (
<>
<RecordIndexTableContainer
recordTableId={recordIndexId}
viewBarId={recordIndexId}
/>
<RecordIndexTableContainerEffect />
</>
)}
{recordIndexViewType === ViewType.Kanban && (
<StyledContainerWithPadding>
<RecordIndexBoardContainer
recordBoardId={recordIndexId}
viewBarId={recordIndexId}
objectNameSingular={objectNameSingular}
/>
<RecordIndexBoardDataLoader
objectNameSingular={objectNameSingular}
recordBoardId={recordIndexId}
/>
<RecordIndexBoardDataLoaderEffect recordBoardId={recordIndexId} />
</StyledContainerWithPadding>
)}
{!isPageHeaderV2Enabled && <RecordIndexActionMenu />}
</RecordFieldValueSelectorContextProvider>
</StyledContainer>
</>
);
};

View File

@ -1,4 +1,3 @@
/* eslint-disable @nx/workspace-no-navigate-prefer-link */
import { isNonEmptyString } from '@sniptt/guards';
import { useState } from 'react';
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
@ -11,6 +10,7 @@ import { useRecordIdsFromFindManyCacheRootQuery } from '@/object-record/record-s
import { buildShowPageURL } from '@/object-record/record-show/utils/buildShowPageURL';
import { buildIndexTablePageURL } from '@/object-record/record-table/utils/buildIndexTableURL';
import { useQueryVariablesFromActiveFieldsOfViewOrDefaultView } from '@/views/hooks/useQueryVariablesFromActiveFieldsOfViewOrDefaultView';
import { isDefined } from 'twenty-ui';
import { capitalize } from '~/utils/string/capitalize';
export const useRecordShowPagePagination = (
@ -100,22 +100,43 @@ export const useRecordShowPagePagination = (
const loading = loadingRecordAfter || loadingRecordBefore || loadingCursor;
const isThereARecordBefore = recordsBefore.length > 0;
const isThereARecordAfter = recordsAfter.length > 0;
const recordBefore = recordsBefore[0];
const recordAfter = recordsAfter[0];
const { recordIdsInCache } = useRecordIdsFromFindManyCacheRootQuery({
objectNamePlural: objectMetadataItem.namePlural,
fieldVariables: {
filter,
orderBy,
},
});
const navigateToPreviousRecord = () => {
navigate(
buildShowPageURL(objectNameSingular, recordBefore.id, viewIdQueryParam),
);
if (isDefined(recordBefore)) {
navigate(
buildShowPageURL(objectNameSingular, recordBefore.id, viewIdQueryParam),
);
}
if (!loadingRecordBefore && !isDefined(recordBefore)) {
const firstRecordId = recordIdsInCache[recordIdsInCache.length - 1];
navigate(
buildShowPageURL(objectNameSingular, firstRecordId, viewIdQueryParam),
);
}
};
const navigateToNextRecord = () => {
navigate(
buildShowPageURL(objectNameSingular, recordAfter.id, viewIdQueryParam),
);
if (isDefined(recordAfter)) {
navigate(
buildShowPageURL(objectNameSingular, recordAfter.id, viewIdQueryParam),
);
}
if (!loadingRecordAfter && !isDefined(recordAfter)) {
const lastRecordId = recordIdsInCache[0];
navigate(
buildShowPageURL(objectNameSingular, lastRecordId, viewIdQueryParam),
);
}
};
const navigateToIndexView = () => {
@ -129,31 +150,21 @@ export const useRecordShowPagePagination = (
navigate(indexTableURL);
};
const { recordIdsInCache } = useRecordIdsFromFindManyCacheRootQuery({
objectNamePlural: objectMetadataItem.namePlural,
fieldVariables: {
filter,
orderBy,
},
});
const rankInView = recordIdsInCache.findIndex((id) => id === objectRecordId);
const rankFoundInFiew = rankInView > -1;
const rankFoundInView = rankInView > -1;
const objectLabel = capitalize(objectMetadataItem.labelPlural);
const totalCount = Math.max(1, totalCountBefore, totalCountAfter);
const viewNameWithCount = rankFoundInFiew
const viewNameWithCount = rankFoundInView
? `${rankInView + 1} of ${totalCount} in ${objectLabel}`
: `${objectLabel} (${totalCount})`;
return {
viewName: viewNameWithCount,
hasPreviousRecord: isThereARecordBefore,
isLoadingPagination: loading,
hasNextRecord: isThereARecordAfter,
navigateToPreviousRecord,
navigateToNextRecord,
navigateToIndexView,