fix: view group followup (#9162)
This PR fixes all followup that @Bonapara add on Discord. - [x] When no group by is set, clicking on group by should open the "field selection" menu - [x] When closed, chevron should be "chevron-right" instead of "chevron-up" - [x] Sort : Add ability to switch from alphabetical to manual when moving a option in sort alphabetical - [x] Add subtext for group by and sort - [x] Group by menu display bug - [x] Changing the sort should not close the menu - [x] Group by Activation -> shows empty state + is slow - [x] Switching from Kanban view Settings to Table Options menu displays an empty menu - [x] Unnecessary spacing under groups - [x] When no "select" are set on an object, redirect the user directly to the new Select field page - [x] Sort : Default should be manual - [x] Hidding "no value" displays all options and remove the "hide empty group" toggle - [x] Hide Empty group option disappeared - [x] Group by should not be persisted on "Locked/Main view" (**For now we just disable the group by on main view**) - [x] Hide Empty group should not be activated by default on Opportunities Kanban view - [ ] Animate the group opening/closing (**We'll be done later**) Performance improvement: https://github.com/user-attachments/assets/fd2acf66-0e56-45d0-8b2f-99c62e57d6f7 https://github.com/user-attachments/assets/80f1a2e1-9f77-4923-b85d-acb9cad96886 Also fix #9036 --------- Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
@ -330,7 +330,6 @@ export enum FeatureFlagKey {
|
||||
IsSsoEnabled = 'IsSSOEnabled',
|
||||
IsStripeIntegrationEnabled = 'IsStripeIntegrationEnabled',
|
||||
IsUniqueIndexesEnabled = 'IsUniqueIndexesEnabled',
|
||||
IsViewGroupsEnabled = 'IsViewGroupsEnabled',
|
||||
IsWorkflowEnabled = 'IsWorkflowEnabled'
|
||||
}
|
||||
|
||||
|
||||
@ -32,7 +32,7 @@ export const triggerAttachRelationOptimisticEffect = ({
|
||||
id: targetRecordCacheId,
|
||||
fields: {
|
||||
[fieldNameOnTargetRecord]: (targetRecordFieldValue, { toReference }) => {
|
||||
const fieldValueisObjectRecordConnectionWithRefs =
|
||||
const fieldValueIsObjectRecordConnectionWithRefs =
|
||||
isObjectRecordConnectionWithRefs(
|
||||
sourceObjectNameSingular,
|
||||
targetRecordFieldValue,
|
||||
@ -47,7 +47,7 @@ export const triggerAttachRelationOptimisticEffect = ({
|
||||
return targetRecordFieldValue;
|
||||
}
|
||||
|
||||
if (fieldValueisObjectRecordConnectionWithRefs) {
|
||||
if (fieldValueIsObjectRecordConnectionWithRefs) {
|
||||
const nextEdges: RecordGqlRefEdge[] = [
|
||||
...targetRecordFieldValue.edges,
|
||||
{
|
||||
|
||||
@ -34,6 +34,7 @@ export const ObjectOptionsDropdown = ({
|
||||
clickableComponent={
|
||||
<StyledHeaderDropdownButton>Options</StyledHeaderDropdownButton>
|
||||
}
|
||||
onClose={handleResetContent}
|
||||
dropdownComponents={
|
||||
<ObjectOptionsDropdownContext.Provider
|
||||
value={{
|
||||
|
||||
@ -25,6 +25,7 @@ import { useSetRecoilState } from 'recoil';
|
||||
|
||||
export const ObjectOptionsDropdownHiddenRecordGroupsContent = () => {
|
||||
const {
|
||||
viewType,
|
||||
currentContentId,
|
||||
recordIndexId,
|
||||
objectMetadataItem,
|
||||
@ -47,6 +48,7 @@ export const ObjectOptionsDropdownHiddenRecordGroupsContent = () => {
|
||||
const { handleVisibilityChange: handleRecordGroupVisibilityChange } =
|
||||
useRecordGroupVisibility({
|
||||
viewBarId: recordIndexId,
|
||||
viewType,
|
||||
});
|
||||
|
||||
const viewGroupSettingsUrl = getSettingsPagePath(
|
||||
|
||||
@ -30,8 +30,7 @@ import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
|
||||
import { ViewType } from '@/views/types/ViewType';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { FeatureFlagKey } from '~/generated/graphql';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
export const ObjectOptionsDropdownMenuContent = () => {
|
||||
const {
|
||||
@ -42,10 +41,6 @@ export const ObjectOptionsDropdownMenuContent = () => {
|
||||
closeDropdown,
|
||||
} = useOptionsDropdown();
|
||||
|
||||
const isViewGroupEnabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsViewGroupsEnabled,
|
||||
);
|
||||
|
||||
const { getIcon } = useIcons();
|
||||
const { currentViewWithCombinedFiltersAndSorts: currentView } =
|
||||
useGetCurrentView();
|
||||
@ -120,9 +115,13 @@ export const ObjectOptionsDropdownMenuContent = () => {
|
||||
contextualText={`${visibleBoardFields.length} shown`}
|
||||
hasSubMenu
|
||||
/>
|
||||
{(viewType === ViewType.Kanban || isViewGroupEnabled) && (
|
||||
{viewType === ViewType.Kanban && currentView?.key !== 'INDEX' && (
|
||||
<MenuItem
|
||||
onClick={() => onContentChange('recordGroups')}
|
||||
onClick={() =>
|
||||
isDefined(recordGroupFieldMetadata)
|
||||
? onContentChange('recordGroups')
|
||||
: onContentChange('recordGroupFields')
|
||||
}
|
||||
LeftIcon={IconLayoutList}
|
||||
text="Group by"
|
||||
contextualText={recordGroupFieldMetadata?.label}
|
||||
|
||||
@ -26,6 +26,7 @@ import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMe
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
export const ObjectOptionsDropdownRecordGroupFieldsContent = () => {
|
||||
@ -36,6 +37,7 @@ export const ObjectOptionsDropdownRecordGroupFieldsContent = () => {
|
||||
recordIndexId,
|
||||
objectMetadataItem,
|
||||
onContentChange,
|
||||
resetContent,
|
||||
closeDropdown,
|
||||
} = useOptionsDropdown();
|
||||
|
||||
@ -47,7 +49,7 @@ export const ObjectOptionsDropdownRecordGroupFieldsContent = () => {
|
||||
hiddenRecordGroupIdsComponentSelector,
|
||||
);
|
||||
|
||||
const recordGroupFieldMetadataItem = useRecoilComponentValueV2(
|
||||
const recordGroupFieldMetadata = useRecoilComponentValueV2(
|
||||
recordGroupFieldMetadataComponentState,
|
||||
);
|
||||
|
||||
@ -64,11 +66,14 @@ export const ObjectOptionsDropdownRecordGroupFieldsContent = () => {
|
||||
viewBarComponentId: recordIndexId,
|
||||
});
|
||||
|
||||
const newFieldSettingsUrl = getSettingsPagePath(
|
||||
SettingsPath.ObjectNewFieldSelect,
|
||||
const newSelectFieldSettingsUrl = getSettingsPagePath(
|
||||
SettingsPath.ObjectNewFieldConfigure,
|
||||
{
|
||||
objectSlug: objectNamePlural,
|
||||
},
|
||||
{
|
||||
fieldType: FieldMetadataType.Select,
|
||||
},
|
||||
);
|
||||
|
||||
const location = useLocation();
|
||||
@ -101,7 +106,11 @@ export const ObjectOptionsDropdownRecordGroupFieldsContent = () => {
|
||||
<>
|
||||
<DropdownMenuHeader
|
||||
StartIcon={IconChevronLeft}
|
||||
onClick={() => onContentChange('recordGroups')}
|
||||
onClick={() =>
|
||||
isDefined(recordGroupFieldMetadata)
|
||||
? onContentChange('recordGroups')
|
||||
: resetContent()
|
||||
}
|
||||
>
|
||||
Group by
|
||||
</DropdownMenuHeader>
|
||||
@ -114,13 +123,13 @@ export const ObjectOptionsDropdownRecordGroupFieldsContent = () => {
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItemSelect
|
||||
text="None"
|
||||
selected={!isDefined(recordGroupFieldMetadataItem)}
|
||||
selected={!isDefined(recordGroupFieldMetadata)}
|
||||
onClick={handleResetRecordGroupField}
|
||||
/>
|
||||
{filteredRecordGroupFieldMetadataItems.map((fieldMetadataItem) => (
|
||||
<MenuItemSelect
|
||||
key={fieldMetadataItem.id}
|
||||
selected={fieldMetadataItem.id === recordGroupFieldMetadataItem?.id}
|
||||
selected={fieldMetadataItem.id === recordGroupFieldMetadata?.id}
|
||||
onClick={() => handleRecordGroupFieldChange(fieldMetadataItem)}
|
||||
LeftIcon={getIcon(fieldMetadataItem.icon)}
|
||||
text={fieldMetadataItem.label}
|
||||
@ -130,7 +139,7 @@ export const ObjectOptionsDropdownRecordGroupFieldsContent = () => {
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItemsContainer>
|
||||
<UndecoratedLink
|
||||
to={newFieldSettingsUrl}
|
||||
to={newSelectFieldSettingsUrl}
|
||||
onClick={() => {
|
||||
setNavigationMemorizedUrl(location.pathname + location.search);
|
||||
closeDropdown();
|
||||
|
||||
@ -17,8 +17,7 @@ import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
|
||||
export const ObjectOptionsDropdownRecordGroupSortContent = () => {
|
||||
const { currentContentId, onContentChange, closeDropdown } =
|
||||
useOptionsDropdown();
|
||||
const { currentContentId, onContentChange } = useOptionsDropdown();
|
||||
|
||||
const hiddenRecordGroupIds = useRecoilComponentValueV2(
|
||||
hiddenRecordGroupIdsComponentSelector,
|
||||
@ -30,7 +29,6 @@ export const ObjectOptionsDropdownRecordGroupSortContent = () => {
|
||||
|
||||
const handleRecordGroupSortChange = (sort: RecordGroupSort) => {
|
||||
setRecordGroupSort(sort);
|
||||
closeDropdown();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@ -11,47 +11,54 @@ import {
|
||||
} from 'twenty-ui';
|
||||
|
||||
import { useOptionsDropdown } from '@/object-record/object-options-dropdown/hooks/useOptionsDropdown';
|
||||
import { RecordGroupReorderConfirmationModal } from '@/object-record/record-group/components/RecordGroupReorderConfirmationModal';
|
||||
import { RecordGroupsVisibilityDropdownSection } from '@/object-record/record-group/components/RecordGroupsVisibilityDropdownSection';
|
||||
import { useRecordGroupReorder } from '@/object-record/record-group/hooks/useRecordGroupReorder';
|
||||
import { useRecordGroupReorderConfirmationModal } from '@/object-record/record-group/hooks/useRecordGroupReorderConfirmationModal';
|
||||
import { useRecordGroupVisibility } from '@/object-record/record-group/hooks/useRecordGroupVisibility';
|
||||
import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState';
|
||||
import { hiddenRecordGroupIdsComponentSelector } from '@/object-record/record-group/states/selectors/hiddenRecordGroupIdsComponentSelector';
|
||||
import { visibleRecordGroupIdsComponentSelector } from '@/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentSelector';
|
||||
import { recordIndexRecordGroupHideComponentState } from '@/object-record/record-index/states/recordIndexRecordGroupHideComponentState';
|
||||
import { recordIndexRecordGroupIsDraggableSortComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexRecordGroupIsDraggableSortComponentSelector';
|
||||
import { visibleRecordGroupIdsComponentFamilySelector } from '@/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentFamilySelector';
|
||||
import { recordIndexRecordGroupHideComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordGroupHideComponentFamilyState';
|
||||
import { recordIndexRecordGroupSortComponentState } from '@/object-record/record-index/states/recordIndexRecordGroupSortComponentState';
|
||||
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { FeatureFlagKey } from '~/generated/graphql';
|
||||
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
|
||||
|
||||
export const ObjectOptionsDropdownRecordGroupsContent = () => {
|
||||
const isViewGroupEnabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsViewGroupsEnabled,
|
||||
);
|
||||
const {
|
||||
viewType,
|
||||
currentContentId,
|
||||
recordIndexId,
|
||||
onContentChange,
|
||||
resetContent,
|
||||
} = useOptionsDropdown();
|
||||
|
||||
const { currentContentId, recordIndexId, onContentChange, resetContent } =
|
||||
useOptionsDropdown();
|
||||
const { currentViewWithCombinedFiltersAndSorts: currentView } =
|
||||
useGetCurrentView();
|
||||
|
||||
const recordGroupFieldMetadata = useRecoilComponentValueV2(
|
||||
recordGroupFieldMetadataComponentState,
|
||||
);
|
||||
|
||||
const visibleRecordGroupIds = useRecoilComponentValueV2(
|
||||
visibleRecordGroupIdsComponentSelector,
|
||||
const visibleRecordGroupIds = useRecoilComponentFamilyValueV2(
|
||||
visibleRecordGroupIdsComponentFamilySelector,
|
||||
viewType,
|
||||
);
|
||||
|
||||
const hiddenRecordGroupIds = useRecoilComponentValueV2(
|
||||
hiddenRecordGroupIdsComponentSelector,
|
||||
);
|
||||
|
||||
const isDragableSortRecordGroup = useRecoilComponentValueV2(
|
||||
recordIndexRecordGroupIsDraggableSortComponentSelector,
|
||||
const hideEmptyRecordGroup = useRecoilComponentFamilyValueV2(
|
||||
recordIndexRecordGroupHideComponentFamilyState,
|
||||
viewType,
|
||||
);
|
||||
|
||||
const hideEmptyRecordGroup = useRecoilComponentValueV2(
|
||||
recordIndexRecordGroupHideComponentState,
|
||||
const recordGroupSort = useRecoilComponentValueV2(
|
||||
recordIndexRecordGroupSortComponentState,
|
||||
);
|
||||
|
||||
const {
|
||||
@ -59,12 +66,16 @@ export const ObjectOptionsDropdownRecordGroupsContent = () => {
|
||||
handleHideEmptyRecordGroupChange,
|
||||
} = useRecordGroupVisibility({
|
||||
viewBarId: recordIndexId,
|
||||
viewType,
|
||||
});
|
||||
|
||||
const { handleOrderChange: handleRecordGroupOrderChange } =
|
||||
useRecordGroupReorder({
|
||||
viewBarId: recordIndexId,
|
||||
});
|
||||
const {
|
||||
handleRecordGroupOrderChangeWithModal,
|
||||
handleRecordGroupReorderConfirmClick,
|
||||
} = useRecordGroupReorderConfirmationModal({
|
||||
recordIndexId,
|
||||
viewType,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
@ -81,22 +92,20 @@ export const ObjectOptionsDropdownRecordGroupsContent = () => {
|
||||
Group by
|
||||
</DropdownMenuHeader>
|
||||
<DropdownMenuItemsContainer>
|
||||
{isViewGroupEnabled && (
|
||||
{currentView?.key !== 'INDEX' && (
|
||||
<>
|
||||
<MenuItem
|
||||
onClick={() => onContentChange('recordGroupFields')}
|
||||
LeftIcon={IconLayoutList}
|
||||
text={
|
||||
!recordGroupFieldMetadata
|
||||
? 'Group by'
|
||||
: `Group by "${recordGroupFieldMetadata.label}"`
|
||||
}
|
||||
text="Group by"
|
||||
contextualText={recordGroupFieldMetadata?.label}
|
||||
hasSubMenu
|
||||
/>
|
||||
<MenuItem
|
||||
onClick={() => onContentChange('recordGroupSort')}
|
||||
LeftIcon={IconSortDescending}
|
||||
text="Sort"
|
||||
contextualText={recordGroupSort}
|
||||
hasSubMenu
|
||||
/>
|
||||
</>
|
||||
@ -115,9 +124,9 @@ export const ObjectOptionsDropdownRecordGroupsContent = () => {
|
||||
<RecordGroupsVisibilityDropdownSection
|
||||
title="Visible groups"
|
||||
recordGroupIds={visibleRecordGroupIds}
|
||||
onDragEnd={handleRecordGroupOrderChange}
|
||||
onDragEnd={handleRecordGroupOrderChangeWithModal}
|
||||
onVisibilityChange={handleRecordGroupVisibilityChange}
|
||||
isDraggable={isDragableSortRecordGroup}
|
||||
isDraggable={true}
|
||||
showDragGrip={true}
|
||||
/>
|
||||
</>
|
||||
@ -134,6 +143,9 @@ export const ObjectOptionsDropdownRecordGroupsContent = () => {
|
||||
</DropdownMenuItemsContainer>
|
||||
</>
|
||||
)}
|
||||
<RecordGroupReorderConfirmationModal
|
||||
onConfirmClick={handleRecordGroupReorderConfirmClick}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -15,7 +15,7 @@ import { RecordBoardScope } from '@/object-record/record-board/scopes/RecordBoar
|
||||
import { RecordBoardComponentInstanceContext } from '@/object-record/record-board/states/contexts/RecordBoardComponentInstanceContext';
|
||||
import { getDraggedRecordPosition } from '@/object-record/record-board/utils/getDraggedRecordPosition';
|
||||
import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState';
|
||||
import { visibleRecordGroupIdsComponentSelector } from '@/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentSelector';
|
||||
import { visibleRecordGroupIdsComponentFamilySelector } from '@/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentFamilySelector';
|
||||
import { recordIndexRecordIdsByGroupComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordIdsByGroupComponentFamilyState';
|
||||
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
@ -26,8 +26,9 @@ import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useLis
|
||||
import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId';
|
||||
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
||||
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
|
||||
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
|
||||
import { ViewType } from '@/views/types/ViewType';
|
||||
import { useScrollRestoration } from '~/hooks/useScrollRestoration';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
@ -64,8 +65,9 @@ export const RecordBoard = () => {
|
||||
useContext(RecordBoardContext);
|
||||
const boardRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const visibleRecordGroupIds = useRecoilComponentValueV2(
|
||||
visibleRecordGroupIdsComponentSelector,
|
||||
const visibleRecordGroupIds = useRecoilComponentFamilyValueV2(
|
||||
visibleRecordGroupIdsComponentFamilySelector,
|
||||
ViewType.Kanban,
|
||||
);
|
||||
|
||||
const recordIndexRecordIdsByGroupFamilyState =
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { RecordBoardColumnHeaderWrapper } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderWrapper';
|
||||
import { visibleRecordGroupIdsComponentSelector } from '@/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentSelector';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { visibleRecordGroupIdsComponentFamilySelector } from '@/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentFamilySelector';
|
||||
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
|
||||
import { ViewType } from '@/views/types/ViewType';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
const StyledHeaderContainer = styled.div`
|
||||
@ -23,8 +24,9 @@ const StyledHeaderContainer = styled.div`
|
||||
`;
|
||||
|
||||
export const RecordBoardHeader = () => {
|
||||
const visibleRecordGroupIds = useRecoilComponentValueV2(
|
||||
visibleRecordGroupIdsComponentSelector,
|
||||
const visibleRecordGroupIds = useRecoilComponentFamilyValueV2(
|
||||
visibleRecordGroupIdsComponentFamilySelector,
|
||||
ViewType.Kanban,
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@ -2,18 +2,19 @@ import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState';
|
||||
import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState';
|
||||
import { visibleRecordGroupIdsComponentSelector } from '@/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentSelector';
|
||||
import { visibleRecordGroupIdsComponentFamilySelector } from '@/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentFamilySelector';
|
||||
import { recordIndexRecordIdsByGroupComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordIdsByGroupComponentFamilyState';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { sortRecordsByPosition } from '@/object-record/utils/sortRecordsByPosition';
|
||||
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
|
||||
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
|
||||
import { ViewType } from '@/views/types/ViewType';
|
||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
export const useSetRecordBoardRecordIds = (recordBoardId?: string) => {
|
||||
const visibleRecordGroupIdsSelector = useRecoilComponentCallbackStateV2(
|
||||
visibleRecordGroupIdsComponentSelector,
|
||||
const visibleRecordGroupIdsFamilySelector = useRecoilComponentCallbackStateV2(
|
||||
visibleRecordGroupIdsComponentFamilySelector,
|
||||
);
|
||||
|
||||
const recordGroupFieldMetadataState = useRecoilComponentCallbackStateV2(
|
||||
@ -32,7 +33,7 @@ export const useSetRecordBoardRecordIds = (recordBoardId?: string) => {
|
||||
(records: ObjectRecord[]) => {
|
||||
const recordGroupIds = getSnapshotValue(
|
||||
snapshot,
|
||||
visibleRecordGroupIdsSelector,
|
||||
visibleRecordGroupIdsFamilySelector(ViewType.Kanban),
|
||||
);
|
||||
|
||||
for (const recordGroupId of recordGroupIds) {
|
||||
@ -72,7 +73,7 @@ export const useSetRecordBoardRecordIds = (recordBoardId?: string) => {
|
||||
}
|
||||
},
|
||||
[
|
||||
visibleRecordGroupIdsSelector,
|
||||
visibleRecordGroupIdsFamilySelector,
|
||||
recordIndexRecordIdsByGroupFamilyState,
|
||||
recordGroupFieldMetadataState,
|
||||
],
|
||||
|
||||
@ -4,9 +4,10 @@ import { useCallback, useRef } from 'react';
|
||||
import { useRecordGroupActions } from '@/object-record/record-group/hooks/useRecordGroupActions';
|
||||
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||
import { MenuItem } from 'twenty-ui';
|
||||
import { OverlayContainer } from '@/ui/layout/overlay/components/OverlayContainer';
|
||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||
import { ViewType } from '@/views/types/ViewType';
|
||||
import { MenuItem } from 'twenty-ui';
|
||||
|
||||
const StyledMenuContainer = styled.div`
|
||||
position: absolute;
|
||||
@ -27,7 +28,9 @@ export const RecordBoardColumnDropdownMenu = ({
|
||||
}: RecordBoardColumnDropdownMenuProps) => {
|
||||
const boardColumnMenuRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const recordGroupActions = useRecordGroupActions();
|
||||
const recordGroupActions = useRecordGroupActions({
|
||||
viewType: ViewType.Kanban,
|
||||
});
|
||||
|
||||
const closeMenu = useCallback(() => {
|
||||
onClose();
|
||||
|
||||
@ -0,0 +1,39 @@
|
||||
import { isRecordGroupReorderConfirmationModalVisibleState } from '@/object-record/record-group/states/isRecordGroupReorderConfirmationModalVisibleState';
|
||||
import { recordIndexRecordGroupSortComponentState } from '@/object-record/record-index/states/recordIndexRecordGroupSortComponentState';
|
||||
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
type RecordGroupReorderConfirmationModalProps = {
|
||||
onConfirmClick: () => void;
|
||||
};
|
||||
|
||||
export const RecordGroupReorderConfirmationModal = ({
|
||||
onConfirmClick,
|
||||
}: RecordGroupReorderConfirmationModalProps) => {
|
||||
const [
|
||||
isRecordGroupReorderConfirmationModalVisible,
|
||||
setIsRecordGroupReorderConfirmationModalVisible,
|
||||
] = useRecoilState(isRecordGroupReorderConfirmationModalVisibleState);
|
||||
|
||||
const recordGroupSort = useRecoilComponentValueV2(
|
||||
recordIndexRecordGroupSortComponentState,
|
||||
);
|
||||
|
||||
if (!isRecordGroupReorderConfirmationModalVisible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return createPortal(
|
||||
<ConfirmationModal
|
||||
isOpen={isRecordGroupReorderConfirmationModalVisible}
|
||||
setIsOpen={setIsRecordGroupReorderConfirmationModalVisible}
|
||||
title="Group sorting"
|
||||
subtitle={`Would you like to remove ${recordGroupSort} group sorting ?`}
|
||||
onConfirmClick={onConfirmClick}
|
||||
deleteButtonText="Remove"
|
||||
/>,
|
||||
document.body,
|
||||
);
|
||||
};
|
||||
@ -8,12 +8,19 @@ import { RecordGroupAction } from '@/object-record/record-group/types/RecordGrou
|
||||
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||
import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { ViewType } from '@/views/types/ViewType';
|
||||
import { useCallback, useContext, useMemo } from 'react';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
import { IconEyeOff, IconSettings, isDefined } from 'twenty-ui';
|
||||
|
||||
export const useRecordGroupActions = () => {
|
||||
type UseRecordGroupActionsParams = {
|
||||
viewType: ViewType;
|
||||
};
|
||||
|
||||
export const useRecordGroupActions = ({
|
||||
viewType,
|
||||
}: UseRecordGroupActionsParams) => {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
|
||||
@ -34,6 +41,7 @@ export const useRecordGroupActions = () => {
|
||||
const { handleVisibilityChange: handleRecordGroupVisibilityChange } =
|
||||
useRecordGroupVisibility({
|
||||
viewBarId: recordIndexId,
|
||||
viewType,
|
||||
});
|
||||
|
||||
const setNavigationMemorizedUrl = useSetRecoilState(
|
||||
|
||||
@ -2,11 +2,12 @@ import { OnDragEndResponder } from '@hello-pangea/dnd';
|
||||
|
||||
import { useSetRecordGroup } from '@/object-record/record-group/hooks/useSetRecordGroup';
|
||||
import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState';
|
||||
import { visibleRecordGroupIdsComponentSelector } from '@/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentSelector';
|
||||
import { visibleRecordGroupIdsComponentFamilySelector } from '@/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentFamilySelector';
|
||||
import { RecordGroupDefinition } from '@/object-record/record-group/types/RecordGroupDefinition';
|
||||
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
|
||||
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
|
||||
import { useSaveCurrentViewGroups } from '@/views/hooks/useSaveCurrentViewGroups';
|
||||
import { ViewType } from '@/views/types/ViewType';
|
||||
import { mapRecordGroupDefinitionsToViewGroups } from '@/views/utils/mapRecordGroupDefinitionsToViewGroups';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import { moveArrayItem } from '~/utils/array/moveArrayItem';
|
||||
@ -15,15 +16,17 @@ import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
type UseRecordGroupHandlersParams = {
|
||||
viewBarId: string;
|
||||
viewType: ViewType;
|
||||
};
|
||||
|
||||
export const useRecordGroupReorder = ({
|
||||
viewBarId,
|
||||
viewType,
|
||||
}: UseRecordGroupHandlersParams) => {
|
||||
const setRecordGroup = useSetRecordGroup(viewBarId);
|
||||
|
||||
const visibleRecordGroupIdsSelector = useRecoilComponentCallbackStateV2(
|
||||
visibleRecordGroupIdsComponentSelector,
|
||||
const visibleRecordGroupIdsFamilySelector = useRecoilComponentCallbackStateV2(
|
||||
visibleRecordGroupIdsComponentFamilySelector,
|
||||
);
|
||||
|
||||
const { saveViewGroups } = useSaveCurrentViewGroups(viewBarId);
|
||||
@ -37,7 +40,7 @@ export const useRecordGroupReorder = ({
|
||||
|
||||
const visibleRecordGroupIds = getSnapshotValue(
|
||||
snapshot,
|
||||
visibleRecordGroupIdsSelector,
|
||||
visibleRecordGroupIdsFamilySelector(viewType),
|
||||
);
|
||||
|
||||
const reorderedVisibleRecordGroupIds = moveArrayItem(
|
||||
@ -80,7 +83,12 @@ export const useRecordGroupReorder = ({
|
||||
mapRecordGroupDefinitionsToViewGroups(updatedRecordGroups),
|
||||
);
|
||||
},
|
||||
[saveViewGroups, setRecordGroup, visibleRecordGroupIdsSelector],
|
||||
[
|
||||
saveViewGroups,
|
||||
setRecordGroup,
|
||||
viewType,
|
||||
visibleRecordGroupIdsFamilySelector,
|
||||
],
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
@ -0,0 +1,78 @@
|
||||
import { useRecordGroupReorder } from '@/object-record/record-group/hooks/useRecordGroupReorder';
|
||||
import { isRecordGroupReorderConfirmationModalVisibleState } from '@/object-record/record-group/states/isRecordGroupReorderConfirmationModalVisibleState';
|
||||
import { RecordGroupSort } from '@/object-record/record-group/types/RecordGroupSort';
|
||||
import { recordIndexRecordGroupSortComponentState } from '@/object-record/record-index/states/recordIndexRecordGroupSortComponentState';
|
||||
import { recordIndexRecordGroupIsDraggableSortComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexRecordGroupIsDraggableSortComponentSelector';
|
||||
import { useGoBackToPreviousDropdownFocusId } from '@/ui/layout/dropdown/hooks/useGoBackToPreviousDropdownFocusId';
|
||||
import { useSetActiveDropdownFocusIdAndMemorizePrevious } from '@/ui/layout/dropdown/hooks/useSetFocusedDropdownIdAndMemorizePrevious';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
import { ViewType } from '@/views/types/ViewType';
|
||||
import { OnDragEndResponder } from '@hello-pangea/dnd';
|
||||
import { useState } from 'react';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
|
||||
type UseRecordGroupReorderConfirmationModalParams = {
|
||||
recordIndexId: string;
|
||||
viewType: ViewType;
|
||||
};
|
||||
|
||||
export const useRecordGroupReorderConfirmationModal = ({
|
||||
recordIndexId,
|
||||
viewType,
|
||||
}: UseRecordGroupReorderConfirmationModalParams) => {
|
||||
const { setActiveDropdownFocusIdAndMemorizePrevious } =
|
||||
useSetActiveDropdownFocusIdAndMemorizePrevious();
|
||||
const { goBackToPreviousDropdownFocusId } =
|
||||
useGoBackToPreviousDropdownFocusId();
|
||||
|
||||
const setIsRecordGroupReorderConfirmationModalVisible = useSetRecoilState(
|
||||
isRecordGroupReorderConfirmationModalVisibleState,
|
||||
);
|
||||
|
||||
const [pendingDragEndReorder, setPendingDragEndReorder] =
|
||||
useState<Parameters<OnDragEndResponder> | null>(null);
|
||||
|
||||
const { handleOrderChange: handleRecordGroupOrderChange } =
|
||||
useRecordGroupReorder({
|
||||
viewBarId: recordIndexId,
|
||||
viewType,
|
||||
});
|
||||
|
||||
const isDragableSortRecordGroup = useRecoilComponentValueV2(
|
||||
recordIndexRecordGroupIsDraggableSortComponentSelector,
|
||||
);
|
||||
|
||||
const setRecordGroupSort = useSetRecoilComponentStateV2(
|
||||
recordIndexRecordGroupSortComponentState,
|
||||
);
|
||||
|
||||
const handleRecordGroupOrderChangeWithModal: OnDragEndResponder = (
|
||||
result,
|
||||
provided,
|
||||
) => {
|
||||
if (!isDragableSortRecordGroup) {
|
||||
setIsRecordGroupReorderConfirmationModalVisible(true);
|
||||
setActiveDropdownFocusIdAndMemorizePrevious(null);
|
||||
setPendingDragEndReorder([result, provided]);
|
||||
} else {
|
||||
handleRecordGroupOrderChange(result, provided);
|
||||
}
|
||||
};
|
||||
|
||||
const handleConfirmClick = () => {
|
||||
if (!pendingDragEndReorder) {
|
||||
throw new Error('pendingDragEndReorder is not set');
|
||||
}
|
||||
|
||||
setRecordGroupSort(RecordGroupSort.Manual);
|
||||
setPendingDragEndReorder(null);
|
||||
handleRecordGroupOrderChange(...pendingDragEndReorder);
|
||||
goBackToPreviousDropdownFocusId();
|
||||
};
|
||||
|
||||
return {
|
||||
handleRecordGroupOrderChangeWithModal,
|
||||
handleRecordGroupReorderConfirmClick: handleConfirmClick,
|
||||
};
|
||||
};
|
||||
@ -1,20 +1,25 @@
|
||||
import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState';
|
||||
import { RecordGroupDefinition } from '@/object-record/record-group/types/RecordGroupDefinition';
|
||||
import { recordIndexRecordGroupHideComponentState } from '@/object-record/record-index/states/recordIndexRecordGroupHideComponentState';
|
||||
import { recordIndexRecordGroupHideComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordGroupHideComponentFamilyState';
|
||||
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
|
||||
import { useSaveCurrentViewGroups } from '@/views/hooks/useSaveCurrentViewGroups';
|
||||
import { ViewType } from '@/views/types/ViewType';
|
||||
import { recordGroupDefinitionToViewGroup } from '@/views/utils/recordGroupDefinitionToViewGroup';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
type UseRecordGroupVisibilityParams = {
|
||||
viewBarId: string;
|
||||
viewType: ViewType;
|
||||
};
|
||||
|
||||
export const useRecordGroupVisibility = ({
|
||||
viewBarId,
|
||||
viewType,
|
||||
}: UseRecordGroupVisibilityParams) => {
|
||||
const objectOptionsDropdownRecordGroupHideState =
|
||||
useRecoilComponentCallbackStateV2(recordIndexRecordGroupHideComponentState);
|
||||
const objectOptionsDropdownRecordGroupHideFamilyState =
|
||||
useRecoilComponentCallbackStateV2(
|
||||
recordIndexRecordGroupHideComponentFamilyState,
|
||||
);
|
||||
|
||||
const { saveViewGroup } = useSaveCurrentViewGroups(viewBarId);
|
||||
|
||||
@ -27,22 +32,19 @@ export const useRecordGroupVisibility = ({
|
||||
);
|
||||
|
||||
saveViewGroup(recordGroupDefinitionToViewGroup(updatedRecordGroup));
|
||||
|
||||
// If visibility is manually toggled, we should reset the hideEmptyRecordGroup state
|
||||
set(objectOptionsDropdownRecordGroupHideState, false);
|
||||
},
|
||||
[saveViewGroup, objectOptionsDropdownRecordGroupHideState],
|
||||
[saveViewGroup],
|
||||
);
|
||||
|
||||
const handleHideEmptyRecordGroupChange = useRecoilCallback(
|
||||
({ set }) =>
|
||||
async () => {
|
||||
set(
|
||||
objectOptionsDropdownRecordGroupHideState,
|
||||
objectOptionsDropdownRecordGroupHideFamilyState(viewType),
|
||||
(currentHideState) => !currentHideState,
|
||||
);
|
||||
},
|
||||
[objectOptionsDropdownRecordGroupHideState],
|
||||
[viewType, objectOptionsDropdownRecordGroupHideFamilyState],
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
@ -0,0 +1,6 @@
|
||||
import { atom } from 'recoil';
|
||||
|
||||
export const isRecordGroupReorderConfirmationModalVisibleState = atom<boolean>({
|
||||
key: 'isRecordGroupReorderConfirmationModalVisibleState',
|
||||
default: false,
|
||||
});
|
||||
@ -0,0 +1,8 @@
|
||||
import { OnDragEndResponder } from '@hello-pangea/dnd';
|
||||
import { atom } from 'recoil';
|
||||
|
||||
export const recordGroupPendingDragEndReorderState =
|
||||
atom<Parameters<OnDragEndResponder> | null>({
|
||||
key: 'recordGroupPendingDragEndReorderState',
|
||||
default: null,
|
||||
});
|
||||
@ -0,0 +1,50 @@
|
||||
import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState';
|
||||
import { recordGroupIdsComponentState } from '@/object-record/record-group/states/recordGroupIdsComponentState';
|
||||
import {
|
||||
RecordGroupDefinition,
|
||||
RecordGroupDefinitionType,
|
||||
} from '@/object-record/record-group/types/RecordGroupDefinition';
|
||||
import { recordGroupSortedInsert } from '@/object-record/record-group/utils/recordGroupSortedInsert';
|
||||
import { createComponentSelectorV2 } from '@/ui/utilities/state/component-state/utils/createComponentSelectorV2';
|
||||
|
||||
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
export const availableRecordGroupIdsComponentSelector =
|
||||
createComponentSelectorV2<RecordGroupDefinition['id'][]>({
|
||||
key: 'availableRecordGroupIdsComponentSelector',
|
||||
componentInstanceContext: ViewComponentInstanceContext,
|
||||
get:
|
||||
({ instanceId }) =>
|
||||
({ get }) => {
|
||||
const recordGroupIds = get(
|
||||
recordGroupIdsComponentState.atomFamily({
|
||||
instanceId,
|
||||
}),
|
||||
);
|
||||
|
||||
const result: RecordGroupDefinition[] = [];
|
||||
|
||||
for (const recordGroupId of recordGroupIds) {
|
||||
const recordGroupDefinition = get(
|
||||
recordGroupDefinitionFamilyState(recordGroupId),
|
||||
);
|
||||
|
||||
if (!isDefined(recordGroupDefinition)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
recordGroupDefinition.type === RecordGroupDefinitionType.NoValue
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
recordGroupSortedInsert(result, recordGroupDefinition, (a, b) =>
|
||||
a.title.localeCompare(b.title),
|
||||
);
|
||||
}
|
||||
|
||||
return result.map(({ id }) => id);
|
||||
},
|
||||
});
|
||||
@ -0,0 +1,84 @@
|
||||
import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState';
|
||||
import { recordGroupIdsComponentState } from '@/object-record/record-group/states/recordGroupIdsComponentState';
|
||||
import { RecordGroupDefinition } from '@/object-record/record-group/types/RecordGroupDefinition';
|
||||
import { RecordGroupSort } from '@/object-record/record-group/types/RecordGroupSort';
|
||||
import { recordGroupSortedInsert } from '@/object-record/record-group/utils/recordGroupSortedInsert';
|
||||
import { recordIndexRecordGroupHideComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordGroupHideComponentFamilyState';
|
||||
import { recordIndexRecordGroupSortComponentState } from '@/object-record/record-index/states/recordIndexRecordGroupSortComponentState';
|
||||
import { recordIndexRecordIdsByGroupComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordIdsByGroupComponentFamilyState';
|
||||
import { createComponentFamilySelectorV2 } from '@/ui/utilities/state/component-state/utils/createComponentFamilySelectorV2';
|
||||
|
||||
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
|
||||
import { ViewType } from '@/views/types/ViewType';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
export const visibleRecordGroupIdsComponentFamilySelector =
|
||||
createComponentFamilySelectorV2<RecordGroupDefinition['id'][], ViewType>({
|
||||
key: 'visibleRecordGroupIdsComponentFamilySelector',
|
||||
componentInstanceContext: ViewComponentInstanceContext,
|
||||
get:
|
||||
({ instanceId, familyKey }) =>
|
||||
({ get }) => {
|
||||
const recordGroupSort = get(
|
||||
recordIndexRecordGroupSortComponentState.atomFamily({
|
||||
instanceId,
|
||||
}),
|
||||
);
|
||||
const recordGroupIds = get(
|
||||
recordGroupIdsComponentState.atomFamily({
|
||||
instanceId,
|
||||
}),
|
||||
);
|
||||
const hideEmptyRecordGroup = get(
|
||||
recordIndexRecordGroupHideComponentFamilyState.atomFamily({
|
||||
instanceId,
|
||||
familyKey,
|
||||
}),
|
||||
);
|
||||
|
||||
const result: RecordGroupDefinition[] = [];
|
||||
|
||||
const comparator = (
|
||||
a: RecordGroupDefinition,
|
||||
b: RecordGroupDefinition,
|
||||
) => {
|
||||
switch (recordGroupSort) {
|
||||
case RecordGroupSort.Alphabetical:
|
||||
return a.title.localeCompare(b.title);
|
||||
case RecordGroupSort.ReverseAlphabetical:
|
||||
return b.title.localeCompare(a.title);
|
||||
case RecordGroupSort.Manual:
|
||||
default:
|
||||
return a.position - b.position;
|
||||
}
|
||||
};
|
||||
|
||||
for (const recordGroupId of recordGroupIds) {
|
||||
const recordGroupDefinition = get(
|
||||
recordGroupDefinitionFamilyState(recordGroupId),
|
||||
);
|
||||
const recordIds = get(
|
||||
recordIndexRecordIdsByGroupComponentFamilyState.atomFamily({
|
||||
instanceId,
|
||||
familyKey: recordGroupId,
|
||||
}),
|
||||
);
|
||||
|
||||
if (!isDefined(recordGroupDefinition)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (hideEmptyRecordGroup && recordIds.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!recordGroupDefinition.isVisible) {
|
||||
continue;
|
||||
}
|
||||
|
||||
recordGroupSortedInsert(result, recordGroupDefinition, comparator);
|
||||
}
|
||||
|
||||
return result.map(({ id }) => id);
|
||||
},
|
||||
});
|
||||
@ -1,83 +0,0 @@
|
||||
import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState';
|
||||
import { recordGroupIdsComponentState } from '@/object-record/record-group/states/recordGroupIdsComponentState';
|
||||
import { RecordGroupDefinition } from '@/object-record/record-group/types/RecordGroupDefinition';
|
||||
import { RecordGroupSort } from '@/object-record/record-group/types/RecordGroupSort';
|
||||
import { recordGroupSortedInsert } from '@/object-record/record-group/utils/recordGroupSortedInsert';
|
||||
import { recordIndexRecordGroupHideComponentState } from '@/object-record/record-index/states/recordIndexRecordGroupHideComponentState';
|
||||
import { recordIndexRecordGroupSortComponentState } from '@/object-record/record-index/states/recordIndexRecordGroupSortComponentState';
|
||||
import { recordIndexRecordIdsByGroupComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordIdsByGroupComponentFamilyState';
|
||||
|
||||
import { createComponentSelectorV2 } from '@/ui/utilities/state/component-state/utils/createComponentSelectorV2';
|
||||
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
export const visibleRecordGroupIdsComponentSelector = createComponentSelectorV2<
|
||||
RecordGroupDefinition['id'][]
|
||||
>({
|
||||
key: 'visibleRecordGroupIdsComponentSelector',
|
||||
componentInstanceContext: ViewComponentInstanceContext,
|
||||
get:
|
||||
({ instanceId }) =>
|
||||
({ get }) => {
|
||||
const recordGroupSort = get(
|
||||
recordIndexRecordGroupSortComponentState.atomFamily({
|
||||
instanceId,
|
||||
}),
|
||||
);
|
||||
const recordGroupIds = get(
|
||||
recordGroupIdsComponentState.atomFamily({
|
||||
instanceId,
|
||||
}),
|
||||
);
|
||||
const hideEmptyRecordGroup = get(
|
||||
recordIndexRecordGroupHideComponentState.atomFamily({
|
||||
instanceId,
|
||||
}),
|
||||
);
|
||||
|
||||
const result: RecordGroupDefinition[] = [];
|
||||
|
||||
const comparator = (
|
||||
a: RecordGroupDefinition,
|
||||
b: RecordGroupDefinition,
|
||||
) => {
|
||||
switch (recordGroupSort) {
|
||||
case RecordGroupSort.Alphabetical:
|
||||
return a.title.localeCompare(b.title);
|
||||
case RecordGroupSort.ReverseAlphabetical:
|
||||
return b.title.localeCompare(a.title);
|
||||
case RecordGroupSort.Manual:
|
||||
default:
|
||||
return a.position - b.position;
|
||||
}
|
||||
};
|
||||
|
||||
for (const recordGroupId of recordGroupIds) {
|
||||
const recordGroupDefinition = get(
|
||||
recordGroupDefinitionFamilyState(recordGroupId),
|
||||
);
|
||||
const recordIds = get(
|
||||
recordIndexRecordIdsByGroupComponentFamilyState.atomFamily({
|
||||
instanceId,
|
||||
familyKey: recordGroupId,
|
||||
}),
|
||||
);
|
||||
|
||||
if (!isDefined(recordGroupDefinition)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (hideEmptyRecordGroup && recordIds.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!recordGroupDefinition.isVisible) {
|
||||
continue;
|
||||
}
|
||||
|
||||
recordGroupSortedInsert(result, recordGroupDefinition, comparator);
|
||||
}
|
||||
|
||||
return result.map(({ id }) => id);
|
||||
},
|
||||
});
|
||||
@ -1,5 +1,5 @@
|
||||
import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState';
|
||||
import { visibleRecordGroupIdsComponentSelector } from '@/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentSelector';
|
||||
import { availableRecordGroupIdsComponentSelector } from '@/object-record/record-group/states/selectors/availableRecordGroupIdsComponentSelector';
|
||||
import { RecordGroupDefinition } from '@/object-record/record-group/types/RecordGroupDefinition';
|
||||
import { RecordIndexPageKanbanAddMenuItem } from '@/object-record/record-index/components/RecordIndexPageKanbanAddMenuItem';
|
||||
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||
@ -8,18 +8,27 @@ import { isRecordGroupTableSectionToggledComponentState } from '@/object-record/
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { PageAddButton } from '@/ui/layout/page/components/PageAddButton';
|
||||
import { useSetActiveDropdownFocusIdAndMemorizePrevious } from '@/ui/layout/dropdown/hooks/useSetFocusedDropdownIdAndMemorizePrevious';
|
||||
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
export const RecordIndexPageTableAddButtonInGroup = () => {
|
||||
const dropdownId = `record-index-page-table-add-button-dropdown`;
|
||||
type RecordIndexAddRecordInGroupDropdownProps = {
|
||||
dropdownId: string;
|
||||
clickableComponent: React.ReactNode;
|
||||
};
|
||||
|
||||
export const RecordIndexAddRecordInGroupDropdown = ({
|
||||
dropdownId,
|
||||
clickableComponent,
|
||||
}: RecordIndexAddRecordInGroupDropdownProps) => {
|
||||
const { objectMetadataItem } = useRecordIndexContextOrThrow();
|
||||
|
||||
const visibleRecordGroupIds = useRecoilComponentValueV2(
|
||||
visibleRecordGroupIdsComponentSelector,
|
||||
const { setActiveDropdownFocusIdAndMemorizePrevious } =
|
||||
useSetActiveDropdownFocusIdAndMemorizePrevious();
|
||||
|
||||
const recordGroupIds = useRecoilComponentValueV2(
|
||||
availableRecordGroupIdsComponentSelector,
|
||||
);
|
||||
|
||||
const recordGroupFieldMetadata = useRecoilComponentValueV2(
|
||||
@ -44,11 +53,13 @@ export const RecordIndexPageTableAddButtonInGroup = () => {
|
||||
(recordGroup: RecordGroupDefinition) => {
|
||||
set(isRecordGroupTableSectionToggledState(recordGroup.id), true);
|
||||
createNewTableRecordInGroup(recordGroup.id);
|
||||
setActiveDropdownFocusIdAndMemorizePrevious(null);
|
||||
closeDropdown();
|
||||
},
|
||||
[
|
||||
closeDropdown,
|
||||
createNewTableRecordInGroup,
|
||||
setActiveDropdownFocusIdAndMemorizePrevious,
|
||||
isRecordGroupTableSectionToggledState,
|
||||
],
|
||||
);
|
||||
@ -61,11 +72,11 @@ export const RecordIndexPageTableAddButtonInGroup = () => {
|
||||
<Dropdown
|
||||
dropdownMenuWidth="200px"
|
||||
dropdownPlacement="bottom-start"
|
||||
clickableComponent={<PageAddButton />}
|
||||
clickableComponent={clickableComponent}
|
||||
dropdownId={dropdownId}
|
||||
dropdownComponents={
|
||||
<DropdownMenuItemsContainer>
|
||||
{visibleRecordGroupIds.map((recordGroupId) => (
|
||||
{recordGroupIds.map((recordGroupId) => (
|
||||
<RecordIndexPageKanbanAddMenuItem
|
||||
key={recordGroupId}
|
||||
columnId={recordGroupId}
|
||||
@ -2,7 +2,7 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSi
|
||||
import { useAddNewCard } from '@/object-record/record-board/record-board-column/hooks/useAddNewCard';
|
||||
import { useIsOpportunitiesCompanyFieldDisabled } from '@/object-record/record-board/record-board-column/hooks/useIsOpportunitiesCompanyFieldDisabled';
|
||||
import { recordBoardVisibleFieldDefinitionsComponentSelector } from '@/object-record/record-board/states/selectors/recordBoardVisibleFieldDefinitionsComponentSelector';
|
||||
import { visibleRecordGroupIdsComponentSelector } from '@/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentSelector';
|
||||
import { visibleRecordGroupIdsComponentFamilySelector } from '@/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentFamilySelector';
|
||||
import { RecordGroupDefinition } from '@/object-record/record-group/types/RecordGroupDefinition';
|
||||
import { RecordIndexPageKanbanAddMenuItem } from '@/object-record/record-index/components/RecordIndexPageKanbanAddMenuItem';
|
||||
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||
@ -11,7 +11,9 @@ import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { PageAddButton } from '@/ui/layout/page/components/PageAddButton';
|
||||
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { ViewType } from '@/views/types/ViewType';
|
||||
import { useCallback } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
@ -20,8 +22,9 @@ export const RecordIndexPageKanbanAddButton = () => {
|
||||
|
||||
const { recordIndexId, objectMetadataItem } = useRecordIndexContextOrThrow();
|
||||
|
||||
const visibleRecordGroupIds = useRecoilComponentValueV2(
|
||||
visibleRecordGroupIdsComponentSelector,
|
||||
const visibleRecordGroupIds = useRecoilComponentFamilyValueV2(
|
||||
visibleRecordGroupIdsComponentFamilySelector,
|
||||
ViewType.Kanban,
|
||||
);
|
||||
|
||||
const recordIndexKanbanFieldMetadataId = useRecoilValue(
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { hasRecordGroupsComponentSelector } from '@/object-record/record-group/states/selectors/hasRecordGroupsComponentSelector';
|
||||
import { RecordIndexPageTableAddButtonInGroup } from '@/object-record/record-index/components/RecordIndexPageTableAddButtonInGroup';
|
||||
import { RecordIndexAddRecordInGroupDropdown } from '@/object-record/record-index/components/RecordIndexAddRecordInGroupDropdown';
|
||||
import { RecordIndexPageTableAddButtonNoGroup } from '@/object-record/record-index/components/RecordIndexPageTableAddButtonNoGroup';
|
||||
import { PageAddButton } from '@/ui/layout/page/components/PageAddButton';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
|
||||
export const RecordIndexPageTableAddButton = () => {
|
||||
@ -12,5 +13,10 @@ export const RecordIndexPageTableAddButton = () => {
|
||||
return <RecordIndexPageTableAddButtonNoGroup />;
|
||||
}
|
||||
|
||||
return <RecordIndexPageTableAddButtonInGroup />;
|
||||
return (
|
||||
<RecordIndexAddRecordInGroupDropdown
|
||||
dropdownId="record-index-page-table-add-button-dropdown"
|
||||
clickableComponent={<PageAddButton />}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@ -61,6 +61,8 @@ export const useHandleRecordGroupField = ({
|
||||
(option) =>
|
||||
!existingGroupKeys.has(`${fieldMetadataItem.id}:${option.value}`),
|
||||
)
|
||||
// Alphabetically sort the options by default
|
||||
.sort((a, b) => a.value.localeCompare(b.value))
|
||||
.map(
|
||||
(option, index) =>
|
||||
({
|
||||
|
||||
@ -0,0 +1,19 @@
|
||||
import { createComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentFamilyStateV2';
|
||||
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
|
||||
import { ViewType } from '@/views/types/ViewType';
|
||||
|
||||
export const recordIndexRecordGroupHideComponentFamilyState =
|
||||
createComponentFamilyStateV2<boolean, ViewType>({
|
||||
key: 'recordIndexRecordGroupHideComponentFamilyState',
|
||||
defaultValue: ({ familyKey }) => {
|
||||
switch (familyKey) {
|
||||
case ViewType.Kanban:
|
||||
return false;
|
||||
case ViewType.Table:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
},
|
||||
componentInstanceContext: ViewComponentInstanceContext,
|
||||
});
|
||||
@ -1,9 +0,0 @@
|
||||
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
|
||||
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
|
||||
|
||||
export const recordIndexRecordGroupHideComponentState =
|
||||
createComponentStateV2<boolean>({
|
||||
key: 'recordIndexRecordGroupHideComponentState',
|
||||
defaultValue: false,
|
||||
componentInstanceContext: ViewComponentInstanceContext,
|
||||
});
|
||||
@ -1,5 +1,5 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { isNonEmptyString, isNull } from '@sniptt/guards';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
|
||||
import { hasRecordGroupsComponentSelector } from '@/object-record/record-group/states/selectors/hasRecordGroupsComponentSelector';
|
||||
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
|
||||
@ -16,7 +16,7 @@ import { RecordTableRecordGroupsBody } from '@/object-record/record-table/record
|
||||
import { RecordTableAggregateFooter } from '@/object-record/record-table/record-table-footer/components/RecordTableAggregateFooter';
|
||||
import { RecordTableHeader } from '@/object-record/record-table/record-table-header/components/RecordTableHeader';
|
||||
import { isRecordTableInitialLoadingComponentState } from '@/object-record/record-table/states/isRecordTableInitialLoadingComponentState';
|
||||
import { recordTablePendingRecordIdComponentState } from '@/object-record/record-table/states/recordTablePendingRecordIdComponentState';
|
||||
import { hasPendingRecordComponentSelector } from '@/object-record/record-table/states/selectors/hasPendingRecordComponentSelector';
|
||||
import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect';
|
||||
import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useClickOutsideListener';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
@ -54,8 +54,8 @@ export const RecordTable = () => {
|
||||
recordTableId,
|
||||
);
|
||||
|
||||
const pendingRecordId = useRecoilComponentValueV2(
|
||||
recordTablePendingRecordIdComponentState,
|
||||
const hasPendingRecord = useRecoilComponentValueV2(
|
||||
hasPendingRecordComponentSelector,
|
||||
recordTableId,
|
||||
);
|
||||
|
||||
@ -67,7 +67,7 @@ export const RecordTable = () => {
|
||||
const recordTableIsEmpty =
|
||||
!isRecordTableInitialLoading &&
|
||||
allRecordIds.length === 0 &&
|
||||
isNull(pendingRecordId);
|
||||
!hasPendingRecord;
|
||||
|
||||
const { resetTableRowSelection, setRowSelected } = useRecordTable({
|
||||
recordTableId,
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
||||
import { hasRecordGroupsComponentSelector } from '@/object-record/record-group/states/selectors/hasRecordGroupsComponentSelector';
|
||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
import { RecordTableEmptyStateNoRecordAtAll } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateNoRecordAtAll';
|
||||
import { RecordTableEmptyStateByGroupNoRecordAtAll } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateByGroupNoRecordAtAll';
|
||||
import { RecordTableEmptyStateNoGroupNoRecordAtAll } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateNoGroupNoRecordAtAll';
|
||||
import { RecordTableEmptyStateNoRecordFoundForFilter } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateNoRecordFoundForFilter';
|
||||
import { RecordTableEmptyStateRemote } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateRemote';
|
||||
import { RecordTableEmptyStateSoftDelete } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateSoftDelete';
|
||||
@ -11,6 +13,10 @@ export const RecordTableEmptyState = () => {
|
||||
const { recordTableId, objectNameSingular, objectMetadataItem } =
|
||||
useRecordTableContextOrThrow();
|
||||
|
||||
const hasRecordGroups = useRecoilComponentValueV2(
|
||||
hasRecordGroupsComponentSelector,
|
||||
);
|
||||
|
||||
const { totalCount } = useFindManyRecords({ objectNameSingular, limit: 1 });
|
||||
const noRecordAtAll = totalCount === 0;
|
||||
|
||||
@ -26,7 +32,11 @@ export const RecordTableEmptyState = () => {
|
||||
} else if (isSoftDeleteActive === true) {
|
||||
return <RecordTableEmptyStateSoftDelete />;
|
||||
} else if (noRecordAtAll) {
|
||||
return <RecordTableEmptyStateNoRecordAtAll />;
|
||||
if (hasRecordGroups) {
|
||||
return <RecordTableEmptyStateByGroupNoRecordAtAll />;
|
||||
}
|
||||
|
||||
return <RecordTableEmptyStateNoGroupNoRecordAtAll />;
|
||||
} else {
|
||||
return <RecordTableEmptyStateNoRecordFoundForFilter />;
|
||||
}
|
||||
|
||||
@ -0,0 +1,52 @@
|
||||
import { Button, IconPlus } from 'twenty-ui';
|
||||
|
||||
import { useObjectLabel } from '@/object-metadata/hooks/useObjectLabel';
|
||||
import { RecordIndexAddRecordInGroupDropdown } from '@/object-record/record-index/components/RecordIndexAddRecordInGroupDropdown';
|
||||
import { recordIndexRecordGroupHideComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordGroupHideComponentFamilyState';
|
||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
import { RecordTableEmptyStateDisplay } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateDisplay';
|
||||
import { useSetRecoilComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentFamilyStateV2';
|
||||
import { ViewType } from '@/views/types/ViewType';
|
||||
|
||||
export const RecordTableEmptyStateByGroupNoRecordAtAll = () => {
|
||||
const { objectMetadataItem } = useRecordTableContextOrThrow();
|
||||
|
||||
const setHideEmptyRecordGroup = useSetRecoilComponentFamilyStateV2(
|
||||
recordIndexRecordGroupHideComponentFamilyState,
|
||||
ViewType.Table,
|
||||
);
|
||||
|
||||
const objectLabel = useObjectLabel(objectMetadataItem);
|
||||
|
||||
const buttonTitle = `Add a ${objectLabel}`;
|
||||
|
||||
const title = `Add your first ${objectLabel}`;
|
||||
|
||||
const subTitle = `Use our API or add your first ${objectLabel} manually`;
|
||||
|
||||
const handleButtonClick = () => {
|
||||
// When we have no records in the group, we want to show the empty state
|
||||
setHideEmptyRecordGroup(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<RecordTableEmptyStateDisplay
|
||||
title={title}
|
||||
subTitle={subTitle}
|
||||
animatedPlaceholderType="noRecord"
|
||||
buttonComponent={
|
||||
<RecordIndexAddRecordInGroupDropdown
|
||||
dropdownId="record-table-empty-state-add-button-dropdown"
|
||||
clickableComponent={
|
||||
<Button
|
||||
Icon={IconPlus}
|
||||
title={buttonTitle}
|
||||
variant={'secondary'}
|
||||
onClick={handleButtonClick}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -11,41 +11,49 @@ import {
|
||||
IconComponent,
|
||||
} from 'twenty-ui';
|
||||
|
||||
type RecordTableEmptyStateDisplayProps = {
|
||||
animatedPlaceholderType: AnimatedPlaceholderType;
|
||||
title: string;
|
||||
subTitle: string;
|
||||
Icon: IconComponent;
|
||||
type RecordTableEmptyStateDisplayButtonComponentProps = {
|
||||
buttonComponent?: React.ReactNode;
|
||||
};
|
||||
|
||||
type RecordTableEmptyStateDisplayButtonProps = {
|
||||
ButtonIcon: IconComponent;
|
||||
buttonTitle: string;
|
||||
onClick: () => void;
|
||||
};
|
||||
|
||||
export const RecordTableEmptyStateDisplay = ({
|
||||
Icon,
|
||||
animatedPlaceholderType,
|
||||
buttonTitle,
|
||||
onClick,
|
||||
subTitle,
|
||||
title,
|
||||
}: RecordTableEmptyStateDisplayProps) => {
|
||||
type RecordTableEmptyStateDisplayProps = {
|
||||
animatedPlaceholderType: AnimatedPlaceholderType;
|
||||
title: string;
|
||||
subTitle: string;
|
||||
} & (
|
||||
| RecordTableEmptyStateDisplayButtonComponentProps
|
||||
| RecordTableEmptyStateDisplayButtonProps
|
||||
);
|
||||
|
||||
export const RecordTableEmptyStateDisplay = (
|
||||
props: RecordTableEmptyStateDisplayProps,
|
||||
) => {
|
||||
const { objectMetadataItem } = useRecordTableContextOrThrow();
|
||||
const isReadOnly = isObjectMetadataReadOnly(objectMetadataItem);
|
||||
|
||||
return (
|
||||
<AnimatedPlaceholderEmptyContainer>
|
||||
<AnimatedPlaceholder type={animatedPlaceholderType} />
|
||||
<AnimatedPlaceholder type={props.animatedPlaceholderType} />
|
||||
<AnimatedPlaceholderEmptyTextContainer>
|
||||
<AnimatedPlaceholderEmptyTitle>{title}</AnimatedPlaceholderEmptyTitle>
|
||||
<AnimatedPlaceholderEmptyTitle>
|
||||
{props.title}
|
||||
</AnimatedPlaceholderEmptyTitle>
|
||||
<AnimatedPlaceholderEmptySubTitle>
|
||||
{subTitle}
|
||||
{props.subTitle}
|
||||
</AnimatedPlaceholderEmptySubTitle>
|
||||
</AnimatedPlaceholderEmptyTextContainer>
|
||||
{!isReadOnly && (
|
||||
{'buttonComponent' in props && props.buttonComponent}
|
||||
{'buttonTitle' in props && !isReadOnly && (
|
||||
<Button
|
||||
Icon={Icon}
|
||||
title={buttonTitle}
|
||||
Icon={props.ButtonIcon}
|
||||
title={props.buttonTitle}
|
||||
variant={'secondary'}
|
||||
onClick={onClick}
|
||||
onClick={props.onClick}
|
||||
/>
|
||||
)}
|
||||
</AnimatedPlaceholderEmptyContainer>
|
||||
|
||||
@ -5,7 +5,7 @@ import { useRecordTableContextOrThrow } from '@/object-record/record-table/conte
|
||||
import { RecordTableEmptyStateDisplay } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateDisplay';
|
||||
import { useCreateNewTableRecord } from '@/object-record/record-table/hooks/useCreateNewTableRecords';
|
||||
|
||||
export const RecordTableEmptyStateNoRecordAtAll = () => {
|
||||
export const RecordTableEmptyStateNoGroupNoRecordAtAll = () => {
|
||||
const { objectMetadataItem, recordTableId } = useRecordTableContextOrThrow();
|
||||
|
||||
const { createNewTableRecord } = useCreateNewTableRecord(recordTableId);
|
||||
@ -27,7 +27,7 @@ export const RecordTableEmptyStateNoRecordAtAll = () => {
|
||||
buttonTitle={buttonTitle}
|
||||
subTitle={subTitle}
|
||||
title={title}
|
||||
Icon={IconPlus}
|
||||
ButtonIcon={IconPlus}
|
||||
animatedPlaceholderType="noRecord"
|
||||
onClick={handleButtonClick}
|
||||
/>
|
||||
@ -27,7 +27,7 @@ export const RecordTableEmptyStateNoRecordFoundForFilter = () => {
|
||||
buttonTitle={buttonTitle}
|
||||
subTitle={subTitle}
|
||||
title={title}
|
||||
Icon={IconPlus}
|
||||
ButtonIcon={IconPlus}
|
||||
animatedPlaceholderType="noMatchRecord"
|
||||
onClick={handleButtonClick}
|
||||
/>
|
||||
|
||||
@ -16,7 +16,7 @@ export const RecordTableEmptyStateRemote = () => {
|
||||
buttonTitle={'Go to Settings'}
|
||||
subTitle={'If this is unexpected, please verify your settings.'}
|
||||
title={'No Data Available for Remote Table'}
|
||||
Icon={IconSettings}
|
||||
ButtonIcon={IconSettings}
|
||||
animatedPlaceholderType="noRecord"
|
||||
onClick={handleButtonClick}
|
||||
/>
|
||||
|
||||
@ -43,7 +43,7 @@ export const RecordTableEmptyStateSoftDelete = () => {
|
||||
buttonTitle={'Remove Deleted filter'}
|
||||
subTitle={'No deleted records matching the filter criteria were found.'}
|
||||
title={`No Deleted ${objectLabel} found`}
|
||||
Icon={IconFilterOff}
|
||||
ButtonIcon={IconFilterOff}
|
||||
animatedPlaceholderType="noDeletedRecord"
|
||||
onClick={handleButtonClick}
|
||||
/>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { RecordTableComponentInstance } from '@/object-record/record-table/components/RecordTableComponentInstance';
|
||||
import { RecordTableEmptyStateNoRecordAtAll } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateNoRecordAtAll';
|
||||
import { RecordTableEmptyStateNoGroupNoRecordAtAll } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateNoGroupNoRecordAtAll';
|
||||
import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope';
|
||||
import { ComponentDecorator } from 'twenty-ui';
|
||||
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
|
||||
@ -10,8 +10,9 @@ import { RecordTableDecorator } from '~/testing/decorators/RecordTableDecorator'
|
||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'Modules/ObjectRecord/RecordTable/RecordTableEmptyStateNoRecordAtAll',
|
||||
component: RecordTableEmptyStateNoRecordAtAll,
|
||||
title:
|
||||
'Modules/ObjectRecord/RecordTable/RecordTableEmptyStateNoGroupNoRecordAtAll',
|
||||
component: RecordTableEmptyStateNoGroupNoRecordAtAll,
|
||||
decorators: [
|
||||
ComponentDecorator,
|
||||
MemoryRouterDecorator,
|
||||
@ -34,6 +35,6 @@ const meta: Meta = {
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof RecordTableEmptyStateNoRecordAtAll>;
|
||||
type Story = StoryObj<typeof RecordTableEmptyStateNoGroupNoRecordAtAll>;
|
||||
|
||||
export const Default: Story = {};
|
||||
@ -1,5 +1,5 @@
|
||||
import { RecordGroupContext } from '@/object-record/record-group/states/context/RecordGroupContext';
|
||||
import { visibleRecordGroupIdsComponentSelector } from '@/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentSelector';
|
||||
import { visibleRecordGroupIdsComponentFamilySelector } from '@/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentFamilySelector';
|
||||
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
|
||||
import { RecordTableRecordGroupBodyContextProvider } from '@/object-record/record-table/components/RecordTableRecordGroupBodyContextProvider';
|
||||
import { RecordTableRecordGroupRows } from '@/object-record/record-table/components/RecordTableRecordGroupRows';
|
||||
@ -7,10 +7,11 @@ import { RecordTableBodyDroppable } from '@/object-record/record-table/record-ta
|
||||
import { RecordTableBodyLoading } from '@/object-record/record-table/record-table-body/components/RecordTableBodyLoading';
|
||||
import { RecordTableBodyRecordGroupDragDropContextProvider } from '@/object-record/record-table/record-table-body/components/RecordTableBodyRecordGroupDragDropContextProvider';
|
||||
import { RecordTableAggregateFooter } from '@/object-record/record-table/record-table-footer/components/RecordTableAggregateFooter';
|
||||
import { RecordTableRecordGroupEmptyRow } from '@/object-record/record-table/record-table-section/components/RecordTableRecordGroupEmptyRow';
|
||||
import { RecordTableRecordGroupSection } from '@/object-record/record-table/record-table-section/components/RecordTableRecordGroupSection';
|
||||
import { isRecordTableInitialLoadingComponentState } from '@/object-record/record-table/states/isRecordTableInitialLoadingComponentState';
|
||||
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { ViewType } from '@/views/types/ViewType';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { FeatureFlagKey } from '~/generated/graphql';
|
||||
|
||||
@ -26,8 +27,9 @@ export const RecordTableRecordGroupsBody = () => {
|
||||
isRecordTableInitialLoadingComponentState,
|
||||
);
|
||||
|
||||
const visibleRecordGroupIds = useRecoilComponentValueV2(
|
||||
visibleRecordGroupIdsComponentSelector,
|
||||
const visibleRecordGroupIds = useRecoilComponentFamilyValueV2(
|
||||
visibleRecordGroupIdsComponentFamilySelector,
|
||||
ViewType.Table,
|
||||
);
|
||||
|
||||
if (isRecordTableInitialLoading && allRecordIds.length === 0) {
|
||||
@ -37,14 +39,13 @@ export const RecordTableRecordGroupsBody = () => {
|
||||
return (
|
||||
<>
|
||||
<RecordTableBodyRecordGroupDragDropContextProvider>
|
||||
{visibleRecordGroupIds.map((recordGroupId, index) => (
|
||||
{visibleRecordGroupIds.map((recordGroupId) => (
|
||||
<RecordTableRecordGroupBodyContextProvider
|
||||
key={recordGroupId}
|
||||
recordGroupId={recordGroupId}
|
||||
>
|
||||
<RecordGroupContext.Provider value={{ recordGroupId }}>
|
||||
<RecordTableBodyDroppable recordGroupId={recordGroupId}>
|
||||
{index > 0 && <RecordTableRecordGroupEmptyRow />}
|
||||
<RecordTableRecordGroupSection />
|
||||
<RecordTableRecordGroupRows />
|
||||
</RecordTableBodyDroppable>
|
||||
|
||||
@ -1,7 +0,0 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
const StyledTrContainer = styled.tr`
|
||||
height: 32px;
|
||||
`;
|
||||
|
||||
export const RecordTableRecordGroupEmptyRow = StyledTrContainer;
|
||||
@ -2,7 +2,7 @@ import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { motion } from 'framer-motion';
|
||||
import { useCallback } from 'react';
|
||||
import { IconChevronUp, isDefined, Tag } from 'twenty-ui';
|
||||
import { IconChevronDown, isDefined, Tag } from 'twenty-ui';
|
||||
|
||||
import { useCurrentRecordGroupId } from '@/object-record/record-group/hooks/useCurrentRecordGroupId';
|
||||
import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState';
|
||||
@ -83,13 +83,13 @@ export const RecordTableRecordGroupSection = () => {
|
||||
<td aria-hidden />
|
||||
<StyledChevronContainer>
|
||||
<motion.span
|
||||
animate={{ rotate: isRecordGroupTableSectionToggled ? 180 : 0 }}
|
||||
animate={{ rotate: !isRecordGroupTableSectionToggled ? -90 : 0 }}
|
||||
transition={{ duration: theme.animation.duration.normal }}
|
||||
style={{
|
||||
display: 'inline-block',
|
||||
}}
|
||||
>
|
||||
<IconChevronUp size={theme.icon.size.md} />
|
||||
<IconChevronDown size={theme.icon.size.md} />
|
||||
</motion.span>
|
||||
</StyledChevronContainer>
|
||||
<StyledRecordGroupSection>
|
||||
|
||||
@ -0,0 +1,46 @@
|
||||
import { recordGroupIdsComponentState } from '@/object-record/record-group/states/recordGroupIdsComponentState';
|
||||
import { hasRecordGroupsComponentSelector } from '@/object-record/record-group/states/selectors/hasRecordGroupsComponentSelector';
|
||||
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
|
||||
import { recordTablePendingRecordIdByGroupComponentFamilyState } from '@/object-record/record-table/states/recordTablePendingRecordIdByGroupComponentFamilyState';
|
||||
import { recordTablePendingRecordIdComponentState } from '@/object-record/record-table/states/recordTablePendingRecordIdComponentState';
|
||||
import { createComponentSelectorV2 } from '@/ui/utilities/state/component-state/utils/createComponentSelectorV2';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
export const hasPendingRecordComponentSelector = createComponentSelectorV2({
|
||||
key: 'hasPendingRecordComponentSelector',
|
||||
componentInstanceContext: RecordTableComponentInstanceContext,
|
||||
get:
|
||||
({ instanceId }) =>
|
||||
({ get }) => {
|
||||
const hasRecordGroups = get(
|
||||
hasRecordGroupsComponentSelector.selectorFamily({ instanceId }),
|
||||
);
|
||||
|
||||
if (!hasRecordGroups) {
|
||||
const pendingRecordId = get(
|
||||
recordTablePendingRecordIdComponentState.atomFamily({ instanceId }),
|
||||
);
|
||||
|
||||
return !isDefined(pendingRecordId);
|
||||
}
|
||||
|
||||
const recordGroupIds = get(
|
||||
recordGroupIdsComponentState.atomFamily({ instanceId }),
|
||||
);
|
||||
|
||||
for (const recordGroupId of recordGroupIds) {
|
||||
const pendingRecordId = get(
|
||||
recordTablePendingRecordIdByGroupComponentFamilyState.atomFamily({
|
||||
instanceId,
|
||||
familyKey: recordGroupId,
|
||||
}),
|
||||
);
|
||||
|
||||
if (!isDefined(pendingRecordId)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
});
|
||||
@ -11,6 +11,7 @@ type Params<V extends string> = {
|
||||
export const getSettingsPagePath = <Path extends SettingsPath>(
|
||||
path: Path,
|
||||
params?: Params<Path>,
|
||||
searchParams?: Record<string, string>,
|
||||
) => {
|
||||
let resultPath = `/settings/${path}`;
|
||||
|
||||
@ -26,5 +27,11 @@ export const getSettingsPagePath = <Path extends SettingsPath>(
|
||||
resultPath = `${resultPath}/${params?.id}`;
|
||||
}
|
||||
|
||||
if (isDefined(searchParams)) {
|
||||
const searchParamsString = new URLSearchParams(searchParams).toString();
|
||||
|
||||
resultPath = `${resultPath}?${searchParamsString}`;
|
||||
}
|
||||
|
||||
return resultPath;
|
||||
};
|
||||
|
||||
@ -71,6 +71,7 @@ export const DropdownMenuItemsContainer = ({
|
||||
<ScrollWrapper
|
||||
contextProviderName="dropdownMenuItemsContainer"
|
||||
componentInstanceId={`scroll-wrapper-dropdown-menu-${id}`}
|
||||
heightMode="fit-content"
|
||||
>
|
||||
<StyledDropdownMenuItemsExternalContainer
|
||||
hasMaxHeight={hasMaxHeight}
|
||||
|
||||
@ -6,7 +6,7 @@ import { previousDropdownFocusIdState } from '@/ui/layout/dropdown/states/previo
|
||||
export const useSetActiveDropdownFocusIdAndMemorizePrevious = () => {
|
||||
const setActiveDropdownFocusIdAndMemorizePrevious = useRecoilCallback(
|
||||
({ snapshot, set }) =>
|
||||
(dropdownId: string) => {
|
||||
(dropdownId: string | null) => {
|
||||
const focusedDropdownId = snapshot
|
||||
.getLoadable(activeDropdownFocusIdState)
|
||||
.getValue();
|
||||
|
||||
@ -142,7 +142,9 @@ export const ConfirmationModal = ({
|
||||
</Section>
|
||||
)}
|
||||
<StyledCenteredButton
|
||||
onClick={() => setIsOpen(false)}
|
||||
onClick={() => {
|
||||
setIsOpen(false);
|
||||
}}
|
||||
variant="secondary"
|
||||
title="Cancel"
|
||||
fullWidth
|
||||
|
||||
@ -15,9 +15,21 @@ import { scrollWrapperScrollTopComponentState } from '@/ui/utilities/scroll/stat
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
import 'overlayscrollbars/overlayscrollbars.css';
|
||||
|
||||
const StyledScrollWrapper = styled.div<{ scrollHide?: boolean }>`
|
||||
type HeightMode = 'full' | 'fit-content';
|
||||
|
||||
const StyledScrollWrapper = styled.div<{
|
||||
scrollHide?: boolean;
|
||||
heightMode: HeightMode;
|
||||
}>`
|
||||
display: flex;
|
||||
height: 100%;
|
||||
height: ${({ heightMode }) => {
|
||||
switch (heightMode) {
|
||||
case 'full':
|
||||
return '100%';
|
||||
case 'fit-content':
|
||||
return 'fit-content';
|
||||
}
|
||||
}};
|
||||
width: 100%;
|
||||
|
||||
.os-scrollbar-handle {
|
||||
@ -33,6 +45,7 @@ const StyledInnerContainer = styled.div`
|
||||
export type ScrollWrapperProps = {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
heightMode?: HeightMode;
|
||||
defaultEnableXScroll?: boolean;
|
||||
defaultEnableYScroll?: boolean;
|
||||
contextProviderName: ContextProviderName;
|
||||
@ -44,6 +57,7 @@ export const ScrollWrapper = ({
|
||||
componentInstanceId,
|
||||
children,
|
||||
className,
|
||||
heightMode = 'full',
|
||||
defaultEnableXScroll = true,
|
||||
defaultEnableYScroll = true,
|
||||
contextProviderName,
|
||||
@ -164,6 +178,7 @@ export const ScrollWrapper = ({
|
||||
ref={scrollableRef}
|
||||
className={className}
|
||||
scrollHide={scrollHide}
|
||||
heightMode={heightMode}
|
||||
>
|
||||
<StyledInnerContainer>{children}</StyledInnerContainer>
|
||||
</StyledScrollWrapper>
|
||||
|
||||
@ -2,7 +2,14 @@ import { ComponentFamilyStateKeyV2 } from '@/ui/utilities/state/component-state/
|
||||
import { ComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/types/ComponentFamilyStateV2';
|
||||
import { ComponentInstanceStateContext } from '@/ui/utilities/state/component-state/types/ComponentInstanceStateContext';
|
||||
import { globalComponentInstanceContextMap } from '@/ui/utilities/state/component-state/utils/globalComponentInstanceContextMap';
|
||||
import { AtomEffect, atomFamily, SerializableParam } from 'recoil';
|
||||
import {
|
||||
AtomEffect,
|
||||
atomFamily,
|
||||
Loadable,
|
||||
RecoilValue,
|
||||
SerializableParam,
|
||||
WrappedValue,
|
||||
} from 'recoil';
|
||||
|
||||
import { isDefined } from 'twenty-ui';
|
||||
|
||||
@ -11,7 +18,16 @@ type CreateComponentFamilyStateArgs<
|
||||
FamilyKey extends SerializableParam,
|
||||
> = {
|
||||
key: string;
|
||||
defaultValue: ValueType;
|
||||
defaultValue:
|
||||
| ValueType
|
||||
| ((
|
||||
param: ComponentFamilyStateKeyV2<FamilyKey>,
|
||||
) =>
|
||||
| ValueType
|
||||
| RecoilValue<ValueType>
|
||||
| Promise<ValueType>
|
||||
| Loadable<ValueType>
|
||||
| WrappedValue<ValueType>);
|
||||
componentInstanceContext: ComponentInstanceStateContext<any> | null;
|
||||
effects?:
|
||||
| AtomEffect<ValueType>[]
|
||||
|
||||
@ -1,77 +1,43 @@
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
import { useCallback } from 'react';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { triggerCreateRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerCreateRecordsOptimisticEffect';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useCreateOneRecordMutation } from '@/object-record/hooks/useCreateOneRecordMutation';
|
||||
import { useDeleteOneRecordMutation } from '@/object-record/hooks/useDeleteOneRecordMutation';
|
||||
import { useCreateManyRecords } from '@/object-record/hooks/useCreateManyRecords';
|
||||
import { useDestroyManyRecords } from '@/object-record/hooks/useDestroyManyRecords';
|
||||
import { useUpdateOneRecordMutation } from '@/object-record/hooks/useUpdateOneRecordMutation';
|
||||
import { GraphQLView } from '@/views/types/GraphQLView';
|
||||
import { ViewGroup } from '@/views/types/ViewGroup';
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
|
||||
export const usePersistViewGroupRecords = () => {
|
||||
const { objectMetadataItem } = useObjectMetadataItem({
|
||||
objectNameSingular: CoreObjectNameSingular.ViewGroup,
|
||||
});
|
||||
const apolloClient = useApolloClient();
|
||||
|
||||
const { createOneRecordMutation } = useCreateOneRecordMutation({
|
||||
const { createManyRecords } = useCreateManyRecords({
|
||||
objectNameSingular: CoreObjectNameSingular.ViewGroup,
|
||||
shouldMatchRootQueryFilter: true,
|
||||
});
|
||||
|
||||
const { updateOneRecordMutation } = useUpdateOneRecordMutation({
|
||||
objectNameSingular: CoreObjectNameSingular.ViewGroup,
|
||||
});
|
||||
|
||||
const { deleteOneRecordMutation } = useDeleteOneRecordMutation({
|
||||
const { destroyManyRecords } = useDestroyManyRecords({
|
||||
objectNameSingular: CoreObjectNameSingular.ViewGroup,
|
||||
});
|
||||
|
||||
const { objectMetadataItems } = useObjectMetadataItems();
|
||||
|
||||
const apolloClient = useApolloClient();
|
||||
|
||||
const createViewGroupRecords = useCallback(
|
||||
(viewGroupsToCreate: ViewGroup[], view: GraphQLView) => {
|
||||
if (!viewGroupsToCreate.length) return;
|
||||
|
||||
return Promise.all(
|
||||
viewGroupsToCreate.map((viewGroup) =>
|
||||
apolloClient.mutate({
|
||||
mutation: createOneRecordMutation,
|
||||
variables: {
|
||||
input: {
|
||||
fieldMetadataId: viewGroup.fieldMetadataId,
|
||||
viewId: view.id,
|
||||
isVisible: viewGroup.isVisible,
|
||||
position: viewGroup.position,
|
||||
id: v4(),
|
||||
fieldValue: viewGroup.fieldValue,
|
||||
},
|
||||
},
|
||||
update: (cache, { data }) => {
|
||||
const record = data?.['createViewGroup'];
|
||||
if (!record) return;
|
||||
|
||||
triggerCreateRecordsOptimisticEffect({
|
||||
cache,
|
||||
objectMetadataItem,
|
||||
recordsToCreate: [record],
|
||||
objectMetadataItems,
|
||||
});
|
||||
},
|
||||
}),
|
||||
),
|
||||
return createManyRecords(
|
||||
viewGroupsToCreate.map((viewGroup) => ({
|
||||
...viewGroup,
|
||||
view: {
|
||||
id: view.id,
|
||||
},
|
||||
})),
|
||||
);
|
||||
},
|
||||
[
|
||||
apolloClient,
|
||||
createOneRecordMutation,
|
||||
objectMetadataItem,
|
||||
objectMetadataItems,
|
||||
],
|
||||
[createManyRecords],
|
||||
);
|
||||
|
||||
const updateViewGroupRecords = useCallback(
|
||||
@ -95,7 +61,8 @@ export const usePersistViewGroupRecords = () => {
|
||||
|
||||
const mutationResults = await Promise.all(mutationPromises);
|
||||
|
||||
// FixMe: Using triggerCreateRecordsOptimisticEffect is actaully causing multiple records to be created
|
||||
// FixMe: Using useUpdateOneRecord hook that call triggerUpdateRecordsOptimisticEffect is actaully causing multiple records to be created
|
||||
// This is a temporary fix
|
||||
mutationResults.forEach(({ data }) => {
|
||||
const record = data?.['updateViewGroup'];
|
||||
|
||||
@ -120,33 +87,11 @@ export const usePersistViewGroupRecords = () => {
|
||||
async (viewGroupsToDelete: ViewGroup[]) => {
|
||||
if (!viewGroupsToDelete.length) return;
|
||||
|
||||
const mutationPromises = viewGroupsToDelete.map((viewGroup) =>
|
||||
apolloClient.mutate<{ deleteViewGroup: ViewGroup }>({
|
||||
mutation: deleteOneRecordMutation,
|
||||
variables: {
|
||||
idToDelete: viewGroup.id,
|
||||
},
|
||||
// Avoid cache being updated with stale data
|
||||
fetchPolicy: 'no-cache',
|
||||
}),
|
||||
return destroyManyRecords(
|
||||
viewGroupsToDelete.map((viewGroup) => viewGroup.id),
|
||||
);
|
||||
|
||||
const mutationResults = await Promise.all(mutationPromises);
|
||||
|
||||
mutationResults.forEach(({ data }) => {
|
||||
const record = data?.['deleteViewGroup'];
|
||||
|
||||
if (!record) return;
|
||||
|
||||
apolloClient.cache.evict({
|
||||
id: apolloClient.cache.identify({
|
||||
__typename: 'ViewGroup',
|
||||
id: record.id,
|
||||
}),
|
||||
});
|
||||
});
|
||||
},
|
||||
[apolloClient, deleteOneRecordMutation],
|
||||
[destroyManyRecords],
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
@ -14,7 +14,6 @@ export enum FeatureFlagKey {
|
||||
IsMicrosoftSyncEnabled = 'IS_MICROSOFT_SYNC_ENABLED',
|
||||
IsAdvancedFiltersEnabled = 'IS_ADVANCED_FILTERS_ENABLED',
|
||||
IsAggregateQueryEnabled = 'IS_AGGREGATE_QUERY_ENABLED',
|
||||
IsViewGroupsEnabled = 'IS_VIEW_GROUPS_ENABLED',
|
||||
IsPageHeaderV2Enabled = 'IS_PAGE_HEADER_V2_ENABLED',
|
||||
IsCrmMigrationEnabled = 'IS_CRM_MIGRATION_ENABLED',
|
||||
IsJsonFilterEnabled = 'IS_JSON_FILTER_ENABLED',
|
||||
|
||||
Reference in New Issue
Block a user