Skip to content

Commit

Permalink
added button in nav bar for kanban view (twentyhq#6829)
Browse files Browse the repository at this point in the history
@Bonapara 
Addressing issue twentyhq#6783.
 
I tried to achieve the exact behavior you were looking for, but I
couldn't get the dropdown to render correctly in that specific column.
I'd love some help to make sure it's working as expected! 😊
Most of the logic is shared with the `useHandleOpportunity` and
`useAddNewCard` hooks, which could be refactored to reduce code debt.
Also, please go harsh with the review because I know there's a lot of
code cleaning required.
I also agree with Charles's point in [this
comment](twentyhq#6783 (comment)).
Thanks :)


https://github.com/user-attachments/assets/bccdb3f1-3946-4e22-b9a4-b7496ef134c9
  • Loading branch information
ehconitin authored Sep 10, 2024
1 parent fbe9e2c commit 05d70b0
Show file tree
Hide file tree
Showing 6 changed files with 309 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { useParams } from 'react-router-dom';
import { useRecoilValue } from 'recoil';
import { useIcons } from 'twenty-ui';

import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
import { RecordIndexPageKanbanAddButton } from '@/object-record/record-index/components/RecordIndexPageKanbanAddButton';
import { recordIndexViewTypeState } from '@/object-record/record-index/states/recordIndexViewTypeState';
import { PageAddButton } from '@/ui/layout/page/PageAddButton';
import { PageHeader } from '@/ui/layout/page/PageHeader';
Expand All @@ -12,13 +12,15 @@ import { capitalize } from '~/utils/string/capitalize';

type RecordIndexPageHeaderProps = {
createRecord: () => void;
recordIndexId: string;
objectNamePlural: string;
};

export const RecordIndexPageHeader = ({
createRecord,
recordIndexId,
objectNamePlural,
}: RecordIndexPageHeaderProps) => {
const objectNamePlural = useParams().objectNamePlural ?? '';

const { findObjectMetadataItemByNamePlural } =
useFilteredObjectMetadataItems();

Expand All @@ -32,7 +34,7 @@ export const RecordIndexPageHeader = ({

const recordIndexViewType = useRecoilValue(recordIndexViewTypeState);

const canAddRecord =
const isTable =
recordIndexViewType === ViewType.Table && !objectMetadataItem?.isRemote;

const pageHeaderTitle =
Expand All @@ -41,7 +43,14 @@ export const RecordIndexPageHeader = ({
return (
<PageHeader title={pageHeaderTitle} Icon={Icon}>
<PageHotkeysEffect onAddButtonClick={createRecord} />
{canAddRecord && <PageAddButton onClick={createRecord} />}
{isTable ? (
<PageAddButton onClick={createRecord} />
) : (
<RecordIndexPageKanbanAddButton
recordIndexId={recordIndexId}
objectNamePlural={objectNamePlural}
/>
)}
</PageHeader>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useRecordBoardStates } from '@/object-record/record-board/hooks/internal/useRecordBoardStates';
import { RecordBoardColumnDefinition } from '@/object-record/record-board/types/RecordBoardColumnDefinition';
import { RecordIndexPageKanbanAddMenuItem } from '@/object-record/record-index/components/RecordIndexPageKanbanAddMenuItem';
import { useRecordIndexPageKanbanAddButton } from '@/object-record/record-index/hooks/useRecordIndexPageKanbanAddButton';
import { SingleEntitySelect } from '@/object-record/relation-picker/components/SingleEntitySelect';
import { useEntitySelectSearch } from '@/object-record/relation-picker/hooks/useEntitySelectSearch';
import { EntityForSelect } from '@/object-record/relation-picker/types/EntityForSelect';
import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope';
import { IconButton } from '@/ui/input/button/components/IconButton';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
import styled from '@emotion/styled';
import { useCallback, useState } from 'react';
import { useRecoilValue } from 'recoil';
import { IconPlus, isDefined } from 'twenty-ui';

const StyledDropdownMenuItemsContainer = styled(DropdownMenuItemsContainer)`
width: 100%;
`;

const StyledDropDownMenu = styled(DropdownMenu)`
width: 200px;
`;

type RecordIndexPageKanbanAddButtonProps = {
recordIndexId: string;
objectNamePlural: string;
};

export const RecordIndexPageKanbanAddButton = ({
recordIndexId,
objectNamePlural,
}: RecordIndexPageKanbanAddButtonProps) => {
const dropdownId = `record-index-page-add-button-dropdown`;
const [isSelectingCompany, setIsSelectingCompany] = useState(false);
const [selectedColumnDefinition, setSelectedColumnDefinition] =
useState<RecordBoardColumnDefinition>();

const { columnIdsState } = useRecordBoardStates(recordIndexId);
const columnIds = useRecoilValue(columnIdsState);

const {
setHotkeyScopeAndMemorizePreviousScope,
goBackToPreviousHotkeyScope,
} = usePreviousHotkeyScope();
const { resetSearchFilter } = useEntitySelectSearch({
relationPickerScopeId: 'relation-picker',
});

const { closeDropdown } = useDropdown(dropdownId);

const {
selectFieldMetadataItem,
isOpportunity,
createOpportunity,
createRecordWithoutCompany,
} = useRecordIndexPageKanbanAddButton({
objectNamePlural,
});

const handleItemClick = useCallback(
(columnDefinition: RecordBoardColumnDefinition) => {
if (isOpportunity) {
setIsSelectingCompany(true);
setSelectedColumnDefinition(columnDefinition);
setHotkeyScopeAndMemorizePreviousScope(
RelationPickerHotkeyScope.RelationPicker,
);
} else {
createRecordWithoutCompany(columnDefinition);
closeDropdown();
}
},
[
isOpportunity,
createRecordWithoutCompany,
setHotkeyScopeAndMemorizePreviousScope,
closeDropdown,
],
);

const handleEntitySelect = useCallback(
(company?: EntityForSelect) => {
setIsSelectingCompany(false);
goBackToPreviousHotkeyScope();
resetSearchFilter();
if (isDefined(company) && isDefined(selectedColumnDefinition)) {
createOpportunity(company, selectedColumnDefinition);
}
closeDropdown();
},
[
createOpportunity,
goBackToPreviousHotkeyScope,
resetSearchFilter,
selectedColumnDefinition,
closeDropdown,
],
);

const handleCancel = useCallback(() => {
resetSearchFilter();
goBackToPreviousHotkeyScope();
setIsSelectingCompany(false);
}, [goBackToPreviousHotkeyScope, resetSearchFilter]);

if (!selectFieldMetadataItem) {
return null;
}

return (
<Dropdown
dropdownMenuWidth="200px"
dropdownPlacement="bottom-start"
clickableComponent={
<IconButton
Icon={IconPlus}
dataTestId="add-button"
size="medium"
variant="secondary"
accent="default"
ariaLabel="Add"
/>
}
dropdownId={dropdownId}
dropdownComponents={
<StyledDropDownMenu>
{isOpportunity && isSelectingCompany ? (
<SingleEntitySelect
disableBackgroundBlur
onCancel={handleCancel}
onEntitySelected={handleEntitySelect}
relationObjectNameSingular={CoreObjectNameSingular.Company}
relationPickerScopeId="relation-picker"
selectedRelationRecordIds={[]}
/>
) : (
<StyledDropdownMenuItemsContainer>
{columnIds.map((columnId) => (
<RecordIndexPageKanbanAddMenuItem
key={columnId}
columnId={columnId}
recordIndexId={recordIndexId}
onItemClick={handleItemClick}
/>
))}
</StyledDropdownMenuItemsContainer>
)}
</StyledDropDownMenu>
}
dropdownHotkeyScope={{ scope: dropdownId }}
/>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { RecordBoardColumnDefinitionType } from '@/object-record/record-board/types/RecordBoardColumnDefinition';
import { useRecordIndexPageKanbanAddMenuItem } from '@/object-record/record-index/hooks/useRecordIndexPageKanbanAddMenuItem';
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
import styled from '@emotion/styled';
import { Tag } from 'twenty-ui';

const StyledMenuItem = styled(MenuItem)`
width: 200px;
`;

type RecordIndexPageKanbanAddMenuItemProps = {
columnId: string;
recordIndexId: string;
onItemClick: (columnDefinition: any) => void;
};

export const RecordIndexPageKanbanAddMenuItem = ({
columnId,
recordIndexId,
onItemClick,
}: RecordIndexPageKanbanAddMenuItemProps) => {
const { columnDefinition } = useRecordIndexPageKanbanAddMenuItem(
recordIndexId,
columnId,
);
if (!columnDefinition) {
return null;
}

return (
<StyledMenuItem
text={
<Tag
variant={
columnDefinition.type === RecordBoardColumnDefinitionType.Value
? 'solid'
: 'outline'
}
color={
columnDefinition.type === RecordBoardColumnDefinitionType.Value
? columnDefinition.color
: 'transparent'
}
text={columnDefinition.title}
weight={
columnDefinition.type === RecordBoardColumnDefinitionType.Value
? 'regular'
: 'medium'
}
/>
}
onClick={() => onItemClick(columnDefinition)}
/>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
import { RecordBoardColumnDefinition } from '@/object-record/record-board/types/RecordBoardColumnDefinition';
import { recordIndexKanbanFieldMetadataIdState } from '@/object-record/record-index/states/recordIndexKanbanFieldMetadataIdState';
import { EntityForSelect } from '@/object-record/relation-picker/types/EntityForSelect';
import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-ui';

type useRecordIndexPageKanbanAddButtonProps = {
objectNamePlural: string;
};

export const useRecordIndexPageKanbanAddButton = ({
objectNamePlural,
}: useRecordIndexPageKanbanAddButtonProps) => {
const { objectNameSingular } = useObjectNameSingularFromPlural({
objectNamePlural,
});
const { objectMetadataItem } = useObjectMetadataItem({ objectNameSingular });

const recordIndexKanbanFieldMetadataId = useRecoilValue(
recordIndexKanbanFieldMetadataIdState,
);
const { createOneRecord } = useCreateOneRecord({ objectNameSingular });

const selectFieldMetadataItem = objectMetadataItem.fields.find(
(field) => field.id === recordIndexKanbanFieldMetadataId,
);
const isOpportunity =
objectMetadataItem.nameSingular === CoreObjectNameSingular.Opportunity;

const createOpportunity = (
company: EntityForSelect,
columnDefinition: RecordBoardColumnDefinition,
) => {
if (isDefined(selectFieldMetadataItem)) {
createOneRecord({
name: company.name,
companyId: company.id,
position: 'first',
[selectFieldMetadataItem.name]: columnDefinition?.value,
});
}
};

const createRecordWithoutCompany = (
columnDefinition: RecordBoardColumnDefinition,
) => {
if (isDefined(selectFieldMetadataItem)) {
createOneRecord({
[selectFieldMetadataItem.name]: columnDefinition?.value,
position: 'first',
});
}
};

return {
selectFieldMetadataItem,
isOpportunity,
createOpportunity,
createRecordWithoutCompany,
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { useRecordBoardStates } from '@/object-record/record-board/hooks/internal/useRecordBoardStates';
import { useRecoilValue } from 'recoil';

export const useRecordIndexPageKanbanAddMenuItem = (
recordIndexId: string,
columnId: string,
) => {
const { columnsFamilySelector } = useRecordBoardStates(recordIndexId);
const columnDefinition = useRecoilValue(columnsFamilySelector(columnId));

return { columnDefinition };
};
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,11 @@ export const RecordIndexPage = () => {
return (
<PageContainer>
<PageTitle title={`${capitalize(objectNamePlural)}`} />
<RecordIndexPageHeader createRecord={handleAddButtonClick} />
<RecordIndexPageHeader
createRecord={handleAddButtonClick}
recordIndexId={recordIndexId}
objectNamePlural={objectNamePlural}
/>
<PageBody>
<StyledIndexContainer>
<RecordIndexContainer
Expand Down

0 comments on commit 05d70b0

Please sign in to comment.