mirror of
				https://github.com/lingble/twenty.git
				synced 2025-10-31 12:47:58 +00:00 
			
		
		
		
	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:
		| @@ -141,10 +141,6 @@ const StyledRecordInlineCellPlaceholder = styled.div` | ||||
|   height: 24px; | ||||
| `; | ||||
|  | ||||
| const StyledRecordInlineCell = styled(RecordInlineCell)` | ||||
|   height: 24px; | ||||
| `; | ||||
|  | ||||
| export const RecordBoardCard = ({ | ||||
|   isCreating = false, | ||||
|   onCreateSuccess, | ||||
| @@ -348,7 +344,7 @@ export const RecordBoardCard = ({ | ||||
|                   }} | ||||
|                 > | ||||
|                   {inView ? ( | ||||
|                     <StyledRecordInlineCell /> | ||||
|                     <RecordInlineCell /> | ||||
|                   ) : ( | ||||
|                     <StyledRecordInlineCellPlaceholder /> | ||||
|                   )} | ||||
|   | ||||
| @@ -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 { RecordBoardColumnNewOpportunityButton } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnNewOpportunityButton'; | ||||
| 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 { isRecordIndexBoardColumnLoadingFamilyState } from '@/object-record/states/isRecordBoardColumnLoadingFamilyState'; | ||||
|  | ||||
| @@ -64,6 +65,8 @@ export const RecordBoardColumnCardsContainer = ({ | ||||
|   const numberOfFields = visibleFieldDefinitions.length; | ||||
|  | ||||
|   const isCompactModeActive = useRecoilValue(isCompactModeActiveState); | ||||
|   const { isOpportunitiesCompanyFieldDisabled } = | ||||
|     useIsOpportunitiesCompanyFieldDisabled(); | ||||
|  | ||||
|   return ( | ||||
|     <StyledColumnCardsContainer | ||||
| @@ -107,8 +110,11 @@ export const RecordBoardColumnCardsContainer = ({ | ||||
|           > | ||||
|             <StyledNewButtonContainer> | ||||
|               {objectMetadataItem.nameSingular === | ||||
|               CoreObjectNameSingular.Opportunity ? ( | ||||
|                 <RecordBoardColumnNewOpportunityButton /> | ||||
|                 CoreObjectNameSingular.Opportunity && | ||||
|               !isOpportunitiesCompanyFieldDisabled ? ( | ||||
|                 <RecordBoardColumnNewOpportunityButton | ||||
|                   columnId={columnDefinition.id} | ||||
|                 /> | ||||
|               ) : ( | ||||
|                 <RecordBoardColumnNewButton columnId={columnDefinition.id} /> | ||||
|               )} | ||||
|   | ||||
| @@ -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 { RecordBoardColumnDropdownMenu } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnDropdownMenu'; | ||||
| 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 { useIsOpportunitiesCompanyFieldDisabled } from '@/object-record/record-board/record-board-column/hooks/useIsOpportunitiesCompanyFieldDisabled'; | ||||
| import { RecordBoardColumnHotkeyScope } from '@/object-record/record-board/types/BoardColumnHotkeyScope'; | ||||
| import { RecordBoardColumnDefinitionType } from '@/object-record/record-board/types/RecordBoardColumnDefinition'; | ||||
| import { SingleEntitySelect } from '@/object-record/relation-picker/components/SingleEntitySelect'; | ||||
| @@ -90,21 +90,18 @@ export const RecordBoardColumnHeader = () => { | ||||
|   const boardColumnTotal = 0; | ||||
|  | ||||
|   const { | ||||
|     isCreatingCard, | ||||
|     handleAddNewOpportunityClick, | ||||
|     handleCancel, | ||||
|     handleEntitySelect, | ||||
|   } = useAddNewOpportunity('first'); | ||||
|     newRecord, | ||||
|     handleNewButtonClick, | ||||
|     handleCreateSuccess, | ||||
|  | ||||
|   const { newRecord, handleNewButtonClick, handleCreateSuccess } = | ||||
|     useColumnNewCardActions(columnDefinition.id); | ||||
|     handleEntitySelect, | ||||
|   } = useColumnNewCardActions(columnDefinition.id); | ||||
|   const { isOpportunitiesCompanyFieldDisabled } = | ||||
|     useIsOpportunitiesCompanyFieldDisabled(); | ||||
|  | ||||
|   const isOpportunity = | ||||
|     objectMetadataItem.nameSingular === CoreObjectNameSingular.Opportunity; | ||||
|  | ||||
|   const handleClick = isOpportunity | ||||
|     ? handleAddNewOpportunityClick | ||||
|     : () => handleNewButtonClick('first'); | ||||
|     objectMetadataItem.nameSingular === CoreObjectNameSingular.Opportunity && | ||||
|     !isOpportunitiesCompanyFieldDisabled; | ||||
|  | ||||
|   return ( | ||||
|     <> | ||||
| @@ -152,7 +149,7 @@ export const RecordBoardColumnHeader = () => { | ||||
|                 <LightIconButton | ||||
|                   accent="tertiary" | ||||
|                   Icon={IconPlus} | ||||
|                   onClick={handleClick} | ||||
|                   onClick={() => handleNewButtonClick('first', isOpportunity)} | ||||
|                 /> | ||||
|               </StyledHeaderActions> | ||||
|             )} | ||||
| @@ -165,23 +162,26 @@ export const RecordBoardColumnHeader = () => { | ||||
|           stageId={columnDefinition.id} | ||||
|         /> | ||||
|       )} | ||||
|       {newRecord?.isCreating && newRecord.position === 'first' && ( | ||||
|       {newRecord?.isCreating && | ||||
|         newRecord.position === 'first' && | ||||
|         (newRecord.isOpportunity ? ( | ||||
|           <SingleEntitySelect | ||||
|             disableBackgroundBlur | ||||
|             onCancel={() => handleCreateSuccess('first', columnDefinition.id)} | ||||
|             onEntitySelected={(company) => | ||||
|               company && handleEntitySelect('first', company) | ||||
|             } | ||||
|             relationObjectNameSingular={CoreObjectNameSingular.Company} | ||||
|             relationPickerScopeId="relation-picker" | ||||
|             selectedRelationRecordIds={[]} | ||||
|           /> | ||||
|         ) : ( | ||||
|           <RecordBoardCard | ||||
|             isCreating={true} | ||||
|             onCreateSuccess={() => handleCreateSuccess('first')} | ||||
|             position="first" | ||||
|           /> | ||||
|       )} | ||||
|       {isCreatingCard && ( | ||||
|         <SingleEntitySelect | ||||
|           disableBackgroundBlur | ||||
|           onCancel={handleCancel} | ||||
|           onEntitySelected={handleEntitySelect} | ||||
|           relationObjectNameSingular={CoreObjectNameSingular.Company} | ||||
|           relationPickerScopeId="relation-picker" | ||||
|           selectedRelationRecordIds={[]} | ||||
|         /> | ||||
|       )} | ||||
|         ))} | ||||
|     </> | ||||
|   ); | ||||
| }; | ||||
|   | ||||
| @@ -30,7 +30,11 @@ export const RecordBoardColumnNewButton = ({ | ||||
|   const { newRecord, handleNewButtonClick, handleCreateSuccess } = | ||||
|     useColumnNewCardActions(columnId); | ||||
|  | ||||
|   if (newRecord.isCreating && newRecord.position === 'last') { | ||||
|   if ( | ||||
|     newRecord.isCreating && | ||||
|     newRecord.position === 'last' && | ||||
|     !newRecord.isOpportunity | ||||
|   ) { | ||||
|     return ( | ||||
|       <RecordBoardCard | ||||
|         isCreating={true} | ||||
| @@ -41,7 +45,7 @@ export const RecordBoardColumnNewButton = ({ | ||||
|   } | ||||
|  | ||||
|   return ( | ||||
|     <StyledNewButton onClick={() => handleNewButtonClick('last')}> | ||||
|     <StyledNewButton onClick={() => handleNewButtonClick('last', false)}> | ||||
|       <IconPlus size={theme.icon.size.md} /> | ||||
|       New | ||||
|     </StyledNewButton> | ||||
|   | ||||
| @@ -3,7 +3,7 @@ import styled from '@emotion/styled'; | ||||
| import { IconPlus } from 'twenty-ui'; | ||||
|  | ||||
| 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'; | ||||
|  | ||||
| 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 { | ||||
|     isCreatingCard, | ||||
|     handleAddNewOpportunityClick, | ||||
|     handleCancel, | ||||
|     newRecord, | ||||
|     handleNewButtonClick, | ||||
|     handleEntitySelect, | ||||
|   } = useAddNewOpportunity('last'); | ||||
|     handleCreateSuccess, | ||||
|   } = useColumnNewCardActions(columnId); | ||||
|   return ( | ||||
|     <> | ||||
|       {isCreatingCard ? ( | ||||
|       {newRecord.isCreating && | ||||
|       newRecord.position === 'last' && | ||||
|       newRecord.isOpportunity ? ( | ||||
|         <SingleEntitySelect | ||||
|           disableBackgroundBlur | ||||
|           onCancel={handleCancel} | ||||
|           onEntitySelected={handleEntitySelect} | ||||
|           onCancel={() => handleCreateSuccess('last', columnId, false)} | ||||
|           onEntitySelected={(company) => | ||||
|             company ? handleEntitySelect('last', company) : null | ||||
|           } | ||||
|           relationObjectNameSingular={CoreObjectNameSingular.Company} | ||||
|           relationPickerScopeId="relation-picker" | ||||
|           selectedRelationRecordIds={[]} | ||||
|         /> | ||||
|       ) : ( | ||||
|         <StyledButton onClick={handleAddNewOpportunityClick}> | ||||
|         <StyledButton onClick={() => handleNewButtonClick('last', true)}> | ||||
|           <IconPlus size={theme.icon.size.md} /> | ||||
|           New | ||||
|         </StyledButton> | ||||
|   | ||||
| @@ -1,15 +1,28 @@ | ||||
| import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext'; | ||||
| import { RecordBoardColumnContext } from '@/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext'; | ||||
| 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 { useRecoilCallback } from 'recoil'; | ||||
| import { RecoilState, useRecoilCallback } from 'recoil'; | ||||
| import { v4 as uuidv4 } from 'uuid'; | ||||
|  | ||||
| type SetFunction = <T>( | ||||
|   recoilVal: RecoilState<T>, | ||||
|   valOrUpdater: T | ((currVal: T) => T), | ||||
| ) => void; | ||||
|  | ||||
| export const useAddNewCard = () => { | ||||
|   const columnContext = useContext(RecordBoardColumnContext); | ||||
|   const { createOneRecord, selectFieldMetadataItem } = | ||||
|     useContext(RecordBoardContext); | ||||
|  | ||||
|   const { | ||||
|     goBackToPreviousHotkeyScope, | ||||
|     setHotkeyScopeAndMemorizePreviousScope, | ||||
|   } = usePreviousHotkeyScope(); | ||||
|  | ||||
|   const getColumnDefinitionId = useCallback( | ||||
|     (columnId?: string) => { | ||||
|       const columnDefinitionId = columnId || columnContext?.columnDefinition.id; | ||||
| @@ -21,8 +34,13 @@ export const useAddNewCard = () => { | ||||
|     [columnContext], | ||||
|   ); | ||||
|  | ||||
|   const addNewCard = useCallback( | ||||
|     (set: any, columnDefinitionId: string, position: 'first' | 'last') => { | ||||
|   const addNewItem = useCallback( | ||||
|     ( | ||||
|       set: SetFunction, | ||||
|       columnDefinitionId: string, | ||||
|       position: 'first' | 'last', | ||||
|       isOpportunity: boolean, | ||||
|     ) => { | ||||
|       set( | ||||
|         recordBoardNewRecordByColumnIdSelector({ | ||||
|           familyKey: columnDefinitionId, | ||||
| @@ -33,6 +51,8 @@ export const useAddNewCard = () => { | ||||
|           columnId: columnDefinitionId, | ||||
|           isCreating: true, | ||||
|           position, | ||||
|           isOpportunity, | ||||
|           company: null, | ||||
|         }, | ||||
|       ); | ||||
|     }, | ||||
| @@ -44,12 +64,19 @@ export const useAddNewCard = () => { | ||||
|       labelIdentifier: string, | ||||
|       labelValue: string, | ||||
|       position: 'first' | 'last', | ||||
|       isOpportunity: boolean, | ||||
|       company?: EntityForSelect, | ||||
|     ) => { | ||||
|       if (labelValue !== '') { | ||||
|       if ( | ||||
|         (isOpportunity && company !== null) || | ||||
|         (!isOpportunity && labelValue !== '') | ||||
|       ) { | ||||
|         createOneRecord({ | ||||
|           [selectFieldMetadataItem.name]: columnContext?.columnDefinition.value, | ||||
|           position, | ||||
|           [labelIdentifier.toLowerCase()]: labelValue, | ||||
|           ...(isOpportunity | ||||
|             ? { companyId: company?.id, name: company?.name } | ||||
|             : { [labelIdentifier.toLowerCase()]: labelValue }), | ||||
|         }); | ||||
|       } | ||||
|     }, | ||||
| @@ -62,18 +89,34 @@ export const useAddNewCard = () => { | ||||
|         labelIdentifier: string, | ||||
|         labelValue: string, | ||||
|         position: 'first' | 'last', | ||||
|         isOpportunity: boolean, | ||||
|         columnId?: string, | ||||
|       ): void => { | ||||
|         const columnDefinitionId = getColumnDefinitionId(columnId); | ||||
|         addNewCard(set, columnDefinitionId, position); | ||||
|         createRecord(labelIdentifier, labelValue, position); | ||||
|         addNewItem(set, columnDefinitionId, position, isOpportunity); | ||||
|         if (isOpportunity) { | ||||
|           setHotkeyScopeAndMemorizePreviousScope( | ||||
|             RelationPickerHotkeyScope.RelationPicker, | ||||
|           ); | ||||
|         } else { | ||||
|           createRecord(labelIdentifier, labelValue, position, isOpportunity); | ||||
|         } | ||||
|       }, | ||||
|     [addNewCard, createRecord, getColumnDefinitionId], | ||||
|     [ | ||||
|       addNewItem, | ||||
|       createRecord, | ||||
|       getColumnDefinitionId, | ||||
|       setHotkeyScopeAndMemorizePreviousScope, | ||||
|     ], | ||||
|   ); | ||||
|  | ||||
|   const handleCreateSuccess = useRecoilCallback( | ||||
|     ({ set }) => | ||||
|       (position: 'first' | 'last', columnId?: string): void => { | ||||
|       ( | ||||
|         position: 'first' | 'last', | ||||
|         columnId?: string, | ||||
|         isOpportunity = false, | ||||
|       ): void => { | ||||
|         const columnDefinitionId = getColumnDefinitionId(columnId); | ||||
|         set( | ||||
|           recordBoardNewRecordByColumnIdSelector({ | ||||
| @@ -85,10 +128,15 @@ export const useAddNewCard = () => { | ||||
|             columnId: columnDefinitionId, | ||||
|             isCreating: false, | ||||
|             position, | ||||
|             isOpportunity: Boolean(isOpportunity), | ||||
|             company: null, | ||||
|           }, | ||||
|         ); | ||||
|         if (isOpportunity === true) { | ||||
|           goBackToPreviousHotkeyScope(); | ||||
|         } | ||||
|       }, | ||||
|     [getColumnDefinitionId], | ||||
|     [getColumnDefinitionId, goBackToPreviousHotkeyScope], | ||||
|   ); | ||||
|  | ||||
|   const handleCreate = ( | ||||
| @@ -98,7 +146,13 @@ export const useAddNewCard = () => { | ||||
|     onCreateSuccess?: () => void, | ||||
|   ) => { | ||||
|     if (labelValue.trim() !== '' && position !== undefined) { | ||||
|       handleAddNewCardClick(labelIdentifier, labelValue.trim(), position); | ||||
|       handleAddNewCardClick( | ||||
|         labelIdentifier, | ||||
|         labelValue.trim(), | ||||
|         position, | ||||
|         false, | ||||
|         '', | ||||
|       ); | ||||
|       onCreateSuccess?.(); | ||||
|     } | ||||
|   }; | ||||
| @@ -125,11 +179,25 @@ export const useAddNewCard = () => { | ||||
|     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 { | ||||
|     handleAddNewCardClick, | ||||
|     handleCreateSuccess, | ||||
|     handleCreate, | ||||
|     handleBlur, | ||||
|     handleInputEnter, | ||||
|     handleEntitySelect, | ||||
|   }; | ||||
| }; | ||||
|   | ||||
| @@ -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, | ||||
|   }; | ||||
| }; | ||||
| @@ -12,7 +12,8 @@ export const useColumnNewCardActions = (columnId: string) => { | ||||
|     (field) => field.isLabelIdentifier, | ||||
|   ); | ||||
|  | ||||
|   const { handleAddNewCardClick, handleCreateSuccess } = useAddNewCard(); | ||||
|   const { handleAddNewCardClick, handleCreateSuccess, handleEntitySelect } = | ||||
|     useAddNewCard(); | ||||
|  | ||||
|   const newRecord = useRecoilValue( | ||||
|     recordBoardNewRecordByColumnIdSelector({ | ||||
| @@ -21,11 +22,15 @@ export const useColumnNewCardActions = (columnId: string) => { | ||||
|     }), | ||||
|   ); | ||||
|  | ||||
|   const handleNewButtonClick = (position: 'first' | 'last') => { | ||||
|   const handleNewButtonClick = ( | ||||
|     position: 'first' | 'last', | ||||
|     isOpportunity: boolean, | ||||
|   ) => { | ||||
|     handleAddNewCardClick( | ||||
|       labelIdentifierField?.label ?? '', | ||||
|       '', | ||||
|       position, | ||||
|       isOpportunity, | ||||
|       columnId, | ||||
|     ); | ||||
|   }; | ||||
| @@ -34,5 +39,6 @@ export const useColumnNewCardActions = (columnId: string) => { | ||||
|     newRecord, | ||||
|     handleNewButtonClick, | ||||
|     handleCreateSuccess, | ||||
|     handleEntitySelect, | ||||
|   }; | ||||
| }; | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
| import { EntityForSelect } from '@/object-record/relation-picker/types/EntityForSelect'; | ||||
| import { createComponentFamilyState } from '@/ui/utilities/state/component-state/utils/createComponentFamilyState'; | ||||
|  | ||||
| export type NewCard = { | ||||
| @@ -5,6 +6,8 @@ export type NewCard = { | ||||
|   columnId: string; | ||||
|   isCreating: boolean; | ||||
|   position: 'first' | 'last'; | ||||
|   isOpportunity: boolean; | ||||
|   company: EntityForSelect | null; | ||||
| }; | ||||
|  | ||||
| export const recordBoardNewRecordByColumnIdComponentFamilyState = | ||||
| @@ -15,5 +18,7 @@ export const recordBoardNewRecordByColumnIdComponentFamilyState = | ||||
|       columnId: '', | ||||
|       isCreating: false, | ||||
|       position: 'last', | ||||
|       isOpportunity: false, | ||||
|       company: null, | ||||
|     }, | ||||
|   }); | ||||
|   | ||||
| @@ -1,24 +1,21 @@ | ||||
| import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; | ||||
| import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; | ||||
| import { useRecordBoardStates } from '@/object-record/record-board/hooks/internal/useRecordBoardStates'; | ||||
| 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 { RecordIndexPageKanbanAddMenuItem } from '@/object-record/record-index/components/RecordIndexPageKanbanAddMenuItem'; | ||||
| import { RecordIndexRootPropsContext } from '@/object-record/record-index/contexts/RecordIndexRootPropsContext'; | ||||
| 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 { recordIndexKanbanFieldMetadataIdState } from '@/object-record/record-index/states/recordIndexKanbanFieldMetadataIdState'; | ||||
| 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, useContext, useState } from 'react'; | ||||
| import { useCallback, useContext } from 'react'; | ||||
| import { useRecoilValue } from 'recoil'; | ||||
| import { IconPlus, isDefined } from 'twenty-ui'; | ||||
| import { IconPlus } from 'twenty-ui'; | ||||
|  | ||||
| const StyledDropdownMenuItemsContainer = styled(DropdownMenuItemsContainer)` | ||||
|   width: 100%; | ||||
| @@ -30,13 +27,21 @@ const StyledDropDownMenu = styled(DropdownMenu)` | ||||
|  | ||||
| export const RecordIndexPageKanbanAddButton = () => { | ||||
|   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, | ||||
|   ); | ||||
|   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 } = | ||||
|     useRecordBoardStates(recordIndexId); | ||||
| @@ -48,73 +53,32 @@ export const RecordIndexPageKanbanAddButton = () => { | ||||
|     (field) => field.isLabelIdentifier, | ||||
|   ); | ||||
|  | ||||
|   const { | ||||
|     setHotkeyScopeAndMemorizePreviousScope, | ||||
|     goBackToPreviousHotkeyScope, | ||||
|   } = usePreviousHotkeyScope(); | ||||
|   const { resetSearchFilter } = useEntitySelectSearch({ | ||||
|     relationPickerScopeId: 'relation-picker', | ||||
|   }); | ||||
|  | ||||
|   const { closeDropdown } = useDropdown(dropdownId); | ||||
|  | ||||
|   const { selectFieldMetadataItem, isOpportunity, createOpportunity } = | ||||
|     useRecordIndexPageKanbanAddButton({ | ||||
|       objectNamePlural, | ||||
|     }); | ||||
|  | ||||
|   const { isOpportunitiesCompanyFieldDisabled } = | ||||
|     useIsOpportunitiesCompanyFieldDisabled(); | ||||
|   const { handleAddNewCardClick } = useAddNewCard(); | ||||
|  | ||||
|   const handleItemClick = useCallback( | ||||
|     (columnDefinition: RecordBoardColumnDefinition) => { | ||||
|       if (isOpportunity) { | ||||
|         setIsSelectingCompany(true); | ||||
|         setSelectedColumnDefinition(columnDefinition); | ||||
|         setHotkeyScopeAndMemorizePreviousScope( | ||||
|           RelationPickerHotkeyScope.RelationPicker, | ||||
|         ); | ||||
|       } else { | ||||
|       const isOpportunityEnabled = | ||||
|         isOpportunity && !isOpportunitiesCompanyFieldDisabled; | ||||
|       handleAddNewCardClick( | ||||
|         labelIdentifierField?.label ?? '', | ||||
|         '', | ||||
|         'first', | ||||
|         isOpportunityEnabled, | ||||
|         columnDefinition.id, | ||||
|       ); | ||||
|       closeDropdown(); | ||||
|       } | ||||
|     }, | ||||
|     [ | ||||
|       isOpportunity, | ||||
|       handleAddNewCardClick, | ||||
|       setHotkeyScopeAndMemorizePreviousScope, | ||||
|       closeDropdown, | ||||
|       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) { | ||||
|     return null; | ||||
| @@ -137,16 +101,6 @@ export const RecordIndexPageKanbanAddButton = () => { | ||||
|       dropdownId={dropdownId} | ||||
|       dropdownComponents={ | ||||
|         <StyledDropDownMenu> | ||||
|           {isOpportunity && isSelectingCompany ? ( | ||||
|             <SingleEntitySelect | ||||
|               disableBackgroundBlur | ||||
|               onCancel={handleCancel} | ||||
|               onEntitySelected={handleEntitySelect} | ||||
|               relationObjectNameSingular={CoreObjectNameSingular.Company} | ||||
|               relationPickerScopeId="relation-picker" | ||||
|               selectedRelationRecordIds={[]} | ||||
|             /> | ||||
|           ) : ( | ||||
|           <StyledDropdownMenuItemsContainer> | ||||
|             {columnIds.map((columnId) => ( | ||||
|               <RecordIndexPageKanbanAddMenuItem | ||||
| @@ -157,7 +111,6 @@ export const RecordIndexPageKanbanAddButton = () => { | ||||
|               /> | ||||
|             ))} | ||||
|           </StyledDropdownMenuItemsContainer> | ||||
|           )} | ||||
|         </StyledDropDownMenu> | ||||
|       } | ||||
|       dropdownHotkeyScope={{ scope: dropdownId }} | ||||
|   | ||||
| @@ -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, | ||||
|   }; | ||||
| }; | ||||
| @@ -4,8 +4,8 @@ import { ReactElement, useContext } from 'react'; | ||||
| import { | ||||
|   AppTooltip, | ||||
|   IconComponent, | ||||
|   TooltipDelay, | ||||
|   OverflowingTextWithTooltip, | ||||
|   TooltipDelay, | ||||
| } from 'twenty-ui'; | ||||
|  | ||||
| import { FieldContext } from '@/object-record/record-field/contexts/FieldContext'; | ||||
| @@ -55,11 +55,9 @@ const StyledInlineCellBaseContainer = styled.div` | ||||
|   box-sizing: border-box; | ||||
|   width: 100%; | ||||
|   display: flex; | ||||
|  | ||||
|   height: 24px; | ||||
|   gap: ${({ theme }) => theme.spacing(1)}; | ||||
|  | ||||
|   user-select: none; | ||||
|  | ||||
|   justify-content: center; | ||||
| `; | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 nitin
					nitin