Skip to content

Commit

Permalink
feat: view group actions
Browse files Browse the repository at this point in the history
  • Loading branch information
magrinj committed Oct 15, 2024
1 parent d28675c commit 7d27a03
Show file tree
Hide file tree
Showing 13 changed files with 306 additions and 161 deletions.
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import styled from '@emotion/styled';
import { useCallback, useContext, useRef } from 'react';
import { useCallback, useRef } from 'react';

import { RecordBoardColumnContext } from '@/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext';
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { useRecordGroupActions } from '@/object-record/record-group/hooks/useRecordGroupActions';

const StyledMenuContainer = styled.div`
position: absolute;
Expand All @@ -25,6 +25,8 @@ export const RecordBoardColumnDropdownMenu = ({
}: RecordBoardColumnDropdownMenuProps) => {
const boardColumnMenuRef = useRef<HTMLDivElement>(null);

const recordGroupActions = useRecordGroupActions();

const closeMenu = useCallback(() => {
onClose();
}, [onClose]);
Expand All @@ -34,13 +36,11 @@ export const RecordBoardColumnDropdownMenu = ({
callback: closeMenu,
});

const { columnDefinition } = useContext(RecordBoardColumnContext);

return (
<StyledMenuContainer ref={boardColumnMenuRef}>
<DropdownMenu data-select-disable>
<DropdownMenuItemsContainer>
{columnDefinition.actions.map((action) => (
{recordGroupActions.map((action) => (
<MenuItem
key={action.id}
onClick={() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,13 +137,11 @@ export const RecordBoardColumnHeader = () => {
<StyledRightContainer>
{isHeaderHovered && (
<StyledHeaderActions>
{columnDefinition.actions.length > 0 && (
<LightIconButton
accent="tertiary"
Icon={IconDotsVertical}
onClick={handleBoardColumnMenuOpen}
/>
)}
<LightIconButton
accent="tertiary"
Icon={IconDotsVertical}
onClick={handleBoardColumnMenuOpen}
/>

<LightIconButton
accent="tertiary"
Expand All @@ -155,7 +153,7 @@ export const RecordBoardColumnHeader = () => {
</StyledRightContainer>
</StyledHeaderContainer>
</StyledHeader>
{isBoardColumnMenuOpen && columnDefinition.actions.length > 0 && (
{isBoardColumnMenuOpen && (
<RecordBoardColumnDropdownMenu
onClose={handleBoardColumnMenuClose}
stageId={columnDefinition.id}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { getObjectSlug } from '@/object-metadata/utils/getObjectSlug';
import { RecordBoardColumnContext } from '@/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext';
import { useRecordGroupStates } from '@/object-record/record-group/hooks/useRecordGroupStates';
import { useRecordGroupVisibility } from '@/object-record/record-group/hooks/useRecordGroupVisibility';
import { RecordGroupAction } from '@/object-record/record-group/types/RecordGroupActions';
import { RecordIndexRootPropsContext } from '@/object-record/record-index/contexts/RecordIndexRootPropsContext';
import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState';
import { useCallback, useContext, useMemo } from 'react';
import { useNavigate, useLocation } from 'react-router-dom';
import { useSetRecoilState } from 'recoil';
import { IconEyeOff, IconSettings } from 'twenty-ui';

export const useRecordGroupActions = () => {
const navigate = useNavigate();
const location = useLocation();

const { objectNameSingular, recordIndexId } = useContext(
RecordIndexRootPropsContext,
);

const { columnDefinition: recordGroupDefinition } = useContext(
RecordBoardColumnContext,
);

const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular,
});

const { viewGroupFieldMetadataItem } = useRecordGroupStates({
objectNameSingular,
viewBarId: recordIndexId,
});

const { handleVisibilityChange: handleRecordGroupVisibilityChange } =
useRecordGroupVisibility({
viewBarId: recordIndexId,
});

const setNavigationMemorizedUrl = useSetRecoilState(
navigationMemorizedUrlState,
);

const navigateToSelectSettings = useCallback(() => {
setNavigationMemorizedUrl(location.pathname + location.search);
navigate(
`/settings/objects/${getObjectSlug(objectMetadataItem)}/${viewGroupFieldMetadataItem?.name}`,
);
}, [
setNavigationMemorizedUrl,
location.pathname,
location.search,
navigate,
objectMetadataItem,
viewGroupFieldMetadataItem?.name,
]);

const recordGroupActions: RecordGroupAction[] = useMemo(
() => [
{
id: 'edit',
label: 'Edit',
icon: IconSettings,
position: 0,
callback: () => {
navigateToSelectSettings();
},
},
{
id: 'hide',
label: 'Hide',
icon: IconEyeOff,
position: 1,
callback: () => {
handleRecordGroupVisibilityChange(recordGroupDefinition);
},
},
],
[
handleRecordGroupVisibilityChange,
navigateToSelectSettings,
recordGroupDefinition,
],
);

return recordGroupActions;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { OnDragEndResponder } from '@hello-pangea/dnd';
import { useCallback } from 'react';
import { useSetRecoilState } from 'recoil';

import { moveArrayItem } from '~/utils/array/moveArrayItem';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
import { useSaveCurrentViewGroups } from '@/views/hooks/useSaveCurrentViewGroups';
import { recordIndexGroupDefinitionsState } from '@/object-record/record-index/states/recordIndexGroupDefinitionsState';
import { mapGroupDefinitionsToViewGroups } from '@/views/utils/mapGroupDefinitionsToViewGroups';
import { useRecordGroupStates } from '@/object-record/record-group/hooks/useRecordGroupStates';

type UseRecordGroupHandlersParams = {
objectNameSingular: string;
viewBarId: string;
};

export const useRecordGroupReoder = ({
objectNameSingular,
viewBarId,
}: UseRecordGroupHandlersParams) => {
const setRecordIndexGroupDefinitions = useSetRecoilState(
recordIndexGroupDefinitionsState,
);

const { visibleRecordGroups } = useRecordGroupStates({
objectNameSingular,
viewBarId,
});

const { saveViewGroups } = useSaveCurrentViewGroups(viewBarId);

const handleOrderChange: OnDragEndResponder = useCallback(
(result) => {
if (!result.destination) {
return;
}

const reorderedVisibleBoardGroups = moveArrayItem(visibleRecordGroups, {
fromIndex: result.source.index - 1,
toIndex: result.destination.index - 1,
});

if (isDeeplyEqual(visibleRecordGroups, reorderedVisibleBoardGroups))
return;

const updatedGroups = [...reorderedVisibleBoardGroups].map(
(group, index) => ({ ...group, position: index }),
);

setRecordIndexGroupDefinitions(updatedGroups);
saveViewGroups(mapGroupDefinitionsToViewGroups(updatedGroups));
},
[saveViewGroups, setRecordIndexGroupDefinitions, visibleRecordGroups],
);

return {
visibleRecordGroups,
handleOrderChange,
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { useMemo } from 'react';
import { useRecoilValue } from 'recoil';

import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { recordIndexGroupDefinitionsState } from '@/object-record/record-index/states/recordIndexGroupDefinitionsState';

type UseRecordGroupStatesParams = {
objectNameSingular: string;
viewBarId: string;
};

export const useRecordGroupStates = ({
objectNameSingular,
}: UseRecordGroupStatesParams) => {
const recordIndexGroupDefinitions = useRecoilValue(
recordIndexGroupDefinitionsState,
);

const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular,
});

const viewGroupFieldMetadataItem = useMemo(() => {
if (recordIndexGroupDefinitions.length === 0) return null;
// We're assuming that all groups have the same fieldMetadataId for now
const fieldMetadataId =
'fieldMetadataId' in recordIndexGroupDefinitions[0]
? recordIndexGroupDefinitions[0].fieldMetadataId
: null;

if (!fieldMetadataId) return null;

return objectMetadataItem.fields.find(
(field) => field.id === fieldMetadataId,
);
}, [objectMetadataItem, recordIndexGroupDefinitions]);

const visibleRecordGroups = useMemo(
() =>
recordIndexGroupDefinitions
.filter((boardGroup) => boardGroup.isVisible)
.sort(
(boardGroupA, boardGroupB) =>
boardGroupA.position - boardGroupB.position,
),
[recordIndexGroupDefinitions],
);

const hiddenRecordGroups = useMemo(
() =>
recordIndexGroupDefinitions
.filter((boardGroup) => !boardGroup.isVisible)
.map((boardGroup) => ({
...boardGroup,
isVisible: false,
})),
[recordIndexGroupDefinitions],
);

return {
hiddenRecordGroups,
visibleRecordGroups,
viewGroupFieldMetadataItem,
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { useCallback } from 'react';
import { useRecoilState } from 'recoil';

import { useSaveCurrentViewGroups } from '@/views/hooks/useSaveCurrentViewGroups';
import { recordIndexGroupDefinitionsState } from '@/object-record/record-index/states/recordIndexGroupDefinitionsState';
import { mapGroupDefinitionsToViewGroups } from '@/views/utils/mapGroupDefinitionsToViewGroups';
import { RecordGroupDefinition } from '@/object-record/record-group/types/RecordGroupDefinition';

type UseRecordGroupVisibilityParams = {
viewBarId: string;
};

export const useRecordGroupVisibility = ({
viewBarId,
}: UseRecordGroupVisibilityParams) => {
const [recordIndexGroupDefinitions, setRecordIndexGroupDefinitions] =
useRecoilState(recordIndexGroupDefinitionsState);

const { saveViewGroups } = useSaveCurrentViewGroups(viewBarId);

const handleVisibilityChange = useCallback(
async (updatedGroupDefinition: RecordGroupDefinition) => {
const updatedGroupsDefinitions = recordIndexGroupDefinitions.map(
(groupDefinition) =>
groupDefinition.id === updatedGroupDefinition.id
? {
...groupDefinition,
isVisible: !groupDefinition.isVisible,
}
: groupDefinition,
);

setRecordIndexGroupDefinitions(updatedGroupsDefinitions);

saveViewGroups(mapGroupDefinitionsToViewGroups(updatedGroupsDefinitions));
},
[
recordIndexGroupDefinitions,
setRecordIndexGroupDefinitions,
saveViewGroups,
],
);

return {
handleVisibilityChange,
};
};
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { RecordGroupAction } from '@/object-record/record-group/types/RecordGroupActions';
import { ThemeColor } from 'twenty-ui';

export const enum RecordGroupDefinitionType {
Expand All @@ -13,7 +12,6 @@ export type RecordGroupDefinitionNoValue = {
position: number;
value: null;
isVisible: boolean;
actions: RecordGroupAction[];
};

export type RecordGroupDefinitionValue = {
Expand All @@ -25,7 +23,6 @@ export type RecordGroupDefinitionValue = {
color: ThemeColor;
position: number;
isVisible: boolean;
actions: RecordGroupAction[];
};

export type RecordGroupDefinition =
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useEffect } from 'react';
import { useCallback, useEffect } from 'react';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { useNavigate, useLocation } from 'react-router-dom';

import { contextStoreCurrentObjectMetadataIdState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdState';
import { contextStoreTargetedRecordIdsState } from '@/context-store/states/contextStoreTargetedRecordIdsState';
Expand All @@ -12,6 +13,7 @@ import { recordIndexKanbanFieldMetadataIdState } from '@/object-record/record-in
import { FieldMetadataType } from '~/generated-metadata/graphql';
import { isDefined } from '~/utils/isDefined';
import { recordIndexGroupDefinitionsState } from '@/object-record/record-index/states/recordIndexGroupDefinitionsState';
import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState';

type RecordIndexBoardDataLoaderEffectProps = {
objectNameSingular: string;
Expand Down
Loading

0 comments on commit 7d27a03

Please sign in to comment.