Kanban card creation followup (#7285)

@Bonapara #7002
@FelixMalfait  #6316 
;)

Naming could be improved, do let me know!


https://github.com/user-attachments/assets/b10c9120-644d-4943-bc65-ec0d62f9986f
This commit is contained in:
nitin
2024-10-03 21:20:54 +05:30
committed by GitHub
parent 5f9435c718
commit 04579144ca
12 changed files with 199 additions and 282 deletions

View File

@@ -141,10 +141,6 @@ const StyledRecordInlineCellPlaceholder = styled.div`
height: 24px; height: 24px;
`; `;
const StyledRecordInlineCell = styled(RecordInlineCell)`
height: 24px;
`;
export const RecordBoardCard = ({ export const RecordBoardCard = ({
isCreating = false, isCreating = false,
onCreateSuccess, onCreateSuccess,
@@ -348,7 +344,7 @@ export const RecordBoardCard = ({
}} }}
> >
{inView ? ( {inView ? (
<StyledRecordInlineCell /> <RecordInlineCell />
) : ( ) : (
<StyledRecordInlineCellPlaceholder /> <StyledRecordInlineCellPlaceholder />
)} )}

View File

@@ -12,6 +12,7 @@ import { RecordBoardColumnFetchMoreLoader } from '@/object-record/record-board/r
import { RecordBoardColumnNewButton } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnNewButton'; import { RecordBoardColumnNewButton } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnNewButton';
import { RecordBoardColumnNewOpportunityButton } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnNewOpportunityButton'; import { RecordBoardColumnNewOpportunityButton } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnNewOpportunityButton';
import { RecordBoardColumnContext } from '@/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext'; import { RecordBoardColumnContext } from '@/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext';
import { useIsOpportunitiesCompanyFieldDisabled } from '@/object-record/record-board/record-board-column/hooks/useIsOpportunitiesCompanyFieldDisabled';
import { getNumberOfCardsPerColumnForSkeletonLoading } from '@/object-record/record-board/record-board-column/utils/getNumberOfCardsPerColumnForSkeletonLoading'; import { getNumberOfCardsPerColumnForSkeletonLoading } from '@/object-record/record-board/record-board-column/utils/getNumberOfCardsPerColumnForSkeletonLoading';
import { isRecordIndexBoardColumnLoadingFamilyState } from '@/object-record/states/isRecordBoardColumnLoadingFamilyState'; import { isRecordIndexBoardColumnLoadingFamilyState } from '@/object-record/states/isRecordBoardColumnLoadingFamilyState';
@@ -64,6 +65,8 @@ export const RecordBoardColumnCardsContainer = ({
const numberOfFields = visibleFieldDefinitions.length; const numberOfFields = visibleFieldDefinitions.length;
const isCompactModeActive = useRecoilValue(isCompactModeActiveState); const isCompactModeActive = useRecoilValue(isCompactModeActiveState);
const { isOpportunitiesCompanyFieldDisabled } =
useIsOpportunitiesCompanyFieldDisabled();
return ( return (
<StyledColumnCardsContainer <StyledColumnCardsContainer
@@ -107,8 +110,11 @@ export const RecordBoardColumnCardsContainer = ({
> >
<StyledNewButtonContainer> <StyledNewButtonContainer>
{objectMetadataItem.nameSingular === {objectMetadataItem.nameSingular ===
CoreObjectNameSingular.Opportunity ? ( CoreObjectNameSingular.Opportunity &&
<RecordBoardColumnNewOpportunityButton /> !isOpportunitiesCompanyFieldDisabled ? (
<RecordBoardColumnNewOpportunityButton
columnId={columnDefinition.id}
/>
) : ( ) : (
<RecordBoardColumnNewButton columnId={columnDefinition.id} /> <RecordBoardColumnNewButton columnId={columnDefinition.id} />
)} )}

View File

@@ -7,8 +7,8 @@ import { RecordBoardContext } from '@/object-record/record-board/contexts/Record
import { RecordBoardCard } from '@/object-record/record-board/record-board-card/components/RecordBoardCard'; import { RecordBoardCard } from '@/object-record/record-board/record-board-card/components/RecordBoardCard';
import { RecordBoardColumnDropdownMenu } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnDropdownMenu'; import { RecordBoardColumnDropdownMenu } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnDropdownMenu';
import { RecordBoardColumnContext } from '@/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext'; import { RecordBoardColumnContext } from '@/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext';
import { useAddNewOpportunity } from '@/object-record/record-board/record-board-column/hooks/useAddNewOpportunity';
import { useColumnNewCardActions } from '@/object-record/record-board/record-board-column/hooks/useColumnNewCardActions'; import { useColumnNewCardActions } from '@/object-record/record-board/record-board-column/hooks/useColumnNewCardActions';
import { useIsOpportunitiesCompanyFieldDisabled } from '@/object-record/record-board/record-board-column/hooks/useIsOpportunitiesCompanyFieldDisabled';
import { RecordBoardColumnHotkeyScope } from '@/object-record/record-board/types/BoardColumnHotkeyScope'; import { RecordBoardColumnHotkeyScope } from '@/object-record/record-board/types/BoardColumnHotkeyScope';
import { RecordBoardColumnDefinitionType } from '@/object-record/record-board/types/RecordBoardColumnDefinition'; import { RecordBoardColumnDefinitionType } from '@/object-record/record-board/types/RecordBoardColumnDefinition';
import { SingleEntitySelect } from '@/object-record/relation-picker/components/SingleEntitySelect'; import { SingleEntitySelect } from '@/object-record/relation-picker/components/SingleEntitySelect';
@@ -90,21 +90,18 @@ export const RecordBoardColumnHeader = () => {
const boardColumnTotal = 0; const boardColumnTotal = 0;
const { const {
isCreatingCard, newRecord,
handleAddNewOpportunityClick, handleNewButtonClick,
handleCancel, handleCreateSuccess,
handleEntitySelect,
} = useAddNewOpportunity('first');
const { newRecord, handleNewButtonClick, handleCreateSuccess } = handleEntitySelect,
useColumnNewCardActions(columnDefinition.id); } = useColumnNewCardActions(columnDefinition.id);
const { isOpportunitiesCompanyFieldDisabled } =
useIsOpportunitiesCompanyFieldDisabled();
const isOpportunity = const isOpportunity =
objectMetadataItem.nameSingular === CoreObjectNameSingular.Opportunity; objectMetadataItem.nameSingular === CoreObjectNameSingular.Opportunity &&
!isOpportunitiesCompanyFieldDisabled;
const handleClick = isOpportunity
? handleAddNewOpportunityClick
: () => handleNewButtonClick('first');
return ( return (
<> <>
@@ -152,7 +149,7 @@ export const RecordBoardColumnHeader = () => {
<LightIconButton <LightIconButton
accent="tertiary" accent="tertiary"
Icon={IconPlus} Icon={IconPlus}
onClick={handleClick} onClick={() => handleNewButtonClick('first', isOpportunity)}
/> />
</StyledHeaderActions> </StyledHeaderActions>
)} )}
@@ -165,23 +162,26 @@ export const RecordBoardColumnHeader = () => {
stageId={columnDefinition.id} stageId={columnDefinition.id}
/> />
)} )}
{newRecord?.isCreating && newRecord.position === 'first' && ( {newRecord?.isCreating &&
<RecordBoardCard newRecord.position === 'first' &&
isCreating={true} (newRecord.isOpportunity ? (
onCreateSuccess={() => handleCreateSuccess('first')} <SingleEntitySelect
position="first" disableBackgroundBlur
/> onCancel={() => handleCreateSuccess('first', columnDefinition.id)}
)} onEntitySelected={(company) =>
{isCreatingCard && ( company && handleEntitySelect('first', company)
<SingleEntitySelect }
disableBackgroundBlur relationObjectNameSingular={CoreObjectNameSingular.Company}
onCancel={handleCancel} relationPickerScopeId="relation-picker"
onEntitySelected={handleEntitySelect} selectedRelationRecordIds={[]}
relationObjectNameSingular={CoreObjectNameSingular.Company} />
relationPickerScopeId="relation-picker" ) : (
selectedRelationRecordIds={[]} <RecordBoardCard
/> isCreating={true}
)} onCreateSuccess={() => handleCreateSuccess('first')}
position="first"
/>
))}
</> </>
); );
}; };

View File

@@ -30,7 +30,11 @@ export const RecordBoardColumnNewButton = ({
const { newRecord, handleNewButtonClick, handleCreateSuccess } = const { newRecord, handleNewButtonClick, handleCreateSuccess } =
useColumnNewCardActions(columnId); useColumnNewCardActions(columnId);
if (newRecord.isCreating && newRecord.position === 'last') { if (
newRecord.isCreating &&
newRecord.position === 'last' &&
!newRecord.isOpportunity
) {
return ( return (
<RecordBoardCard <RecordBoardCard
isCreating={true} isCreating={true}
@@ -41,7 +45,7 @@ export const RecordBoardColumnNewButton = ({
} }
return ( return (
<StyledNewButton onClick={() => handleNewButtonClick('last')}> <StyledNewButton onClick={() => handleNewButtonClick('last', false)}>
<IconPlus size={theme.icon.size.md} /> <IconPlus size={theme.icon.size.md} />
New New
</StyledNewButton> </StyledNewButton>

View File

@@ -3,7 +3,7 @@ import styled from '@emotion/styled';
import { IconPlus } from 'twenty-ui'; import { IconPlus } from 'twenty-ui';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useAddNewOpportunity } from '@/object-record/record-board/record-board-column/hooks/useAddNewOpportunity'; import { useColumnNewCardActions } from '@/object-record/record-board/record-board-column/hooks/useColumnNewCardActions';
import { SingleEntitySelect } from '@/object-record/relation-picker/components/SingleEntitySelect'; import { SingleEntitySelect } from '@/object-record/relation-picker/components/SingleEntitySelect';
const StyledButton = styled.button` const StyledButton = styled.button`
@@ -23,27 +23,36 @@ const StyledButton = styled.button`
} }
`; `;
export const RecordBoardColumnNewOpportunityButton = () => { export const RecordBoardColumnNewOpportunityButton = ({
columnId,
}: {
columnId: string;
}) => {
const theme = useTheme(); const theme = useTheme();
const { const {
isCreatingCard, newRecord,
handleAddNewOpportunityClick, handleNewButtonClick,
handleCancel,
handleEntitySelect, handleEntitySelect,
} = useAddNewOpportunity('last'); handleCreateSuccess,
} = useColumnNewCardActions(columnId);
return ( return (
<> <>
{isCreatingCard ? ( {newRecord.isCreating &&
newRecord.position === 'last' &&
newRecord.isOpportunity ? (
<SingleEntitySelect <SingleEntitySelect
disableBackgroundBlur disableBackgroundBlur
onCancel={handleCancel} onCancel={() => handleCreateSuccess('last', columnId, false)}
onEntitySelected={handleEntitySelect} onEntitySelected={(company) =>
company ? handleEntitySelect('last', company) : null
}
relationObjectNameSingular={CoreObjectNameSingular.Company} relationObjectNameSingular={CoreObjectNameSingular.Company}
relationPickerScopeId="relation-picker" relationPickerScopeId="relation-picker"
selectedRelationRecordIds={[]} selectedRelationRecordIds={[]}
/> />
) : ( ) : (
<StyledButton onClick={handleAddNewOpportunityClick}> <StyledButton onClick={() => handleNewButtonClick('last', true)}>
<IconPlus size={theme.icon.size.md} /> <IconPlus size={theme.icon.size.md} />
New New
</StyledButton> </StyledButton>

View File

@@ -1,15 +1,28 @@
import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext'; import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext';
import { RecordBoardColumnContext } from '@/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext'; import { RecordBoardColumnContext } from '@/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext';
import { recordBoardNewRecordByColumnIdSelector } from '@/object-record/record-board/states/selectors/recordBoardNewRecordByColumnIdSelector'; import { recordBoardNewRecordByColumnIdSelector } from '@/object-record/record-board/states/selectors/recordBoardNewRecordByColumnIdSelector';
import { EntityForSelect } from '@/object-record/relation-picker/types/EntityForSelect';
import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope';
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
import { useCallback, useContext } from 'react'; import { useCallback, useContext } from 'react';
import { useRecoilCallback } from 'recoil'; import { RecoilState, useRecoilCallback } from 'recoil';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
type SetFunction = <T>(
recoilVal: RecoilState<T>,
valOrUpdater: T | ((currVal: T) => T),
) => void;
export const useAddNewCard = () => { export const useAddNewCard = () => {
const columnContext = useContext(RecordBoardColumnContext); const columnContext = useContext(RecordBoardColumnContext);
const { createOneRecord, selectFieldMetadataItem } = const { createOneRecord, selectFieldMetadataItem } =
useContext(RecordBoardContext); useContext(RecordBoardContext);
const {
goBackToPreviousHotkeyScope,
setHotkeyScopeAndMemorizePreviousScope,
} = usePreviousHotkeyScope();
const getColumnDefinitionId = useCallback( const getColumnDefinitionId = useCallback(
(columnId?: string) => { (columnId?: string) => {
const columnDefinitionId = columnId || columnContext?.columnDefinition.id; const columnDefinitionId = columnId || columnContext?.columnDefinition.id;
@@ -21,8 +34,13 @@ export const useAddNewCard = () => {
[columnContext], [columnContext],
); );
const addNewCard = useCallback( const addNewItem = useCallback(
(set: any, columnDefinitionId: string, position: 'first' | 'last') => { (
set: SetFunction,
columnDefinitionId: string,
position: 'first' | 'last',
isOpportunity: boolean,
) => {
set( set(
recordBoardNewRecordByColumnIdSelector({ recordBoardNewRecordByColumnIdSelector({
familyKey: columnDefinitionId, familyKey: columnDefinitionId,
@@ -33,6 +51,8 @@ export const useAddNewCard = () => {
columnId: columnDefinitionId, columnId: columnDefinitionId,
isCreating: true, isCreating: true,
position, position,
isOpportunity,
company: null,
}, },
); );
}, },
@@ -44,12 +64,19 @@ export const useAddNewCard = () => {
labelIdentifier: string, labelIdentifier: string,
labelValue: string, labelValue: string,
position: 'first' | 'last', position: 'first' | 'last',
isOpportunity: boolean,
company?: EntityForSelect,
) => { ) => {
if (labelValue !== '') { if (
(isOpportunity && company !== null) ||
(!isOpportunity && labelValue !== '')
) {
createOneRecord({ createOneRecord({
[selectFieldMetadataItem.name]: columnContext?.columnDefinition.value, [selectFieldMetadataItem.name]: columnContext?.columnDefinition.value,
position, position,
[labelIdentifier.toLowerCase()]: labelValue, ...(isOpportunity
? { companyId: company?.id, name: company?.name }
: { [labelIdentifier.toLowerCase()]: labelValue }),
}); });
} }
}, },
@@ -62,18 +89,34 @@ export const useAddNewCard = () => {
labelIdentifier: string, labelIdentifier: string,
labelValue: string, labelValue: string,
position: 'first' | 'last', position: 'first' | 'last',
isOpportunity: boolean,
columnId?: string, columnId?: string,
): void => { ): void => {
const columnDefinitionId = getColumnDefinitionId(columnId); const columnDefinitionId = getColumnDefinitionId(columnId);
addNewCard(set, columnDefinitionId, position); addNewItem(set, columnDefinitionId, position, isOpportunity);
createRecord(labelIdentifier, labelValue, position); if (isOpportunity) {
setHotkeyScopeAndMemorizePreviousScope(
RelationPickerHotkeyScope.RelationPicker,
);
} else {
createRecord(labelIdentifier, labelValue, position, isOpportunity);
}
}, },
[addNewCard, createRecord, getColumnDefinitionId], [
addNewItem,
createRecord,
getColumnDefinitionId,
setHotkeyScopeAndMemorizePreviousScope,
],
); );
const handleCreateSuccess = useRecoilCallback( const handleCreateSuccess = useRecoilCallback(
({ set }) => ({ set }) =>
(position: 'first' | 'last', columnId?: string): void => { (
position: 'first' | 'last',
columnId?: string,
isOpportunity = false,
): void => {
const columnDefinitionId = getColumnDefinitionId(columnId); const columnDefinitionId = getColumnDefinitionId(columnId);
set( set(
recordBoardNewRecordByColumnIdSelector({ recordBoardNewRecordByColumnIdSelector({
@@ -85,10 +128,15 @@ export const useAddNewCard = () => {
columnId: columnDefinitionId, columnId: columnDefinitionId,
isCreating: false, isCreating: false,
position, position,
isOpportunity: Boolean(isOpportunity),
company: null,
}, },
); );
if (isOpportunity === true) {
goBackToPreviousHotkeyScope();
}
}, },
[getColumnDefinitionId], [getColumnDefinitionId, goBackToPreviousHotkeyScope],
); );
const handleCreate = ( const handleCreate = (
@@ -98,7 +146,13 @@ export const useAddNewCard = () => {
onCreateSuccess?: () => void, onCreateSuccess?: () => void,
) => { ) => {
if (labelValue.trim() !== '' && position !== undefined) { if (labelValue.trim() !== '' && position !== undefined) {
handleAddNewCardClick(labelIdentifier, labelValue.trim(), position); handleAddNewCardClick(
labelIdentifier,
labelValue.trim(),
position,
false,
'',
);
onCreateSuccess?.(); onCreateSuccess?.();
} }
}; };
@@ -125,11 +179,25 @@ export const useAddNewCard = () => {
handleCreate(labelIdentifier, labelValue, position, onCreateSuccess); handleCreate(labelIdentifier, labelValue, position, onCreateSuccess);
}; };
const handleEntitySelect = useCallback(
(
position: 'first' | 'last',
company: EntityForSelect,
columnId?: string,
) => {
const columnDefinitionId = getColumnDefinitionId(columnId);
createRecord('', '', position, true, company);
handleCreateSuccess(position, columnDefinitionId, true);
},
[createRecord, handleCreateSuccess, getColumnDefinitionId],
);
return { return {
handleAddNewCardClick, handleAddNewCardClick,
handleCreateSuccess, handleCreateSuccess,
handleCreate, handleCreate,
handleBlur, handleBlur,
handleInputEnter, handleInputEnter,
handleEntitySelect,
}; };
}; };

View File

@@ -1,75 +0,0 @@
import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext';
import { RecordBoardColumnContext } from '@/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext';
import { useIsOpportunitiesCompanyFieldDisabled } from '@/object-record/record-board/record-board-column/hooks/useIsOpportunitiesCompanyFieldDisabled';
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 { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
import { useCallback, useContext, useState } from 'react';
export const useAddNewOpportunity = (position: string) => {
const [isCreatingCard, setIsCreatingCard] = useState(false);
const { columnDefinition } = useContext(RecordBoardColumnContext);
const { createOneRecord, selectFieldMetadataItem } =
useContext(RecordBoardContext);
const {
goBackToPreviousHotkeyScope,
setHotkeyScopeAndMemorizePreviousScope,
} = usePreviousHotkeyScope();
const { resetSearchFilter } = useEntitySelectSearch({
relationPickerScopeId: 'relation-picker',
});
const { isOpportunitiesCompanyFieldDisabled } =
useIsOpportunitiesCompanyFieldDisabled();
const handleEntitySelect = useCallback(
(company?: EntityForSelect) => {
setIsCreatingCard(false);
goBackToPreviousHotkeyScope();
resetSearchFilter();
createOneRecord({
name: company?.name,
companyId: company?.id,
position: position,
[selectFieldMetadataItem.name]: columnDefinition.value,
});
},
[
columnDefinition,
createOneRecord,
goBackToPreviousHotkeyScope,
resetSearchFilter,
selectFieldMetadataItem,
position,
],
);
const handleAddNewOpportunityClick = useCallback(() => {
if (isOpportunitiesCompanyFieldDisabled) {
handleEntitySelect();
} else {
setIsCreatingCard(true);
}
setHotkeyScopeAndMemorizePreviousScope(
RelationPickerHotkeyScope.RelationPicker,
);
}, [
setHotkeyScopeAndMemorizePreviousScope,
isOpportunitiesCompanyFieldDisabled,
handleEntitySelect,
]);
const handleCancel = useCallback(() => {
resetSearchFilter();
goBackToPreviousHotkeyScope();
setIsCreatingCard(false);
}, [goBackToPreviousHotkeyScope, resetSearchFilter]);
return {
isCreatingCard,
handleEntitySelect,
handleAddNewOpportunityClick,
handleCancel,
};
};

View File

@@ -12,7 +12,8 @@ export const useColumnNewCardActions = (columnId: string) => {
(field) => field.isLabelIdentifier, (field) => field.isLabelIdentifier,
); );
const { handleAddNewCardClick, handleCreateSuccess } = useAddNewCard(); const { handleAddNewCardClick, handleCreateSuccess, handleEntitySelect } =
useAddNewCard();
const newRecord = useRecoilValue( const newRecord = useRecoilValue(
recordBoardNewRecordByColumnIdSelector({ recordBoardNewRecordByColumnIdSelector({
@@ -21,11 +22,15 @@ export const useColumnNewCardActions = (columnId: string) => {
}), }),
); );
const handleNewButtonClick = (position: 'first' | 'last') => { const handleNewButtonClick = (
position: 'first' | 'last',
isOpportunity: boolean,
) => {
handleAddNewCardClick( handleAddNewCardClick(
labelIdentifierField?.label ?? '', labelIdentifierField?.label ?? '',
'', '',
position, position,
isOpportunity,
columnId, columnId,
); );
}; };
@@ -34,5 +39,6 @@ export const useColumnNewCardActions = (columnId: string) => {
newRecord, newRecord,
handleNewButtonClick, handleNewButtonClick,
handleCreateSuccess, handleCreateSuccess,
handleEntitySelect,
}; };
}; };

View File

@@ -1,3 +1,4 @@
import { EntityForSelect } from '@/object-record/relation-picker/types/EntityForSelect';
import { createComponentFamilyState } from '@/ui/utilities/state/component-state/utils/createComponentFamilyState'; import { createComponentFamilyState } from '@/ui/utilities/state/component-state/utils/createComponentFamilyState';
export type NewCard = { export type NewCard = {
@@ -5,6 +6,8 @@ export type NewCard = {
columnId: string; columnId: string;
isCreating: boolean; isCreating: boolean;
position: 'first' | 'last'; position: 'first' | 'last';
isOpportunity: boolean;
company: EntityForSelect | null;
}; };
export const recordBoardNewRecordByColumnIdComponentFamilyState = export const recordBoardNewRecordByColumnIdComponentFamilyState =
@@ -15,5 +18,7 @@ export const recordBoardNewRecordByColumnIdComponentFamilyState =
columnId: '', columnId: '',
isCreating: false, isCreating: false,
position: 'last', position: 'last',
isOpportunity: false,
company: null,
}, },
}); });

View File

@@ -1,24 +1,21 @@
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useRecordBoardStates } from '@/object-record/record-board/hooks/internal/useRecordBoardStates'; import { useRecordBoardStates } from '@/object-record/record-board/hooks/internal/useRecordBoardStates';
import { useAddNewCard } from '@/object-record/record-board/record-board-column/hooks/useAddNewCard'; 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 { RecordBoardColumnDefinition } from '@/object-record/record-board/types/RecordBoardColumnDefinition'; import { RecordBoardColumnDefinition } from '@/object-record/record-board/types/RecordBoardColumnDefinition';
import { RecordIndexPageKanbanAddMenuItem } from '@/object-record/record-index/components/RecordIndexPageKanbanAddMenuItem'; import { RecordIndexPageKanbanAddMenuItem } from '@/object-record/record-index/components/RecordIndexPageKanbanAddMenuItem';
import { RecordIndexRootPropsContext } from '@/object-record/record-index/contexts/RecordIndexRootPropsContext'; import { RecordIndexRootPropsContext } from '@/object-record/record-index/contexts/RecordIndexRootPropsContext';
import { useRecordIndexPageKanbanAddButton } from '@/object-record/record-index/hooks/useRecordIndexPageKanbanAddButton'; import { recordIndexKanbanFieldMetadataIdState } from '@/object-record/record-index/states/recordIndexKanbanFieldMetadataIdState';
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 { IconButton } from '@/ui/input/button/components/IconButton';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu'; import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useCallback, useContext, useState } from 'react'; import { useCallback, useContext } from 'react';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { IconPlus, isDefined } from 'twenty-ui'; import { IconPlus } from 'twenty-ui';
const StyledDropdownMenuItemsContainer = styled(DropdownMenuItemsContainer)` const StyledDropdownMenuItemsContainer = styled(DropdownMenuItemsContainer)`
width: 100%; width: 100%;
@@ -30,13 +27,21 @@ const StyledDropDownMenu = styled(DropdownMenu)`
export const RecordIndexPageKanbanAddButton = () => { export const RecordIndexPageKanbanAddButton = () => {
const dropdownId = `record-index-page-add-button-dropdown`; const dropdownId = `record-index-page-add-button-dropdown`;
const [isSelectingCompany, setIsSelectingCompany] = useState(false);
const [selectedColumnDefinition, setSelectedColumnDefinition] =
useState<RecordBoardColumnDefinition>();
const { recordIndexId, objectNamePlural } = useContext( const { recordIndexId, objectNameSingular } = useContext(
RecordIndexRootPropsContext, RecordIndexRootPropsContext,
); );
const { objectMetadataItem } = useObjectMetadataItem({ objectNameSingular });
const recordIndexKanbanFieldMetadataId = useRecoilValue(
recordIndexKanbanFieldMetadataIdState,
);
const selectFieldMetadataItem = objectMetadataItem.fields.find(
(field) => field.id === recordIndexKanbanFieldMetadataId,
);
const isOpportunity =
objectMetadataItem.nameSingular === CoreObjectNameSingular.Opportunity;
const { columnIdsState, visibleFieldDefinitionsState } = const { columnIdsState, visibleFieldDefinitionsState } =
useRecordBoardStates(recordIndexId); useRecordBoardStates(recordIndexId);
@@ -48,73 +53,32 @@ export const RecordIndexPageKanbanAddButton = () => {
(field) => field.isLabelIdentifier, (field) => field.isLabelIdentifier,
); );
const {
setHotkeyScopeAndMemorizePreviousScope,
goBackToPreviousHotkeyScope,
} = usePreviousHotkeyScope();
const { resetSearchFilter } = useEntitySelectSearch({
relationPickerScopeId: 'relation-picker',
});
const { closeDropdown } = useDropdown(dropdownId); const { closeDropdown } = useDropdown(dropdownId);
const { isOpportunitiesCompanyFieldDisabled } =
const { selectFieldMetadataItem, isOpportunity, createOpportunity } = useIsOpportunitiesCompanyFieldDisabled();
useRecordIndexPageKanbanAddButton({
objectNamePlural,
});
const { handleAddNewCardClick } = useAddNewCard(); const { handleAddNewCardClick } = useAddNewCard();
const handleItemClick = useCallback( const handleItemClick = useCallback(
(columnDefinition: RecordBoardColumnDefinition) => { (columnDefinition: RecordBoardColumnDefinition) => {
if (isOpportunity) { const isOpportunityEnabled =
setIsSelectingCompany(true); isOpportunity && !isOpportunitiesCompanyFieldDisabled;
setSelectedColumnDefinition(columnDefinition); handleAddNewCardClick(
setHotkeyScopeAndMemorizePreviousScope( labelIdentifierField?.label ?? '',
RelationPickerHotkeyScope.RelationPicker, '',
); 'first',
} else { isOpportunityEnabled,
handleAddNewCardClick( columnDefinition.id,
labelIdentifierField?.label ?? '', );
'', closeDropdown();
'first',
columnDefinition.id,
);
closeDropdown();
}
}, },
[ [
isOpportunity, isOpportunity,
handleAddNewCardClick, handleAddNewCardClick,
setHotkeyScopeAndMemorizePreviousScope,
closeDropdown, closeDropdown,
labelIdentifierField, labelIdentifierField,
isOpportunitiesCompanyFieldDisabled,
], ],
); );
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) { if (!selectFieldMetadataItem) {
return null; return null;
@@ -137,27 +101,16 @@ export const RecordIndexPageKanbanAddButton = () => {
dropdownId={dropdownId} dropdownId={dropdownId}
dropdownComponents={ dropdownComponents={
<StyledDropDownMenu> <StyledDropDownMenu>
{isOpportunity && isSelectingCompany ? ( <StyledDropdownMenuItemsContainer>
<SingleEntitySelect {columnIds.map((columnId) => (
disableBackgroundBlur <RecordIndexPageKanbanAddMenuItem
onCancel={handleCancel} key={columnId}
onEntitySelected={handleEntitySelect} columnId={columnId}
relationObjectNameSingular={CoreObjectNameSingular.Company} recordIndexId={recordIndexId}
relationPickerScopeId="relation-picker" onItemClick={handleItemClick}
selectedRelationRecordIds={[]} />
/> ))}
) : ( </StyledDropdownMenuItemsContainer>
<StyledDropdownMenuItemsContainer>
{columnIds.map((columnId) => (
<RecordIndexPageKanbanAddMenuItem
key={columnId}
columnId={columnId}
recordIndexId={recordIndexId}
onItemClick={handleItemClick}
/>
))}
</StyledDropdownMenuItemsContainer>
)}
</StyledDropDownMenu> </StyledDropDownMenu>
} }
dropdownHotkeyScope={{ scope: dropdownId }} dropdownHotkeyScope={{ scope: dropdownId }}

View File

@@ -1,53 +0,0 @@
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,
});
}
};
return {
selectFieldMetadataItem,
isOpportunity,
createOpportunity,
};
};

View File

@@ -4,8 +4,8 @@ import { ReactElement, useContext } from 'react';
import { import {
AppTooltip, AppTooltip,
IconComponent, IconComponent,
TooltipDelay,
OverflowingTextWithTooltip, OverflowingTextWithTooltip,
TooltipDelay,
} from 'twenty-ui'; } from 'twenty-ui';
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext'; import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
@@ -55,11 +55,9 @@ const StyledInlineCellBaseContainer = styled.div`
box-sizing: border-box; box-sizing: border-box;
width: 100%; width: 100%;
display: flex; display: flex;
height: 24px;
gap: ${({ theme }) => theme.spacing(1)}; gap: ${({ theme }) => theme.spacing(1)};
user-select: none; user-select: none;
justify-content: center; justify-content: center;
`; `;