diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/no-selection/hooks/useCreateNewTableRecordNoSelectionRecordAction.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/no-selection/hooks/useCreateNewTableRecordNoSelectionRecordAction.ts index e73a6ab6b..56709b1ac 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/no-selection/hooks/useCreateNewTableRecordNoSelectionRecordAction.ts +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/no-selection/hooks/useCreateNewTableRecordNoSelectionRecordAction.ts @@ -1,7 +1,6 @@ import { ActionHookWithObjectMetadataItem } from '@/action-menu/actions/types/ActionHook'; import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState'; -import { useCreateNewTableRecord } from '@/object-record/record-table/hooks/useCreateNewTableRecords'; -import { getRecordIndexIdFromObjectNamePluralAndViewId } from '@/object-record/utils/getRecordIndexIdFromObjectNamePluralAndViewId'; +import { useCreateNewIndexRecord } from '@/object-record/record-table/hooks/useCreateNewIndexRecord'; import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; @@ -15,24 +14,14 @@ export const useCreateNewTableRecordNoSelectionRecordAction: ActionHookWithObjec throw new Error('Current view ID is not defined'); } - const recordTableId = getRecordIndexIdFromObjectNamePluralAndViewId( - objectMetadataItem.namePlural, - currentViewId, - ); - const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission(); - const { createNewTableRecord } = useCreateNewTableRecord({ + const { createNewIndexRecord } = useCreateNewIndexRecord({ objectMetadataItem, - recordTableId, }); - const onClick = () => { - createNewTableRecord(); - }; - return { shouldBeRegistered: !hasObjectReadOnlyPermission, - onClick, + onClick: createNewIndexRecord, }; }; diff --git a/packages/twenty-front/src/modules/activities/hooks/__tests__/useActivityTargetObjectRecords.test.tsx b/packages/twenty-front/src/modules/activities/hooks/__tests__/useActivityTargetObjectRecords.test.tsx index cc98f024d..ba8f488ca 100644 --- a/packages/twenty-front/src/modules/activities/hooks/__tests__/useActivityTargetObjectRecords.test.tsx +++ b/packages/twenty-front/src/modules/activities/hooks/__tests__/useActivityTargetObjectRecords.test.tsx @@ -7,6 +7,7 @@ import { RecoilRoot, useSetRecoilState } from 'recoil'; import { useActivityTargetObjectRecords } from '@/activities/hooks/useActivityTargetObjectRecords'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope'; import { JestObjectMetadataItemSetter } from '~/testing/jest/JestObjectMetadataItemSetter'; import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; @@ -130,13 +131,19 @@ describe('useActivityTargetObjectRecords', () => { objectMetadataItemsState, ); - const { activityTargetObjectRecords } = - useActivityTargetObjectRecords(task); + const setRecordFromStore = useSetRecoilState( + recordStoreFamilyState(task.id), + ); + + const { activityTargetObjectRecords } = useActivityTargetObjectRecords( + task.id, + ); return { activityTargetObjectRecords, setCurrentWorkspaceMember, setObjectMetadataItems, + setRecordFromStore, }; }, { wrapper: Wrapper }, @@ -145,6 +152,7 @@ describe('useActivityTargetObjectRecords', () => { act(() => { result.current.setCurrentWorkspaceMember(mockWorkspaceMembers[0]); result.current.setObjectMetadataItems(generatedMockObjectMetadataItems); + result.current.setRecordFromStore(task); }); const activityTargetObjectRecords = diff --git a/packages/twenty-front/src/modules/activities/hooks/useActivityTargetObjectRecords.ts b/packages/twenty-front/src/modules/activities/hooks/useActivityTargetObjectRecords.ts index 63e7c48ba..d1c707728 100644 --- a/packages/twenty-front/src/modules/activities/hooks/useActivityTargetObjectRecords.ts +++ b/packages/twenty-front/src/modules/activities/hooks/useActivityTargetObjectRecords.ts @@ -8,14 +8,19 @@ import { Task } from '@/activities/types/Task'; import { TaskTarget } from '@/activities/types/TaskTarget'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; +import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { isDefined } from 'twenty-shared'; export const useActivityTargetObjectRecords = ( - activity?: Task | Note, + activityRecordId?: string, activityTargets?: NoteTarget[] | TaskTarget[], ) => { const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + const activity = useRecoilValue( + recordStoreFamilyState(activityRecordId ?? ''), + ) as Note | Task | null; + if (!isDefined(activity) && !isDefined(activityTargets)) { return { activityTargetObjectRecords: [] }; } diff --git a/packages/twenty-front/src/modules/activities/inline-cell/components/ActivityTargetsInlineCell.tsx b/packages/twenty-front/src/modules/activities/inline-cell/components/ActivityTargetsInlineCell.tsx index 227e84aae..3447326e8 100644 --- a/packages/twenty-front/src/modules/activities/inline-cell/components/ActivityTargetsInlineCell.tsx +++ b/packages/twenty-front/src/modules/activities/inline-cell/components/ActivityTargetsInlineCell.tsx @@ -7,8 +7,6 @@ import { useActivityTargetObjectRecords } from '@/activities/hooks/useActivityTa import { useOpenActivityTargetInlineCellEditMode } from '@/activities/inline-cell/hooks/useOpenActivityTargetInlineCellEditMode'; import { useUpdateActivityTargetFromInlineCell } from '@/activities/inline-cell/hooks/useUpdateActivityTargetFromInlineCell'; import { ActivityEditorHotkeyScope } from '@/activities/types/ActivityEditorHotkeyScope'; -import { Note } from '@/activities/types/Note'; -import { Task } from '@/activities/types/Task'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { useFieldContext } from '@/object-record/hooks/useFieldContext'; import { FieldContext } from '@/object-record/record-field/contexts/FieldContext'; @@ -22,7 +20,7 @@ import { MultipleRecordPicker } from '@/object-record/record-picker/multiple-rec import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; type ActivityTargetsInlineCellProps = { - activity: Task | Note; + activityRecordId: string; showLabel?: boolean; maxWidth?: number; activityObjectNameSingular: @@ -31,15 +29,15 @@ type ActivityTargetsInlineCellProps = { }; export const ActivityTargetsInlineCell = ({ - activity, + activityRecordId, showLabel = true, maxWidth, activityObjectNameSingular, }: ActivityTargetsInlineCellProps) => { const { activityTargetObjectRecords } = - useActivityTargetObjectRecords(activity); + useActivityTargetObjectRecords(activityRecordId); - const multipleRecordPickerInstanceId = `multiple-record-picker-target-${activity.id}`; + const multipleRecordPickerInstanceId = `multiple-record-picker-target-${activityRecordId}`; const { closeInlineCell } = useInlineCell(); @@ -58,7 +56,7 @@ export const ActivityTargetsInlineCell = ({ const { FieldContextProvider: ActivityTargetsContextProvider } = useFieldContext({ objectNameSingular: activityObjectNameSingular, - objectRecordId: activity.id, + objectRecordId: activityRecordId, fieldMetadataName: fieldDefinition.metadata.fieldName, fieldPosition: 3, overridenIsFieldEmpty: activityTargetObjectRecords.length === 0, @@ -70,11 +68,11 @@ export const ActivityTargetsInlineCell = ({ const { updateActivityTargetFromInlineCell } = useUpdateActivityTargetFromInlineCell({ activityObjectNameSingular, - activityId: activity.id, + activityId: activityRecordId, }); return ( - + {ActivityTargetsContextProvider && ( diff --git a/packages/twenty-front/src/modules/activities/notes/components/NoteCard.tsx b/packages/twenty-front/src/modules/activities/notes/components/NoteCard.tsx index e3019c9fd..99abf357a 100644 --- a/packages/twenty-front/src/modules/activities/notes/components/NoteCard.tsx +++ b/packages/twenty-front/src/modules/activities/notes/components/NoteCard.tsx @@ -96,7 +96,7 @@ export const NoteCard = ({ {NoteTargetsContextProvider && ( diff --git a/packages/twenty-front/src/modules/activities/tasks/components/TaskRow.tsx b/packages/twenty-front/src/modules/activities/tasks/components/TaskRow.tsx index a33ce6977..2a62529b1 100644 --- a/packages/twenty-front/src/modules/activities/tasks/components/TaskRow.tsx +++ b/packages/twenty-front/src/modules/activities/tasks/components/TaskRow.tsx @@ -132,7 +132,7 @@ export const TaskRow = ({ task }: { task: Task }) => { diff --git a/packages/twenty-front/src/modules/activities/timeline-activities/components/EventRow.tsx b/packages/twenty-front/src/modules/activities/timeline-activities/components/EventRow.tsx index e78e4a136..bf5f20991 100644 --- a/packages/twenty-front/src/modules/activities/timeline-activities/components/EventRow.tsx +++ b/packages/twenty-front/src/modules/activities/timeline-activities/components/EventRow.tsx @@ -11,6 +11,8 @@ import { TimelineActivity } from '@/activities/timeline-activities/types/Timelin import { getTimelineActivityAuthorFullName } from '@/activities/timeline-activities/utils/getTimelineActivityAuthorFullName'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { getObjectRecordIdentifier } from '@/object-metadata/utils/getObjectRecordIdentifier'; +import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { MOBILE_VIEWPORT } from 'twenty-ui'; import { beautifyPastDateRelativeToNow } from '~/utils/date-utils'; import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull'; @@ -104,7 +106,10 @@ export const EventRow = ({ }: EventRowProps) => { const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); - const { labelIdentifierValue } = useContext(TimelineActivityContext); + const { recordId } = useContext(TimelineActivityContext); + + const recordFromStore = useRecoilValue(recordStoreFamilyState(recordId)); + const beautifiedCreatedAt = beautifyPastDateRelativeToNow(event.createdAt); const linkedObjectMetadataItem = useLinkedObjectObjectMetadataItem( event.linkedObjectMetadataId, @@ -114,6 +119,18 @@ export const EventRow = ({ return null; } + if (isUndefinedOrNull(recordFromStore)) { + return null; + } + if (isUndefinedOrNull(mainObjectMetadataItem)) { + return null; + } + + const labelIdentifier = getObjectRecordIdentifier({ + objectMetadataItem: mainObjectMetadataItem, + record: recordFromStore, + }); + const authorFullName = getTimelineActivityAuthorFullName( event, currentWorkspaceMember, @@ -143,7 +160,7 @@ export const EventRow = ({ = { SnackBarDecorator, (Story) => { return ( - + ); diff --git a/packages/twenty-front/src/modules/activities/timeline-activities/contexts/TimelineActivityContext.ts b/packages/twenty-front/src/modules/activities/timeline-activities/contexts/TimelineActivityContext.ts index cd50acc26..dece9b42a 100644 --- a/packages/twenty-front/src/modules/activities/timeline-activities/contexts/TimelineActivityContext.ts +++ b/packages/twenty-front/src/modules/activities/timeline-activities/contexts/TimelineActivityContext.ts @@ -1,10 +1,10 @@ import { createContext } from 'react'; type TimelineActivityContextValue = { - labelIdentifierValue: string; + recordId: string; }; export const TimelineActivityContext = createContext({ - labelIdentifierValue: '', + recordId: '', }); diff --git a/packages/twenty-front/src/modules/activities/timeline-activities/rows/message/components/__stories__/EventCardMessage.stories.tsx b/packages/twenty-front/src/modules/activities/timeline-activities/rows/message/components/__stories__/EventCardMessage.stories.tsx index 876a25707..ff0c85a9f 100644 --- a/packages/twenty-front/src/modules/activities/timeline-activities/rows/message/components/__stories__/EventCardMessage.stories.tsx +++ b/packages/twenty-front/src/modules/activities/timeline-activities/rows/message/components/__stories__/EventCardMessage.stories.tsx @@ -16,9 +16,7 @@ const meta: Meta = { SnackBarDecorator, (Story) => { return ( - + ); diff --git a/packages/twenty-front/src/modules/command-menu/pages/record-page/components/CommandMenuRecordPage.tsx b/packages/twenty-front/src/modules/command-menu/pages/record-page/components/CommandMenuRecordPage.tsx index fedd90839..2611a5063 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/record-page/components/CommandMenuRecordPage.tsx +++ b/packages/twenty-front/src/modules/command-menu/pages/record-page/components/CommandMenuRecordPage.tsx @@ -1,4 +1,5 @@ import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext'; +import { TimelineActivityContext } from '@/activities/timeline-activities/contexts/TimelineActivityContext'; import { isNewViewableRecordLoadingComponentState } from '@/command-menu/pages/record-page/states/isNewViewableRecordLoadingComponentState'; import { viewableRecordIdComponentState } from '@/command-menu/pages/record-page/states/viewableRecordIdComponentState'; import { viewableRecordNameSingularComponentState } from '@/command-menu/pages/record-page/states/viewableRecordNameSingularComponentState'; @@ -7,6 +8,7 @@ import { ContextStoreComponentInstanceContext } from '@/context-store/states/con import { RecordFilterGroupsComponentInstanceContext } from '@/object-record/record-filter-group/states/context/RecordFilterGroupsComponentInstanceContext'; import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext'; import { RecordShowContainer } from '@/object-record/record-show/components/RecordShowContainer'; +import { RecordShowEffect } from '@/object-record/record-show/components/RecordShowEffect'; import { useRecordShowPage } from '@/object-record/record-show/hooks/useRecordShowPage'; import { RecordSortsComponentInstanceContext } from '@/object-record/record-sort/states/context/RecordSortsComponentInstanceContext'; import { RecordValueSetterEffect } from '@/object-record/record-store/components/RecordValueSetterEffect'; @@ -74,13 +76,23 @@ export const CommandMenuRecordPage = () => { {!isNewViewableRecordLoading && ( )} - + + + + diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCard.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCard.tsx index c5e9e696a..54a6566c0 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCard.tsx +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCard.tsx @@ -25,7 +25,6 @@ import styled from '@emotion/styled'; import { useContext, useState } from 'react'; import { InView, useInView } from 'react-intersection-observer'; import { useRecoilValue, useSetRecoilState } from 'recoil'; -import { isDefined } from 'twenty-shared'; import { AnimatedEaseInOut } from 'twenty-ui'; import { useDebouncedCallback } from 'use-debounce'; import { useNavigateApp } from '~/hooks/useNavigateApp'; @@ -71,15 +70,7 @@ const StyledBoardCardWrapper = styled.div` width: 100%; `; -export const RecordBoardCard = ({ - isCreating = false, - onCreateSuccess, - position, -}: { - isCreating?: boolean; - onCreateSuccess?: () => void; - position?: 'first' | 'last'; -}) => { +export const RecordBoardCard = () => { const navigate = useNavigateApp(); const { openRecordInCommandMenu } = useOpenRecordInCommandMenu(); @@ -134,18 +125,16 @@ export const RecordBoardCard = ({ }; const handleCardClick = () => { - if (!isCreating) { - if (recordIndexOpenRecordIn === ViewOpenRecordInType.SIDE_PANEL) { - openRecordInCommandMenu({ - recordId, - objectNameSingular, - }); - } else { - navigate(AppPath.RecordShowPage, { - objectNameSingular, - objectRecordId: recordId, - }); - } + if (recordIndexOpenRecordIn === ViewOpenRecordInType.SIDE_PANEL) { + openRecordInCommandMenu({ + recordId, + objectNameSingular, + }); + } else { + navigate(AppPath.RecordShowPage, { + objectNameSingular, + objectRecordId: recordId, + }); } }; @@ -166,16 +155,12 @@ export const RecordBoardCard = ({ (boardField) => !boardField.isLabelIdentifier, ); - const labelIdentifierField = visibleFieldDefinitions.find( - (field) => field.isLabelIdentifier, - ); - return ( - {!isCreating && } + - {isDefined(labelIdentifierField) && ( - - )} + theme.border.radius.sm}; - width: ${({ theme }) => theme.spacing(53)}; -`; - const StyledCompactIconContainer = styled.div` align-items: center; display: flex; @@ -59,38 +40,21 @@ const StyledCheckboxContainer = styled.div` `; type RecordBoardCardHeaderProps = { - isCreating?: boolean; - onCreateSuccess?: () => void; - position?: 'first' | 'last'; - identifierFieldDefinition: RecordBoardFieldDefinition; isCardExpanded?: boolean; setIsCardExpanded?: Dispatch>; }; export const RecordBoardCardHeader = ({ - isCreating = false, - onCreateSuccess, - position, - identifierFieldDefinition, isCardExpanded, setIsCardExpanded, }: RecordBoardCardHeaderProps) => { - const [newLabelValue, setNewLabelValue] = useState(''); - - const columnId = useContext(RecordBoardColumnContext)?.columnId; - - const { handleBlur, handleInputEnter } = useAddNewCard({ - recordPickerComponentInstanceId: `add-new-card-record-picker-column-${columnId}`, - }); - const { recordId } = useContext(RecordBoardCardContext); const { indexIdentifierUrl } = useRecordIndexContextOrThrow(); const record = useRecoilValue(recordStoreFamilyState(recordId)); - const { updateOneRecord, objectMetadataItem } = - useContext(RecordBoardContext); + const { objectMetadataItem } = useContext(RecordBoardContext); const recordBoardId = useAvailableScopeIdOrThrow( RecordBoardScopeInternalContext, @@ -100,11 +64,6 @@ export const RecordBoardCardHeader = ({ isRecordBoardCompactModeActiveComponentState, ); - const isIdentifierEmpty = isFieldValueEmpty({ - fieldDefinition: identifierFieldDefinition, - fieldValue: record?.[identifierFieldDefinition.metadata.fieldName], - }); - const { checkIfLastUnselectAndCloseDropdown } = useRecordBoardSelection(recordBoardId); @@ -114,112 +73,52 @@ export const RecordBoardCardHeader = ({ recordId, ); - const useUpdateOneRecordHook: RecordUpdateHook = () => { - const updateEntity = ({ variables }: RecordUpdateHookParams) => { - updateOneRecord?.({ - idToUpdate: variables.where.id as string, - updateOneRecordInput: variables.updateOneRecordInput, - }); - }; - - return [updateEntity, { loading: false }]; - }; - const recordIndexOpenRecordIn = useRecoilValue(recordIndexOpenRecordInState); return ( - {isCreating && position !== undefined ? ( - - - handleInputEnter(newLabelValue, position, onCreateSuccess) - } - onBlur={() => - handleBlur(newLabelValue, position, onCreateSuccess) - } - onChange={(text: string) => setNewLabelValue(text)} - placeholder={identifierFieldDefinition.label} - /> - - ) : isIdentifierEmpty ? ( - - - - ) : ( - isDefined(record) && ( - - ) + {isDefined(record) && ( + )} - {!isCreating && ( - <> - {showCompactView && ( - - - { - setIsCardExpanded?.((prev) => !prev); - }} - /> - - - )} - - - { - setIsCurrentCardSelected(!isCurrentCardSelected); - checkIfLastUnselectAndCloseDropdown(); - }} - variant={CheckboxVariant.Secondary} - /> - - - + {showCompactView && ( + + + { + setIsCardExpanded?.((prev) => !prev); + }} + /> + + )} + + + { + setIsCurrentCardSelected(!isCurrentCardSelected); + checkIfLastUnselectAndCloseDropdown(); + }} + variant={CheckboxVariant.Secondary} + /> + + ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnCardsContainer.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnCardsContainer.tsx index 8cdc5c32f..d855a7563 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnCardsContainer.tsx +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnCardsContainer.tsx @@ -3,16 +3,11 @@ import { Draggable, DroppableProvided } from '@hello-pangea/dnd'; import { useContext } from 'react'; import { useRecoilValue } from 'recoil'; -import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; -import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext'; import { RecordBoardColumnCardContainerSkeletonLoader } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnCardContainerSkeletonLoader'; import { RecordBoardColumnCardsMemo } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnCardsMemo'; import { RecordBoardColumnFetchMoreLoader } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnFetchMoreLoader'; -import { RecordBoardColumnNewOpportunity } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnNewOpportunity'; -import { RecordBoardColumnNewRecord } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnNewRecord'; import { RecordBoardColumnNewRecordButton } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnNewRecordButton'; 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 { isRecordBoardCompactModeActiveComponentState } from '@/object-record/record-board/states/isRecordBoardCompactModeActiveComponentState'; import { recordBoardVisibleFieldDefinitionsComponentSelector } from '@/object-record/record-board/states/selectors/recordBoardVisibleFieldDefinitionsComponentSelector'; @@ -50,7 +45,6 @@ export const RecordBoardColumnCardsContainer = ({ droppableProvided, }: RecordBoardColumnCardsContainerProps) => { const { columnDefinition } = useContext(RecordBoardColumnContext); - const { objectMetadataItem } = useContext(RecordBoardContext); const columnId = columnDefinition.id; @@ -68,42 +62,12 @@ export const RecordBoardColumnCardsContainer = ({ isRecordBoardCompactModeActiveComponentState, ); - const { isOpportunitiesCompanyFieldDisabled } = - useIsOpportunitiesCompanyFieldDisabled(); - return ( - - {(draggableProvided) => ( -
- {objectMetadataItem.nameSingular === - CoreObjectNameSingular.Opportunity && - !isOpportunitiesCompanyFieldDisabled ? ( - - ) : ( - - )} -
- )} -
{isRecordIndexBoardColumnLoading ? ( Array.from( { @@ -138,23 +102,8 @@ export const RecordBoardColumnCardsContainer = ({ // eslint-disable-next-line react/jsx-props-no-spreading {...draggableProvided?.draggableProps} > - {objectMetadataItem.nameSingular === - CoreObjectNameSingular.Opportunity && - !isOpportunitiesCompanyFieldDisabled ? ( - - ) : ( - - )} - + )} diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeader.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeader.tsx index 6829fb826..8f7fd93d5 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeader.tsx +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeader.tsx @@ -1,16 +1,14 @@ import styled from '@emotion/styled'; import { useContext, useState } from 'react'; -import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext'; import { RecordBoardColumnDropdownMenu } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnDropdownMenu'; import { RecordBoardColumnHeaderAggregateDropdown } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdown'; import { RecordBoardColumnContext } from '@/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext'; import { useAggregateRecordsForRecordBoardColumn } from '@/object-record/record-board/record-board-column/hooks/useAggregateRecordsForRecordBoardColumn'; -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 { RecordGroupDefinitionType } from '@/object-record/record-group/types/RecordGroupDefinition'; +import { useCreateNewIndexRecord } from '@/object-record/record-table/hooks/useCreateNewIndexRecord'; import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission'; import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; import { IconDotsVertical, IconPlus, LightIconButton, Tag } from 'twenty-ui'; @@ -69,7 +67,8 @@ export const RecordBoardColumnHeader = () => { const [isBoardColumnMenuOpen, setIsBoardColumnMenuOpen] = useState(false); const [isHeaderHovered, setIsHeaderHovered] = useState(false); - const { objectMetadataItem } = useContext(RecordBoardContext); + const { objectMetadataItem, selectFieldMetadataItem } = + useContext(RecordBoardContext); const { setHotkeyScopeAndMemorizePreviousScope, @@ -94,18 +93,11 @@ export const RecordBoardColumnHeader = () => { const { aggregateValue, aggregateLabel } = useAggregateRecordsForRecordBoardColumn(); - const { handleNewButtonClick } = useColumnNewCardActions( - columnDefinition.id ?? '', - ); - const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission(); - const { isOpportunitiesCompanyFieldDisabled } = - useIsOpportunitiesCompanyFieldDisabled(); - - const isOpportunity = - objectMetadataItem.nameSingular === CoreObjectNameSingular.Opportunity && - !isOpportunitiesCompanyFieldDisabled; + const { createNewIndexRecord } = useCreateNewIndexRecord({ + objectMetadataItem: objectMetadataItem, + }); return ( @@ -153,7 +145,12 @@ export const RecordBoardColumnHeader = () => { handleNewButtonClick('first', isOpportunity)} + onClick={() => { + createNewIndexRecord({ + position: 'first', + [selectFieldMetadataItem.name]: columnDefinition.value, + }); + }} /> )} diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnNewOpportunity.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnNewOpportunity.tsx deleted file mode 100644 index 1f9887736..000000000 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnNewOpportunity.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import { useOpenRecordInCommandMenu } from '@/command-menu/hooks/useOpenRecordInCommandMenu'; -import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; -import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord'; -import { useAddNewCard } from '@/object-record/record-board/record-board-column/hooks/useAddNewCard'; -import { recordBoardNewRecordByColumnIdSelector } from '@/object-record/record-board/states/selectors/recordBoardNewRecordByColumnIdSelector'; -import { SingleRecordPicker } from '@/object-record/record-picker/single-record-picker/components/SingleRecordPicker'; -import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState'; -import { viewableRecordNameSingularState } from '@/object-record/record-right-drawer/states/viewableRecordNameSingularState'; -import { OverlayContainer } from '@/ui/layout/overlay/components/OverlayContainer'; -import { useRecoilValue, useSetRecoilState } from 'recoil'; -import { isDefined } from 'twenty-shared'; -import { v4 } from 'uuid'; - -export const RecordBoardColumnNewOpportunity = ({ - columnId, - position, -}: { - columnId: string; - position: 'last' | 'first'; -}) => { - const newRecord = useRecoilValue( - recordBoardNewRecordByColumnIdSelector({ - familyKey: columnId, - scopeId: columnId, - }), - ); - - const { handleCreateSuccess, handleEntitySelect } = useAddNewCard({ - recordPickerComponentInstanceId: `add-new-card-record-picker-column-${columnId}`, - }); - - const { createOneRecord: createCompany } = useCreateOneRecord({ - objectNameSingular: CoreObjectNameSingular.Company, - }); - - const setViewableRecordId = useSetRecoilState(viewableRecordIdState); - const setViewableRecordNameSingular = useSetRecoilState( - viewableRecordNameSingularState, - ); - - const { openRecordInCommandMenu } = useOpenRecordInCommandMenu(); - - const createCompanyOpportunityAndOpenCommandMenu = async ( - searchInput?: string, - ) => { - const newRecordId = v4(); - - const createdCompany = await createCompany({ - id: newRecordId, - name: searchInput, - }); - - setViewableRecordId(newRecordId); - setViewableRecordNameSingular(CoreObjectNameSingular.Company); - openRecordInCommandMenu({ - recordId: newRecordId, - objectNameSingular: CoreObjectNameSingular.Company, - }); - - if (isDefined(createdCompany)) { - handleEntitySelect(position, createdCompany); - } - }; - - return ( - <> - {newRecord.isCreating && newRecord.position === position && ( - - handleCreateSuccess(position, columnId, false)} - onRecordSelected={(company) => - company ? handleEntitySelect(position, company) : null - } - objectNameSingular={CoreObjectNameSingular.Company} - onCreate={createCompanyOpportunityAndOpenCommandMenu} - /> - - )} - - ); -}; diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnNewRecord.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnNewRecord.tsx deleted file mode 100644 index a1527a0fc..000000000 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnNewRecord.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { RecordBoardCard } from '@/object-record/record-board/record-board-card/components/RecordBoardCard'; -import { useAddNewCard } from '@/object-record/record-board/record-board-column/hooks/useAddNewCard'; -import { recordBoardNewRecordByColumnIdSelector } from '@/object-record/record-board/states/selectors/recordBoardNewRecordByColumnIdSelector'; -import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission'; -import { useRecoilValue } from 'recoil'; - -export const RecordBoardColumnNewRecord = ({ - columnId, - position, -}: { - columnId: string; - position: 'first' | 'last'; -}) => { - const newRecord = useRecoilValue( - recordBoardNewRecordByColumnIdSelector({ - familyKey: columnId, - scopeId: columnId, - }), - ); - - const { handleCreateSuccess } = useAddNewCard({ - recordPickerComponentInstanceId: `add-new-card-record-picker-column-${columnId}`, - }); - - const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission(); - - if (hasObjectReadOnlyPermission) { - return null; - } - - return ( - <> - {newRecord.isCreating && newRecord.position === position && ( - handleCreateSuccess(position)} - position={position} - /> - )} - - ); -}; diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnNewRecordButton.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnNewRecordButton.tsx index a30c9d77d..fd00b51b0 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnNewRecordButton.tsx +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnNewRecordButton.tsx @@ -1,7 +1,10 @@ -import { useColumnNewCardActions } from '@/object-record/record-board/record-board-column/hooks/useColumnNewCardActions'; +import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext'; +import { RecordBoardColumnContext } from '@/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext'; +import { useCreateNewIndexRecord } from '@/object-record/record-table/hooks/useCreateNewIndexRecord'; import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; +import { useContext } from 'react'; import { IconPlus } from 'twenty-ui'; const StyledNewButton = styled.button` @@ -21,23 +24,33 @@ const StyledNewButton = styled.button` } `; -export const RecordBoardColumnNewRecordButton = ({ - columnId, -}: { - columnId: string; -}) => { +export const RecordBoardColumnNewRecordButton = () => { const theme = useTheme(); - const { handleNewButtonClick } = useColumnNewCardActions(columnId); + const { objectMetadataItem, selectFieldMetadataItem } = + useContext(RecordBoardContext); + + const { columnDefinition } = useContext(RecordBoardColumnContext); const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission(); + const { createNewIndexRecord } = useCreateNewIndexRecord({ + objectMetadataItem: objectMetadataItem, + }); + if (hasObjectReadOnlyPermission) { return null; } return ( - handleNewButtonClick('last', false)}> + { + createNewIndexRecord({ + position: 'last', + [selectFieldMetadataItem.name]: columnDefinition.value, + }); + }} + > New diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/hooks/useAddNewCard.ts b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/hooks/useAddNewCard.ts deleted file mode 100644 index a96c57566..000000000 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/hooks/useAddNewCard.ts +++ /dev/null @@ -1,235 +0,0 @@ -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 { useSingleRecordPickerSearch } from '@/object-record/record-picker/single-record-picker/hooks/useSingleRecordPickerSearch'; -import { SingleRecordPickerHotkeyScope } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerHotkeyScope'; -import { SingleRecordPickerRecord } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerRecord'; -import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; -import { useCallback, useContext } from 'react'; -import { RecoilState, useRecoilCallback } from 'recoil'; -import { isDefined } from 'twenty-shared'; -import { v4 as uuidv4 } from 'uuid'; -import { FieldMetadataType } from '~/generated-metadata/graphql'; - -type SetFunction = ( - recoilVal: RecoilState, - valOrUpdater: T | ((currVal: T) => T), -) => void; - -type UseAddNewCardProps = { - recordPickerComponentInstanceId: string; -}; - -export const useAddNewCard = ({ - recordPickerComponentInstanceId, -}: UseAddNewCardProps) => { - const columnContext = useContext(RecordBoardColumnContext); - const { createOneRecord, selectFieldMetadataItem, objectMetadataItem } = - useContext(RecordBoardContext); - const { resetSearchFilter } = useSingleRecordPickerSearch( - recordPickerComponentInstanceId, - ); - - const { - goBackToPreviousHotkeyScope, - setHotkeyScopeAndMemorizePreviousScope, - } = usePreviousHotkeyScope(); - - const getColumnDefinitionId = useCallback( - (columnId?: string) => { - const columnDefinitionId = columnId || columnContext?.columnDefinition.id; - if (!columnDefinitionId) { - throw new Error('Column ID is required'); - } - return columnDefinitionId; - }, - [columnContext], - ); - - const addNewItem = useCallback( - ( - set: SetFunction, - columnDefinitionId: string, - position: 'first' | 'last', - isOpportunity: boolean, - ) => { - set( - recordBoardNewRecordByColumnIdSelector({ - familyKey: columnDefinitionId, - scopeId: columnDefinitionId, - }), - { - id: uuidv4(), - columnId: columnDefinitionId, - isCreating: true, - position, - isOpportunity, - company: null, - }, - ); - }, - [], - ); - - const createRecord = useCallback( - ( - labelValue: string, - position: 'first' | 'last', - isOpportunity: boolean, - company?: SingleRecordPickerRecord, - ) => { - if ( - (isOpportunity && company !== null) || - (!isOpportunity && labelValue !== '') - ) { - // TODO: Refactor this whole section (Add new card): this should be: - // - simpler - // - piloted by metadata, - // - avoid drill down props, especially internal stuff - // - and follow record table pending record creation logic - let computedLabelIdentifierValue: any = labelValue; - - const labelIdentifierField = objectMetadataItem?.fields.find( - (field) => - field.id === objectMetadataItem.labelIdentifierFieldMetadataId, - ); - - if (!isDefined(labelIdentifierField)) { - throw new Error('Label identifier field not found'); - } - - if (labelIdentifierField.type === FieldMetadataType.FULL_NAME) { - computedLabelIdentifierValue = { - firstName: labelValue, - lastName: '', - }; - } - - createOneRecord({ - [selectFieldMetadataItem.name]: columnContext?.columnDefinition.value, - position, - ...(isOpportunity - ? { companyId: company?.id, name: company?.name } - : { - [labelIdentifierField.name]: computedLabelIdentifierValue, - }), - }); - } - }, - [ - objectMetadataItem?.fields, - objectMetadataItem?.labelIdentifierFieldMetadataId, - createOneRecord, - selectFieldMetadataItem?.name, - columnContext?.columnDefinition?.value, - ], - ); - - const handleAddNewCardClick = useRecoilCallback( - ({ set }) => - ( - labelValue: string, - position: 'first' | 'last', - isOpportunity: boolean, - columnId?: string, - ): void => { - const columnDefinitionId = getColumnDefinitionId(columnId); - addNewItem(set, columnDefinitionId, position, isOpportunity); - if (isOpportunity) { - setHotkeyScopeAndMemorizePreviousScope( - SingleRecordPickerHotkeyScope.SingleRecordPicker, - ); - } else { - createRecord(labelValue, position, isOpportunity); - } - }, - [ - addNewItem, - createRecord, - getColumnDefinitionId, - setHotkeyScopeAndMemorizePreviousScope, - ], - ); - - const handleCreateSuccess = useRecoilCallback( - ({ set }) => - ( - position: 'first' | 'last', - columnId?: string, - isOpportunity = false, - ): void => { - const columnDefinitionId = getColumnDefinitionId(columnId); - set( - recordBoardNewRecordByColumnIdSelector({ - familyKey: columnDefinitionId, - scopeId: columnDefinitionId, - }), - { - id: '', - columnId: columnDefinitionId, - isCreating: false, - position, - isOpportunity: Boolean(isOpportunity), - company: null, - }, - ); - resetSearchFilter(); - if (isOpportunity === true) { - goBackToPreviousHotkeyScope(); - } - }, - [getColumnDefinitionId, goBackToPreviousHotkeyScope, resetSearchFilter], - ); - - const handleCreate = ( - labelValue: string, - position: 'first' | 'last', - onCreateSuccess?: () => void, - ) => { - if (labelValue.trim() !== '' && position !== undefined) { - handleAddNewCardClick(labelValue.trim(), position, false, ''); - onCreateSuccess?.(); - } - }; - - const handleBlur = ( - labelValue: string, - position: 'first' | 'last', - onCreateSuccess?: () => void, - ) => { - if (labelValue.trim() === '') { - onCreateSuccess?.(); - } else { - handleCreate(labelValue, position, onCreateSuccess); - } - }; - - const handleInputEnter = ( - labelValue: string, - position: 'first' | 'last', - onCreateSuccess?: () => void, - ) => { - handleCreate(labelValue, position, onCreateSuccess); - }; - - const handleEntitySelect = useCallback( - ( - position: 'first' | 'last', - company: SingleRecordPickerRecord, - columnId?: string, - ) => { - const columnDefinitionId = getColumnDefinitionId(columnId); - createRecord('', position, true, company); - handleCreateSuccess(position, columnDefinitionId, true); - }, - [createRecord, handleCreateSuccess, getColumnDefinitionId], - ); - - return { - handleAddNewCardClick, - handleCreateSuccess, - handleBlur, - handleInputEnter, - handleEntitySelect, - }; -}; diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/hooks/useColumnNewCardActions.ts b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/hooks/useColumnNewCardActions.ts deleted file mode 100644 index d43cc7ecf..000000000 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/hooks/useColumnNewCardActions.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { useAddNewCard } from '@/object-record/record-board/record-board-column/hooks/useAddNewCard'; - -export const useColumnNewCardActions = (columnId: string) => { - const { handleAddNewCardClick } = useAddNewCard({ - recordPickerComponentInstanceId: `add-new-card-record-picker-column-${columnId}`, - }); - - const handleNewButtonClick = ( - position: 'first' | 'last', - isOpportunity: boolean, - ) => { - handleAddNewCardClick('', position, isOpportunity, columnId); - }; - - return { - handleNewButtonClick, - }; -}; diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/hooks/useIsOpportunitiesCompanyFieldDisabled.ts b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/hooks/useIsOpportunitiesCompanyFieldDisabled.ts deleted file mode 100644 index f2895e819..000000000 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/hooks/useIsOpportunitiesCompanyFieldDisabled.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; -import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; - -export const useIsOpportunitiesCompanyFieldDisabled = () => { - const { objectMetadataItem: opportunityMetadataItem } = useObjectMetadataItem( - { - objectNameSingular: CoreObjectNameSingular.Opportunity, - }, - ); - const isOpportunitiesCompanyFieldDisabled = - !opportunityMetadataItem.fields.find( - (field) => field.name === CoreObjectNameSingular.Company, - )?.isActive || false; - return { - isOpportunitiesCompanyFieldDisabled, - }; -}; diff --git a/packages/twenty-front/src/modules/object-record/record-board/states/recordBoardNewRecordByColumnIdComponentFamilyState.ts b/packages/twenty-front/src/modules/object-record/record-board/states/recordBoardNewRecordByColumnIdComponentFamilyState.ts deleted file mode 100644 index 49251ad9e..000000000 --- a/packages/twenty-front/src/modules/object-record/record-board/states/recordBoardNewRecordByColumnIdComponentFamilyState.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { SingleRecordPickerRecord } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerRecord'; -import { createComponentFamilyState } from '@/ui/utilities/state/component-state/utils/createComponentFamilyState'; - -export type NewCard = { - id: string; - columnId: string; - isCreating: boolean; - position: 'first' | 'last'; - isOpportunity: boolean; - company: SingleRecordPickerRecord | null; -}; - -export const recordBoardNewRecordByColumnIdComponentFamilyState = - createComponentFamilyState({ - key: 'recordBoardNewRecordByColumnIdComponentFamilyState', - defaultValue: { - id: '', - columnId: '', - isCreating: false, - position: 'last', - isOpportunity: false, - company: null, - }, - }); diff --git a/packages/twenty-front/src/modules/object-record/record-board/states/selectors/recordBoardNewRecordByColumnIdSelector.ts b/packages/twenty-front/src/modules/object-record/record-board/states/selectors/recordBoardNewRecordByColumnIdSelector.ts deleted file mode 100644 index 122daffa3..000000000 --- a/packages/twenty-front/src/modules/object-record/record-board/states/selectors/recordBoardNewRecordByColumnIdSelector.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { createComponentFamilySelector } from '@/ui/utilities/state/component-state/utils/createComponentFamilySelector'; -import { - NewCard, - recordBoardNewRecordByColumnIdComponentFamilyState, -} from '../recordBoardNewRecordByColumnIdComponentFamilyState'; - -export const recordBoardNewRecordByColumnIdSelector = - createComponentFamilySelector({ - key: 'recordBoardNewRecordByColumnIdSelector', - get: - ({ familyKey, scopeId }: { familyKey: string; scopeId: string }) => - ({ get }) => { - return get( - recordBoardNewRecordByColumnIdComponentFamilyState({ - familyKey, - scopeId, - }), - ) as NewCard; - }, - set: - ({ familyKey, scopeId }: { familyKey: string; scopeId: string }) => - ({ set }, newValue) => { - set( - recordBoardNewRecordByColumnIdComponentFamilyState({ - familyKey, - scopeId, - }), - newValue as NewCard, - ); - }, - }); diff --git a/packages/twenty-front/src/modules/object-record/record-field/hooks/useIsFieldValueReadOnly.ts b/packages/twenty-front/src/modules/object-record/record-field/hooks/useIsFieldValueReadOnly.ts index d6146718c..a9b8aa580 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/hooks/useIsFieldValueReadOnly.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/hooks/useIsFieldValueReadOnly.ts @@ -2,11 +2,12 @@ import { useContext } from 'react'; import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; -import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; +import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector'; import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilValue } from 'recoil'; +import { isDefined } from 'twenty-shared'; import { FieldContext } from '../contexts/FieldContext'; import { isFieldValueReadOnly } from '../utils/isFieldValueReadOnly'; @@ -15,8 +16,11 @@ export const useIsFieldValueReadOnly = () => { const { metadata, type } = fieldDefinition; - const recordFromStore = useRecoilValue( - recordStoreFamilyState(recordId), + const recordDeletedAt = useRecoilValue( + recordStoreFamilySelector({ + recordId, + fieldName: 'deletedAt', + }), ); const contextStoreCurrentViewType = useRecoilComponentValueV2( @@ -34,7 +38,7 @@ export const useIsFieldValueReadOnly = () => { fieldName: metadata.fieldName, fieldType: type, isObjectRemote: objectMetadataItem.isRemote, - isRecordDeleted: recordFromStore?.deletedAt, + isRecordDeleted: isDefined(recordDeletedAt), hasObjectReadOnlyPermission, contextStoreCurrentViewType, }); diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/RelationFromManyFieldDisplay.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/RelationFromManyFieldDisplay.tsx index cfa51862f..f238be043 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/RelationFromManyFieldDisplay.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/RelationFromManyFieldDisplay.tsx @@ -18,7 +18,7 @@ export const RelationFromManyFieldDisplay = () => { fieldDefinition?.metadata.relationObjectMetadataNameSingular; const { activityTargetObjectRecords } = useActivityTargetObjectRecords( - undefined, + '', fieldValue as NoteTarget[] | TaskTarget[], ); diff --git a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexAddRecordInGroupDropdown.tsx b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexAddRecordInGroupDropdown.tsx index cd310e58e..03ffe793c 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexAddRecordInGroupDropdown.tsx +++ b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexAddRecordInGroupDropdown.tsx @@ -3,7 +3,7 @@ import { availableRecordGroupIdsComponentSelector } from '@/object-record/record import { RecordGroupDefinition } from '@/object-record/record-group/types/RecordGroupDefinition'; import { RecordIndexPageKanbanAddMenuItem } from '@/object-record/record-index/components/RecordIndexPageKanbanAddMenuItem'; import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext'; -import { useCreateNewTableRecordInGroup } from '@/object-record/record-table/hooks/useCreateNewTableRecordInGroup'; +import { useCreateNewIndexRecord } from '@/object-record/record-table/hooks/useCreateNewIndexRecord'; import { isRecordGroupTableSectionToggledComponentState } from '@/object-record/record-table/record-table-section/states/isRecordGroupTableSectionToggledComponentState'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; @@ -44,23 +44,31 @@ export const RecordIndexAddRecordInGroupDropdown = ({ (field) => field.id === recordGroupFieldMetadata?.id, ); - const { createNewTableRecordInGroup } = useCreateNewTableRecordInGroup(); - const { closeDropdown } = useDropdown(dropdownId); + const { createNewIndexRecord } = useCreateNewIndexRecord({ + objectMetadataItem, + }); + const handleCreateNewTableRecordInGroup = useRecoilCallback( ({ set }) => (recordGroup: RecordGroupDefinition) => { set(isRecordGroupTableSectionToggledState(recordGroup.id), true); - createNewTableRecordInGroup(recordGroup.id); setActiveDropdownFocusIdAndMemorizePrevious(null); + if (!selectFieldMetadataItem) { + return; + } + createNewIndexRecord({ + [selectFieldMetadataItem.name]: recordGroup.value, + }); closeDropdown(); }, [ - closeDropdown, - createNewTableRecordInGroup, - setActiveDropdownFocusIdAndMemorizePrevious, isRecordGroupTableSectionToggledState, + setActiveDropdownFocusIdAndMemorizePrevious, + selectFieldMetadataItem, + createNewIndexRecord, + closeDropdown, ], ); diff --git a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexPageKanbanAddButton.tsx b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexPageKanbanAddButton.tsx deleted file mode 100644 index fa5186354..000000000 --- a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexPageKanbanAddButton.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; -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 { visibleRecordGroupIdsComponentFamilySelector } from '@/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentFamilySelector'; -import { RecordGroupDefinition } from '@/object-record/record-group/types/RecordGroupDefinition'; -import { RecordIndexPageKanbanAddMenuItem } from '@/object-record/record-index/components/RecordIndexPageKanbanAddMenuItem'; -import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext'; -import { recordIndexKanbanFieldMetadataIdState } from '@/object-record/record-index/states/recordIndexKanbanFieldMetadataIdState'; -import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; -import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; -import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; -import { PageAddButton } from '@/ui/layout/page/components/PageAddButton'; -import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2'; -import { ViewType } from '@/views/types/ViewType'; -import { useCallback } from 'react'; -import { useRecoilValue } from 'recoil'; - -export const RecordIndexPageKanbanAddButton = () => { - const dropdownId = `record-index-page-add-button-dropdown`; - - const { objectMetadataItem } = useRecordIndexContextOrThrow(); - - const visibleRecordGroupIds = useRecoilComponentFamilyValueV2( - visibleRecordGroupIdsComponentFamilySelector, - ViewType.Kanban, - ); - - const recordIndexKanbanFieldMetadataId = useRecoilValue( - recordIndexKanbanFieldMetadataIdState, - ); - - const selectFieldMetadataItem = objectMetadataItem.fields.find( - (field) => field.id === recordIndexKanbanFieldMetadataId, - ); - const isOpportunity = - objectMetadataItem.nameSingular === CoreObjectNameSingular.Opportunity; - - const { closeDropdown } = useDropdown(dropdownId); - const { isOpportunitiesCompanyFieldDisabled } = - useIsOpportunitiesCompanyFieldDisabled(); - const { handleAddNewCardClick } = useAddNewCard({ - recordPickerComponentInstanceId: `add-new-card-record-picker`, - }); - - const handleItemClick = useCallback( - (columnDefinition: RecordGroupDefinition) => { - const isOpportunityEnabled = - isOpportunity && !isOpportunitiesCompanyFieldDisabled; - handleAddNewCardClick( - '', - 'first', - isOpportunityEnabled, - columnDefinition.id, - ); - closeDropdown(); - }, - [ - isOpportunity, - handleAddNewCardClick, - closeDropdown, - isOpportunitiesCompanyFieldDisabled, - ], - ); - - if (!selectFieldMetadataItem) { - return null; - } - - return ( - } - dropdownId={dropdownId} - dropdownComponents={ - - {visibleRecordGroupIds.map((recordGroupId) => ( - - ))} - - } - dropdownHotkeyScope={{ scope: dropdownId }} - /> - ); -}; diff --git a/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCell.tsx b/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCell.tsx index 9171b03bc..0c44c7574 100644 --- a/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCell.tsx +++ b/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCell.tsx @@ -6,11 +6,9 @@ import { FieldInput } from '@/object-record/record-field/components/FieldInput'; import { FieldContext } from '@/object-record/record-field/contexts/FieldContext'; import { FieldFocusContextProvider } from '@/object-record/record-field/contexts/FieldFocusContextProvider'; import { useGetButtonIcon } from '@/object-record/record-field/hooks/useGetButtonIcon'; -import { useIsFieldInputOnly } from '@/object-record/record-field/hooks/useIsFieldInputOnly'; import { FieldInputEvent } from '@/object-record/record-field/types/FieldInputEvent'; -import { useInlineCell } from '../hooks/useInlineCell'; - +import { useIsFieldInputOnly } from '@/object-record/record-field/hooks/useIsFieldInputOnly'; import { useIsFieldValueReadOnly } from '@/object-record/record-field/hooks/useIsFieldValueReadOnly'; import { useOpenFieldInputEditMode } from '@/object-record/record-field/hooks/useOpenFieldInputEditMode'; import { FieldInputClickOutsideEvent } from '@/object-record/record-field/meta-types/input/components/DateTimeFieldInput'; @@ -18,6 +16,7 @@ import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinit import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata'; import { isFieldRelation } from '@/object-record/record-field/types/guards/isFieldRelation'; import { isFieldSelect } from '@/object-record/record-field/types/guards/isFieldSelect'; +import { useInlineCell } from '@/object-record/record-inline-cell/hooks/useInlineCell'; import { MultipleRecordPickerHotkeyScope } from '@/object-record/record-picker/multiple-record-picker/types/MultipleRecordPickerHotkeyScope'; import { SingleRecordPickerHotkeyScope } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerHotkeyScope'; import { SelectFieldHotkeyScope } from '@/object-record/select/types/SelectFieldHotkeyScope'; @@ -28,7 +27,6 @@ import { RecordInlineCellContext, RecordInlineCellContextProps, } from './RecordInlineCellContext'; - type RecordInlineCellProps = { readonly?: boolean; loading?: boolean; diff --git a/packages/twenty-front/src/modules/object-record/record-right-drawer/components/RightDrawerRecord.tsx b/packages/twenty-front/src/modules/object-record/record-right-drawer/components/RightDrawerRecord.tsx deleted file mode 100644 index 3a0c79f60..000000000 --- a/packages/twenty-front/src/modules/object-record/record-right-drawer/components/RightDrawerRecord.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import { useRecoilValue } from 'recoil'; - -import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext'; -import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext'; -import { RecordFilterGroupsComponentInstanceContext } from '@/object-record/record-filter-group/states/context/RecordFilterGroupsComponentInstanceContext'; -import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext'; -import { RIGHT_DRAWER_RECORD_INSTANCE_ID } from '@/object-record/record-right-drawer/constants/RightDrawerRecordInstanceId'; -import { isNewViewableRecordLoadingState } from '@/object-record/record-right-drawer/states/isNewViewableRecordLoading'; -import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState'; -import { viewableRecordNameSingularState } from '@/object-record/record-right-drawer/states/viewableRecordNameSingularState'; -import { RecordShowContainer } from '@/object-record/record-show/components/RecordShowContainer'; -import { useRecordShowPage } from '@/object-record/record-show/hooks/useRecordShowPage'; -import { RecordSortsComponentInstanceContext } from '@/object-record/record-sort/states/context/RecordSortsComponentInstanceContext'; -import { RecordValueSetterEffect } from '@/object-record/record-store/components/RecordValueSetterEffect'; -import { RecordFieldValueSelectorContextProvider } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext'; -import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; -import styled from '@emotion/styled'; - -const StyledRightDrawerRecord = styled.div<{ isMobile: boolean }>` - height: ${({ theme, isMobile }) => - isMobile ? `calc(100% - ${theme.spacing(16)})` : '100%'}; -`; - -export const RightDrawerRecord = () => { - const isMobile = useIsMobile(); - - const viewableRecordNameSingular = useRecoilValue( - viewableRecordNameSingularState, - ); - const isNewViewableRecordLoading = useRecoilValue( - isNewViewableRecordLoadingState, - ); - const viewableRecordId = useRecoilValue(viewableRecordIdState); - - if (!viewableRecordNameSingular && !isNewViewableRecordLoading) { - throw new Error(`Object name is not defined`); - } - - const { objectNameSingular, objectRecordId } = useRecordShowPage( - viewableRecordNameSingular ?? '', - viewableRecordId ?? '', - ); - - return ( - - - - - - - - {!isNewViewableRecordLoading && ( - - )} - - - - - - - - - ); -}; diff --git a/packages/twenty-front/src/modules/object-record/record-right-drawer/components/RightDrawerTitleRecordInlineCell.tsx b/packages/twenty-front/src/modules/object-record/record-right-drawer/components/RightDrawerTitleRecordInlineCell.tsx deleted file mode 100644 index 5eb130f15..000000000 --- a/packages/twenty-front/src/modules/object-record/record-right-drawer/components/RightDrawerTitleRecordInlineCell.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { FieldContext } from '@/object-record/record-field/contexts/FieldContext'; -import { usePersistField } from '@/object-record/record-field/hooks/usePersistField'; -import { useRecordFieldInput } from '@/object-record/record-field/hooks/useRecordFieldInput'; -import { RecordInlineCell } from '@/object-record/record-inline-cell/components/RecordInlineCell'; -import { useInlineCell } from '@/object-record/record-inline-cell/hooks/useInlineCell'; -import { useListenRightDrawerClose } from '@/ui/layout/right-drawer/hooks/useListenRightDrawerClose'; -import { useContext } from 'react'; -import { useRecoilValue } from 'recoil'; - -// TODO: this should be better implemented if we refactor field input so that it's easier to implement logic like that -// Idea : maybe we could use draftValue instead of the newValue in the events -// Idea : we can remove all our listeners in the many input types and replace them with a onClose event that gives the event type -// (tab, shift-tab, click-outside, escape, enter) and the newValue, that will reduce the boilerplate -// and also the need to have our difficult to understand persist logic -// the goal would be that we could easily call usePersistField anywhere under a FieldContext and it would work -export const RightDrawerTitleRecordInlineCell = () => { - const { closeInlineCell } = useInlineCell(); - const persistField = usePersistField(); - - const { recordId, fieldDefinition } = useContext(FieldContext); - - const { getDraftValueSelector } = useRecordFieldInput( - `${recordId}-${fieldDefinition.metadata.fieldName}`, - ); - - const draftValue = useRecoilValue(getDraftValueSelector()); - - useListenRightDrawerClose(() => { - if (draftValue !== undefined) { - persistField(draftValue); - } - closeInlineCell(); - }); - - return ; -}; diff --git a/packages/twenty-front/src/modules/object-record/record-right-drawer/constants/RightDrawerRecordInstanceId.ts b/packages/twenty-front/src/modules/object-record/record-right-drawer/constants/RightDrawerRecordInstanceId.ts deleted file mode 100644 index c01b319ee..000000000 --- a/packages/twenty-front/src/modules/object-record/record-right-drawer/constants/RightDrawerRecordInstanceId.ts +++ /dev/null @@ -1 +0,0 @@ -export const RIGHT_DRAWER_RECORD_INSTANCE_ID = 'right-drawer-record'; diff --git a/packages/twenty-front/src/modules/object-record/record-show/components/FieldsCard.tsx b/packages/twenty-front/src/modules/object-record/record-show/components/FieldsCard.tsx index 66baa1fb6..a313f55a9 100644 --- a/packages/twenty-front/src/modules/object-record/record-show/components/FieldsCard.tsx +++ b/packages/twenty-front/src/modules/object-record/record-show/components/FieldsCard.tsx @@ -1,8 +1,8 @@ import groupBy from 'lodash.groupby'; import { ActivityTargetsInlineCell } from '@/activities/inline-cell/components/ActivityTargetsInlineCell'; -import { Note } from '@/activities/types/Note'; -import { Task } from '@/activities/types/Task'; +import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; +import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition'; import { FieldContext } from '@/object-record/record-field/contexts/FieldContext'; @@ -15,7 +15,6 @@ import { useRecordShowContainerData } from '@/object-record/record-show/hooks/us import { RecordDetailDuplicatesSection } from '@/object-record/record-show/record-detail-section/components/RecordDetailDuplicatesSection'; import { RecordDetailRelationSection } from '@/object-record/record-show/record-detail-section/components/RecordDetailRelationSection'; import { isFieldCellSupported } from '@/object-record/utils/isFieldCellSupported'; -import { isDefined } from 'twenty-shared'; import { FieldMetadataType } from '~/generated/graphql'; type FieldsCardProps = { @@ -27,22 +26,20 @@ export const FieldsCard = ({ objectNameSingular, objectRecordId, }: FieldsCardProps) => { - const { - recordFromStore, - recordLoading, - objectMetadataItem, - labelIdentifierFieldMetadataItem, - isPrefetchLoading, - objectMetadataItems, - } = useRecordShowContainerData({ + const { recordLoading, labelIdentifierFieldMetadataItem, isPrefetchLoading } = + useRecordShowContainerData({ + objectNameSingular, + objectRecordId, + }); + + const { objectMetadataItem } = useObjectMetadataItem({ objectNameSingular, - objectRecordId, }); + const { objectMetadataItems } = useObjectMetadataItems(); const { useUpdateOneObjectRecordMutation } = useRecordShowContainerActions({ objectNameSingular, objectRecordId, - recordFromStore, }); const availableFieldMetadataItems = objectMetadataItem.fields @@ -87,100 +84,94 @@ export const FieldsCard = ({ return ( <> - {isDefined(recordFromStore) && ( - <> - - {isPrefetchLoading ? ( - - ) : ( - <> - {inlineRelationFieldMetadataItems?.map( - (fieldMetadataItem, index) => ( - - - - ), - )} - {inlineFieldMetadataItems?.map((fieldMetadataItem, index) => ( - - - - ))} - + + {isPrefetchLoading ? ( + + ) : ( + <> + {inlineRelationFieldMetadataItems?.map( + (fieldMetadataItem, index) => ( + + + + ), )} - - ( + + + + ))} + + )} + + + {boxedRelationFieldMetadataItems?.map((fieldMetadataItem, index) => ( + + - {boxedRelationFieldMetadataItems?.map((fieldMetadataItem, index) => ( - - - - ))} - - )} + + ))} ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-show/components/ObjectRecordShowPageBreadcrumb.tsx b/packages/twenty-front/src/modules/object-record/record-show/components/ObjectRecordShowPageBreadcrumb.tsx index 327444268..a30d1fadd 100644 --- a/packages/twenty-front/src/modules/object-record/record-show/components/ObjectRecordShowPageBreadcrumb.tsx +++ b/packages/twenty-front/src/modules/object-record/record-show/components/ObjectRecordShowPageBreadcrumb.tsx @@ -41,7 +41,7 @@ export const ObjectRecordShowPageBreadcrumb = ({ objectLabelPlural: string; labelIdentifierFieldMetadataItem?: FieldMetadataItem; }) => { - const { record, loading } = useFindOneRecord({ + const { loading } = useFindOneRecord({ objectNameSingular, objectRecordId, recordGqlFields: { @@ -52,7 +52,6 @@ export const ObjectRecordShowPageBreadcrumb = ({ const { useUpdateOneObjectRecordMutation } = useRecordShowContainerActions({ objectNameSingular, objectRecordId, - recordFromStore: record ?? null, }); if (loading) { diff --git a/packages/twenty-front/src/modules/object-record/record-show/components/RecordShowContainer.tsx b/packages/twenty-front/src/modules/object-record/record-show/components/RecordShowContainer.tsx index 0e572e3cc..f9bd7e541 100644 --- a/packages/twenty-front/src/modules/object-record/record-show/components/RecordShowContainer.tsx +++ b/packages/twenty-front/src/modules/object-record/record-show/components/RecordShowContainer.tsx @@ -3,10 +3,13 @@ import { ShowPageContainer } from '@/ui/layout/page/components/ShowPageContainer import { InformationBannerDeletedRecord } from '@/information-banner/components/deleted-record/InformationBannerDeletedRecord'; +import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { RecordShowContainerContextStoreTargetedRecordsEffect } from '@/object-record/record-show/components/RecordShowContainerContextStoreTargetedRecordsEffect'; import { useRecordShowContainerData } from '@/object-record/record-show/hooks/useRecordShowContainerData'; import { useRecordShowContainerTabs } from '@/object-record/record-show/hooks/useRecordShowContainerTabs'; +import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector'; import { ShowPageSubContainer } from '@/ui/layout/show-page/components/ShowPageSubContainer'; +import { useRecoilValue } from 'recoil'; type RecordShowContainerProps = { objectNameSingular: string; @@ -23,16 +26,22 @@ export const RecordShowContainer = ({ isInRightDrawer = false, isNewRightDrawerItemLoading = false, }: RecordShowContainerProps) => { - const { - recordFromStore, - objectMetadataItem, - isPrefetchLoading, - recordLoading, - } = useRecordShowContainerData({ + const { objectMetadataItem } = useObjectMetadataItem({ + objectNameSingular, + }); + + const { isPrefetchLoading, recordLoading } = useRecordShowContainerData({ objectNameSingular, objectRecordId, }); + const recordDeletedAt = useRecoilValue( + recordStoreFamilySelector({ + recordId: objectRecordId, + fieldName: 'deletedAt', + }), + ); + const { layout, tabs } = useRecordShowContainerTabs( loading, objectNameSingular as CoreObjectNameSingular, @@ -45,7 +54,7 @@ export const RecordShowContainer = ({ - {recordFromStore && recordFromStore.deletedAt && ( + {recordDeletedAt && ( { + const { objectMetadataItem } = useObjectMetadataItem({ objectNameSingular }); + const { objectMetadataItems } = useObjectMetadataItems(); + + const FIND_ONE_RECORD_FOR_SHOW_PAGE_OPERATION_SIGNATURE = + buildFindOneRecordForShowPageOperationSignature({ + objectMetadataItem, + objectMetadataItems, + }); + + const { record } = useFindOneRecord({ + objectRecordId: recordId, + objectNameSingular, + recordGqlFields: FIND_ONE_RECORD_FOR_SHOW_PAGE_OPERATION_SIGNATURE.fields, + withSoftDeleted: true, + }); + + const [recordFromStore, setRecordFromStore] = useRecoilState( + recordStoreFamilyState(recordId), + ); + + useEffect(() => { + if (isDefined(record) && !isDeeplyEqual(record, recordFromStore)) { + setRecordFromStore(record); + } + }, [record, recordFromStore, setRecordFromStore]); + + return <>; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-show/components/SummaryCard.tsx b/packages/twenty-front/src/modules/object-record/record-show/components/SummaryCard.tsx index f855c8111..68d6c4a95 100644 --- a/packages/twenty-front/src/modules/object-record/record-show/components/SummaryCard.tsx +++ b/packages/twenty-front/src/modules/object-record/record-show/components/SummaryCard.tsx @@ -4,10 +4,13 @@ import { FieldContext } from '@/object-record/record-field/contexts/FieldContext import { InlineCellHotkeyScope } from '@/object-record/record-inline-cell/types/InlineCellHotkeyScope'; import { useRecordShowContainerActions } from '@/object-record/record-show/hooks/useRecordShowContainerActions'; import { useRecordShowContainerData } from '@/object-record/record-show/hooks/useRecordShowContainerData'; +import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector'; +import { recordStoreIdentifierFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreIdentifierSelector'; import { RecordTitleCell } from '@/object-record/record-title-cell/components/RecordTitleCell'; import { ShowPageSummaryCard } from '@/ui/layout/show-page/components/ShowPageSummaryCard'; import { ShowPageSummaryCardSkeletonLoader } from '@/ui/layout/show-page/components/ShowPageSummaryCardSkeletonLoader'; import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; +import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-shared'; import { FieldMetadataType } from '~/generated/graphql'; @@ -25,28 +28,36 @@ export const SummaryCard = ({ isNewRightDrawerItemLoading, isInRightDrawer, }: SummaryCardProps) => { - const { - recordFromStore, - recordLoading, - labelIdentifierFieldMetadataItem, - isPrefetchLoading, - recordIdentifier, - } = useRecordShowContainerData({ - objectNameSingular, - objectRecordId, - }); + const { recordLoading, labelIdentifierFieldMetadataItem, isPrefetchLoading } = + useRecordShowContainerData({ + objectNameSingular, + objectRecordId, + }); + + const recordCreatedAt = useRecoilValue( + recordStoreFamilySelector({ + recordId: objectRecordId, + fieldName: 'createdAt', + }), + ); const { onUploadPicture, useUpdateOneObjectRecordMutation } = useRecordShowContainerActions({ objectNameSingular, objectRecordId, - recordFromStore, }); const { Icon, IconColor } = useGetStandardObjectIcon(objectNameSingular); const isMobile = useIsMobile() || isInRightDrawer; - if (isNewRightDrawerItemLoading || !isDefined(recordFromStore)) { + const recordIdentifier = useRecoilValue( + recordStoreIdentifierFamilySelector({ + objectNameSingular, + recordId: objectRecordId, + }), + ); + + if (isNewRightDrawerItemLoading || !isDefined(recordCreatedAt)) { return ; } @@ -58,7 +69,7 @@ export const SummaryCard = ({ icon={Icon} iconColor={IconColor} avatarPlaceholder={recordIdentifier?.name ?? ''} - date={recordFromStore.createdAt ?? ''} + date={recordCreatedAt ?? ''} loading={isPrefetchLoading || recordLoading} title={ { const [uploadImage] = useUploadImageMutation(); const { updateOneRecord } = useUpdateOneRecord({ objectNameSingular }); @@ -47,7 +44,7 @@ export const useRecordShowContainerActions = ({ const avatarUrl = result?.data?.uploadImage; - if (!avatarUrl || isUndefinedOrNull(updateOneRecord) || !recordFromStore) { + if (!avatarUrl || isUndefinedOrNull(updateOneRecord)) { return; } diff --git a/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowContainerData.ts b/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowContainerData.ts index 15eeb056b..c1f951db8 100644 --- a/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowContainerData.ts +++ b/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowContainerData.ts @@ -1,12 +1,7 @@ import { useLabelIdentifierFieldMetadataItem } from '@/object-metadata/hooks/useLabelIdentifierFieldMetadataItem'; -import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; -import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; import { recordLoadingFamilyState } from '@/object-record/record-store/states/recordLoadingFamilyState'; -import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; -import { recordStoreIdentifierFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreIdentifierSelector'; -import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { useIsPrefetchLoading } from '@/prefetch/hooks/useIsPrefetchLoading'; -import { useRecoilState, useRecoilValue } from 'recoil'; +import { useRecoilState } from 'recoil'; type UseRecordShowContainerDataProps = { objectNameSingular: string; @@ -17,10 +12,6 @@ export const useRecordShowContainerData = ({ objectNameSingular, objectRecordId, }: UseRecordShowContainerDataProps) => { - const { objectMetadataItem } = useObjectMetadataItem({ - objectNameSingular, - }); - const { labelIdentifierFieldMetadataItem } = useLabelIdentifierFieldMetadataItem({ objectNameSingular, @@ -30,28 +21,11 @@ export const useRecordShowContainerData = ({ recordLoadingFamilyState(objectRecordId), ); - const [recordFromStore] = useRecoilState( - recordStoreFamilyState(objectRecordId), - ); - - const recordIdentifier = useRecoilValue( - recordStoreIdentifierFamilySelector({ - objectNameSingular, - recordId: objectRecordId, - }), - ); - const isPrefetchLoading = useIsPrefetchLoading(); - const { objectMetadataItems } = useObjectMetadataItems(); - return { - recordFromStore, recordLoading, - objectMetadataItem, labelIdentifierFieldMetadataItem, isPrefetchLoading, - recordIdentifier, - objectMetadataItems, }; }; diff --git a/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowContainerTabs.ts b/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowContainerTabs.ts index a4d175e41..c0655a912 100644 --- a/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowContainerTabs.ts +++ b/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowContainerTabs.ts @@ -8,14 +8,15 @@ import { RecordLayout } from '@/object-record/record-show/types/RecordLayout'; import { SingleTabProps } from '@/ui/layout/tab/components/TabList'; import { RecordLayoutTab } from '@/ui/layout/tab/types/RecordLayoutTab'; import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; +import { useMemo } from 'react'; import { useRecoilValue } from 'recoil'; import { IconCalendarEvent, + IconHome, IconMail, IconNotes, IconPrinter, IconSettings, - IconHome, } from 'twenty-ui'; import { FieldMetadataType } from '~/generated-metadata/graphql'; import { FeatureFlagKey } from '~/generated/graphql'; @@ -34,195 +35,200 @@ export const useRecordShowContainerTabs = ( // Object-specific layouts that override or extend the base layout const OBJECT_SPECIFIC_LAYOUTS: Partial< Record - > = { - [CoreObjectNameSingular.Note]: { - tabs: { - richText: { - title: 'Note', - position: 101, - Icon: IconNotes, - cards: [{ type: CardType.RichTextCard }], - hide: { - ifMobile: false, - ifDesktop: false, - ifInRightDrawer: false, - ifFeaturesDisabled: [], - ifRequiredObjectsInactive: [], - ifRelationsMissing: [], + > = useMemo( + () => ({ + [CoreObjectNameSingular.Note]: { + tabs: { + richText: { + title: 'Note', + position: 101, + Icon: IconNotes, + cards: [{ type: CardType.RichTextCard }], + hide: { + ifMobile: false, + ifDesktop: false, + ifInRightDrawer: false, + ifFeaturesDisabled: [], + ifRequiredObjectsInactive: [], + ifRelationsMissing: [], + }, }, + tasks: null, + notes: null, }, - tasks: null, - notes: null, }, - }, - [CoreObjectNameSingular.Task]: { - tabs: { - richText: { - title: 'Note', - position: 101, - Icon: IconNotes, - cards: [{ type: CardType.RichTextCard }], - hide: { - ifMobile: false, - ifDesktop: false, - ifInRightDrawer: false, - ifFeaturesDisabled: [], - ifRequiredObjectsInactive: [], - ifRelationsMissing: [], + [CoreObjectNameSingular.Task]: { + tabs: { + richText: { + title: 'Note', + position: 101, + Icon: IconNotes, + cards: [{ type: CardType.RichTextCard }], + hide: { + ifMobile: false, + ifDesktop: false, + ifInRightDrawer: false, + ifFeaturesDisabled: [], + ifRequiredObjectsInactive: [], + ifRelationsMissing: [], + }, }, + tasks: null, + notes: null, }, - tasks: null, - notes: null, }, - }, - [CoreObjectNameSingular.Company]: { - tabs: { - emails: { - title: 'Emails', - position: 600, - Icon: IconMail, - cards: [{ type: CardType.EmailCard }], - hide: { - ifMobile: false, - ifDesktop: false, - ifInRightDrawer: false, - ifFeaturesDisabled: [], - ifRequiredObjectsInactive: [], - ifRelationsMissing: [], + [CoreObjectNameSingular.Company]: { + tabs: { + emails: { + title: 'Emails', + position: 600, + Icon: IconMail, + cards: [{ type: CardType.EmailCard }], + hide: { + ifMobile: false, + ifDesktop: false, + ifInRightDrawer: false, + ifFeaturesDisabled: [], + ifRequiredObjectsInactive: [], + ifRelationsMissing: [], + }, }, - }, - calendar: { - title: 'Calendar', - position: 700, - Icon: IconCalendarEvent, - cards: [{ type: CardType.CalendarCard }], - hide: { - ifMobile: false, - ifDesktop: false, - ifInRightDrawer: false, - ifFeaturesDisabled: [], - ifRequiredObjectsInactive: [], - ifRelationsMissing: [], + calendar: { + title: 'Calendar', + position: 700, + Icon: IconCalendarEvent, + cards: [{ type: CardType.CalendarCard }], + hide: { + ifMobile: false, + ifDesktop: false, + ifInRightDrawer: false, + ifFeaturesDisabled: [], + ifRequiredObjectsInactive: [], + ifRelationsMissing: [], + }, }, }, }, - }, - [CoreObjectNameSingular.Person]: { - tabs: { - emails: { - title: 'Emails', - position: 600, - Icon: IconMail, - cards: [{ type: CardType.EmailCard }], - hide: { - ifMobile: false, - ifDesktop: false, - ifInRightDrawer: false, - ifFeaturesDisabled: [], - ifRequiredObjectsInactive: [], - ifRelationsMissing: [], + [CoreObjectNameSingular.Person]: { + tabs: { + emails: { + title: 'Emails', + position: 600, + Icon: IconMail, + cards: [{ type: CardType.EmailCard }], + hide: { + ifMobile: false, + ifDesktop: false, + ifInRightDrawer: false, + ifFeaturesDisabled: [], + ifRequiredObjectsInactive: [], + ifRelationsMissing: [], + }, }, - }, - calendar: { - title: 'Calendar', - position: 700, - Icon: IconCalendarEvent, - cards: [{ type: CardType.CalendarCard }], - hide: { - ifMobile: false, - ifDesktop: false, - ifInRightDrawer: false, - ifFeaturesDisabled: [], - ifRequiredObjectsInactive: [], - ifRelationsMissing: [], + calendar: { + title: 'Calendar', + position: 700, + Icon: IconCalendarEvent, + cards: [{ type: CardType.CalendarCard }], + hide: { + ifMobile: false, + ifDesktop: false, + ifInRightDrawer: false, + ifFeaturesDisabled: [], + ifRequiredObjectsInactive: [], + ifRelationsMissing: [], + }, }, }, }, - }, - [CoreObjectNameSingular.Workflow]: { - hideSummaryAndFields: true, - tabs: { - workflow: { - title: 'Flow', - position: 0, - Icon: IconSettings, - cards: [{ type: CardType.WorkflowCard }], - hide: { - ifMobile: false, - ifDesktop: false, - ifInRightDrawer: false, - ifFeaturesDisabled: [FeatureFlagKey.IsWorkflowEnabled], - ifRequiredObjectsInactive: [], - ifRelationsMissing: [], + [CoreObjectNameSingular.Workflow]: { + hideSummaryAndFields: true, + tabs: { + workflow: { + title: 'Flow', + position: 0, + Icon: IconSettings, + cards: [{ type: CardType.WorkflowCard }], + hide: { + ifMobile: false, + ifDesktop: false, + ifInRightDrawer: false, + ifFeaturesDisabled: [FeatureFlagKey.IsWorkflowEnabled], + ifRequiredObjectsInactive: [], + ifRelationsMissing: [], + }, }, + timeline: null, + fields: null, }, - timeline: null, - fields: null, }, - }, - [CoreObjectNameSingular.WorkflowVersion]: { - tabs: { - workflowVersion: { - title: 'Flow', - position: 0, - Icon: IconSettings, - cards: [{ type: CardType.WorkflowVersionCard }], - hide: { - ifMobile: false, - ifDesktop: false, - ifInRightDrawer: false, - ifFeaturesDisabled: [FeatureFlagKey.IsWorkflowEnabled], - ifRequiredObjectsInactive: [], - ifRelationsMissing: [], + [CoreObjectNameSingular.WorkflowVersion]: { + tabs: { + workflowVersion: { + title: 'Flow', + position: 0, + Icon: IconSettings, + cards: [{ type: CardType.WorkflowVersionCard }], + hide: { + ifMobile: false, + ifDesktop: false, + ifInRightDrawer: false, + ifFeaturesDisabled: [FeatureFlagKey.IsWorkflowEnabled], + ifRequiredObjectsInactive: [], + ifRelationsMissing: [], + }, }, + timeline: null, }, - timeline: null, }, - }, - [CoreObjectNameSingular.WorkflowRun]: { - tabs: { - workflowRunOutput: { - title: 'Output', - position: 0, - Icon: IconPrinter, - cards: [{ type: CardType.WorkflowRunOutputCard }], - hide: { - ifMobile: false, - ifDesktop: false, - ifInRightDrawer: false, - ifFeaturesDisabled: [FeatureFlagKey.IsWorkflowEnabled], - ifRequiredObjectsInactive: [], - ifRelationsMissing: [], + [CoreObjectNameSingular.WorkflowRun]: { + tabs: { + workflowRunOutput: { + title: 'Output', + position: 0, + Icon: IconPrinter, + cards: [{ type: CardType.WorkflowRunOutputCard }], + hide: { + ifMobile: false, + ifDesktop: false, + ifInRightDrawer: false, + ifFeaturesDisabled: [FeatureFlagKey.IsWorkflowEnabled], + ifRequiredObjectsInactive: [], + ifRelationsMissing: [], + }, }, - }, - workflowRunFlow: { - title: 'Flow', - position: 0, - Icon: IconSettings, - cards: [{ type: CardType.WorkflowRunCard }], - hide: { - ifMobile: false, - ifDesktop: false, - ifInRightDrawer: false, - ifFeaturesDisabled: [FeatureFlagKey.IsWorkflowEnabled], - ifRequiredObjectsInactive: [], - ifRelationsMissing: [], + workflowRunFlow: { + title: 'Flow', + position: 0, + Icon: IconSettings, + cards: [{ type: CardType.WorkflowRunCard }], + hide: { + ifMobile: false, + ifDesktop: false, + ifInRightDrawer: false, + ifFeaturesDisabled: [FeatureFlagKey.IsWorkflowEnabled], + ifRequiredObjectsInactive: [], + ifRelationsMissing: [], + }, }, + timeline: null, }, - timeline: null, }, - }, - }; + }), + [], + ); // Merge base layout with object-specific layout - const recordLayout: RecordLayout = { - ...BASE_RECORD_LAYOUT, - ...(OBJECT_SPECIFIC_LAYOUTS[targetObjectNameSingular] || {}), - tabs: { - ...BASE_RECORD_LAYOUT.tabs, - ...(OBJECT_SPECIFIC_LAYOUTS[targetObjectNameSingular]?.tabs || {}), - }, - }; + const recordLayout: RecordLayout = useMemo(() => { + return { + ...BASE_RECORD_LAYOUT, + ...(OBJECT_SPECIFIC_LAYOUTS[targetObjectNameSingular] || {}), + tabs: { + ...BASE_RECORD_LAYOUT.tabs, + ...(OBJECT_SPECIFIC_LAYOUTS[targetObjectNameSingular]?.tabs || {}), + }, + }; + }, [OBJECT_SPECIFIC_LAYOUTS, targetObjectNameSingular]); return { layout: recordLayout, diff --git a/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowPage.ts b/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowPage.ts index a8759cea3..4513841d4 100644 --- a/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowPage.ts +++ b/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowPage.ts @@ -1,19 +1,8 @@ -import { useEffect } from 'react'; import { useParams } from 'react-router-dom'; -import { useSetRecoilState } from 'recoil'; import { useIcons } from 'twenty-ui'; -import { useCreateFavorite } from '@/favorites/hooks/useCreateFavorite'; -import { useDeleteFavorite } from '@/favorites/hooks/useDeleteFavorite'; -import { useFavorites } from '@/favorites/hooks/useFavorites'; -import { useLabelIdentifierFieldMetadataItem } from '@/object-metadata/hooks/useLabelIdentifierFieldMetadataItem'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; -import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; -import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord'; -import { buildFindOneRecordForShowPageOperationSignature } from '@/object-record/record-show/graphql/operations/factories/findOneRecordForShowPageOperationSignatureFactory'; -import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; -import { capitalize, isDefined } from 'twenty-shared'; -import { FieldMetadataType } from '~/generated-metadata/graphql'; +import { isDefined } from 'twenty-shared'; export const useRecordShowPage = ( propsObjectNameSingular: string, @@ -32,77 +21,13 @@ export const useRecordShowPage = ( } const { objectMetadataItem } = useObjectMetadataItem({ objectNameSingular }); - const { objectMetadataItems } = useObjectMetadataItems(); - const { labelIdentifierFieldMetadataItem } = - useLabelIdentifierFieldMetadataItem({ objectNameSingular }); - const { sortedFavorites: favorites } = useFavorites(); - const { createFavorite } = useCreateFavorite(); - const { deleteFavorite } = useDeleteFavorite(); - const setEntityFields = useSetRecoilState( - recordStoreFamilyState(objectRecordId), - ); const { getIcon } = useIcons(); const headerIcon = getIcon(objectMetadataItem?.icon); - const FIND_ONE_RECORD_FOR_SHOW_PAGE_OPERATION_SIGNATURE = - buildFindOneRecordForShowPageOperationSignature({ - objectMetadataItem, - objectMetadataItems, - }); - - const { record, loading } = useFindOneRecord({ - objectRecordId, - objectNameSingular, - recordGqlFields: FIND_ONE_RECORD_FOR_SHOW_PAGE_OPERATION_SIGNATURE.fields, - withSoftDeleted: true, - }); - - useEffect(() => { - if (isDefined(record)) { - setEntityFields(record); - } - }, [record, setEntityFields]); - - const correspondingFavorite = favorites.find( - (favorite) => favorite.recordId === objectRecordId, - ); - const isFavorite = isDefined(correspondingFavorite); - - const handleFavoriteButtonClick = async () => { - if (!objectNameSingular || !record) return; - - if (isFavorite) { - deleteFavorite(correspondingFavorite.id); - } else { - createFavorite(record, objectNameSingular); - } - }; - - const labelIdentifierFieldValue = - record?.[labelIdentifierFieldMetadataItem?.name ?? '']; - const pageName = - labelIdentifierFieldMetadataItem?.type === FieldMetadataType.FULL_NAME - ? [ - labelIdentifierFieldValue?.firstName, - labelIdentifierFieldValue?.lastName, - ].join(' ') - : isDefined(labelIdentifierFieldValue) - ? `${labelIdentifierFieldValue}` - : ''; - - const pageTitle = pageName.trim() - ? `${pageName} - ${capitalize(objectNameSingular)}` - : capitalize(objectNameSingular); return { objectNameSingular, objectRecordId, headerIcon, - loading, - pageTitle, - pageName, - isFavorite, - record, objectMetadataItem, - handleFavoriteButtonClick, }; }; diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTable.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTable.tsx index e7ad8f56a..a7ca5c948 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTable.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTable.tsx @@ -16,7 +16,6 @@ import { RecordTableRecordGroupBodyEffects } from '@/object-record/record-table/ import { RecordTableRecordGroupsBody } from '@/object-record/record-table/record-table-body/components/RecordTableRecordGroupsBody'; import { RecordTableHeader } from '@/object-record/record-table/record-table-header/components/RecordTableHeader'; import { isRecordTableInitialLoadingComponentState } from '@/object-record/record-table/states/isRecordTableInitialLoadingComponentState'; -import { hasPendingRecordComponentSelector } from '@/object-record/record-table/states/selectors/hasPendingRecordComponentSelector'; import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect'; import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useClickOutsideListener'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; @@ -52,20 +51,13 @@ export const RecordTable = () => { recordTableId, ); - const hasPendingRecord = useRecoilComponentValueV2( - hasPendingRecordComponentSelector, - recordTableId, - ); - const hasRecordGroups = useRecoilComponentValueV2( hasRecordGroupsComponentSelector, recordTableId, ); const recordTableIsEmpty = - !isRecordTableInitialLoading && - allRecordIds.length === 0 && - !hasPendingRecord; + !isRecordTableInitialLoading && allRecordIds.length === 0; const { resetTableRowSelection, setRowSelected } = useRecordTable({ recordTableId, diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableNoRecordGroupBodyContextProvider.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableNoRecordGroupBodyContextProvider.tsx index 9261b6137..7d57d626e 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableNoRecordGroupBodyContextProvider.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableNoRecordGroupBodyContextProvider.tsx @@ -1,7 +1,6 @@ import { RecordTableBodyContextProvider } from '@/object-record/record-table/contexts/RecordTableBodyContext'; import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; import { useHandleContainerMouseEnter } from '@/object-record/record-table/hooks/internal/useHandleContainerMouseEnter'; -import { useUpsertTableRecordNoGroup } from '@/object-record/record-table/hooks/internal/useUpsertTableRecordNoGroup'; import { useRecordTableMoveFocus } from '@/object-record/record-table/hooks/useRecordTableMoveFocus'; import { useCloseRecordTableCellNoGroup } from '@/object-record/record-table/record-table-cell/hooks/internal/useCloseRecordTableCellNoGroup'; import { useMoveSoftFocusToCurrentCellOnHover } from '@/object-record/record-table/record-table-cell/hooks/useMoveSoftFocusToCurrentCellOnHover'; @@ -23,20 +22,6 @@ export const RecordTableNoRecordGroupBodyContextProvider = ({ }: RecordTableNoRecordGroupBodyContextProviderProps) => { const { recordTableId } = useRecordTableContextOrThrow(); - const { upsertTableRecordNoGroup } = useUpsertTableRecordNoGroup(); - - const handleUpsertTableRecordNoRecordGroup = ({ - persistField, - recordId, - fieldName, - }: { - persistField: () => void; - recordId: string; - fieldName: string; - }) => { - upsertTableRecordNoGroup(persistField, recordId, fieldName); - }; - const { openTableCell } = useOpenRecordTableCellV2(recordTableId); const handleOpenTableCell = (args: OpenTableCellArgs) => { @@ -82,7 +67,6 @@ export const RecordTableNoRecordGroupBodyContextProvider = ({ return ( { const { recordTableId } = useRecordTableContextOrThrow(); - const { upsertTableRecordInGroup } = - useUpsertTableRecordInGroup(recordGroupId); - - const handleupsertTableRecordInGroup = ({ - persistField, - recordId, - fieldName, - }: { - persistField: () => void; - recordId: string; - fieldName: string; - }) => { - upsertTableRecordInGroup(persistField, recordId, fieldName); - }; - const { openTableCell } = useOpenRecordTableCellV2(recordTableId); const handleOpenTableCell = (args: OpenTableCellArgs) => { @@ -52,8 +35,7 @@ export const RecordTableRecordGroupBodyContextProvider = ({ moveFocus(direction); }; - const { closeTableCellInGroup } = - useCloseRecordTableCellInGroup(recordGroupId); + const { closeTableCellInGroup } = useCloseRecordTableCellInGroup(); const handlecloseTableCellInGroup = () => { closeTableCellInGroup(); @@ -86,7 +68,6 @@ export const RecordTableRecordGroupBodyContextProvider = ({ return ( { /> ); })} - {}, onOpenTableCell: () => {}, onMoveFocus: () => {}, onCloseTableCell: () => {}, diff --git a/packages/twenty-front/src/modules/object-record/record-table/contexts/RecordTableBodyContext.ts b/packages/twenty-front/src/modules/object-record/record-table/contexts/RecordTableBodyContext.ts index 9c438c3e0..28bd54547 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/contexts/RecordTableBodyContext.ts +++ b/packages/twenty-front/src/modules/object-record/record-table/contexts/RecordTableBodyContext.ts @@ -8,15 +8,6 @@ import { createRequiredContext } from '~/utils/createRequiredContext'; export type RecordTableBodyContextProps = { recordGroupId?: string; - onUpsertRecord: ({ - persistField, - recordId, - fieldName, - }: { - persistField: () => void; - recordId: string; - fieldName: string; - }) => void; onOpenTableCell: (args: OpenTableCellArgs) => void; onMoveFocus: (direction: MoveFocusDirection) => void; onCloseTableCell: () => void; diff --git a/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyHandler.tsx b/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyHandler.tsx index d0bb45047..9913b0328 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyHandler.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyHandler.tsx @@ -1,9 +1,6 @@ -import { isNull } from '@sniptt/guards'; - import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector'; import { RecordTableEmptyState } from '@/object-record/record-table/empty-state/components/RecordTableEmptyState'; import { isRecordTableInitialLoadingComponentState } from '@/object-record/record-table/states/isRecordTableInitialLoadingComponentState'; -import { recordTablePendingRecordIdComponentState } from '@/object-record/record-table/states/recordTablePendingRecordIdComponentState'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; type RecordTableEmptyHandlerProps = { @@ -25,15 +22,8 @@ export const RecordTableEmptyHandler = ({ recordTableId, ); - const pendingRecordId = useRecoilComponentValueV2( - recordTablePendingRecordIdComponentState, - recordTableId, - ); - const recordTableIsEmpty = - !isRecordTableInitialLoading && - allRecordIds.length === 0 && - isNull(pendingRecordId); + !isRecordTableInitialLoading && allRecordIds.length === 0; if (recordTableIsEmpty) { return ; diff --git a/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyStateNoGroupNoRecordAtAll.tsx b/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyStateNoGroupNoRecordAtAll.tsx index 0ae390a9a..e846608bd 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyStateNoGroupNoRecordAtAll.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyStateNoGroupNoRecordAtAll.tsx @@ -5,18 +5,17 @@ import { useRecordTableContextOrThrow } from '@/object-record/record-table/conte import { RecordTableEmptyStateDisplay } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateDisplay'; import { getEmptyStateSubTitle } from '@/object-record/record-table/empty-state/utils/getEmptyStateSubTitle'; import { getEmptyStateTitle } from '@/object-record/record-table/empty-state/utils/getEmptyStateTitle'; -import { useCreateNewTableRecord } from '@/object-record/record-table/hooks/useCreateNewTableRecords'; +import { useCreateNewIndexRecord } from '@/object-record/record-table/hooks/useCreateNewIndexRecord'; export const RecordTableEmptyStateNoGroupNoRecordAtAll = () => { - const { objectMetadataItem, recordTableId } = useRecordTableContextOrThrow(); + const { objectMetadataItem } = useRecordTableContextOrThrow(); - const { createNewTableRecord } = useCreateNewTableRecord({ + const { createNewIndexRecord } = useCreateNewIndexRecord({ objectMetadataItem, - recordTableId, }); const handleButtonClick = () => { - createNewTableRecord(); + createNewIndexRecord(); }; const objectLabel = useObjectLabel(objectMetadataItem); diff --git a/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyStateNoRecordFoundForFilter.tsx b/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyStateNoRecordFoundForFilter.tsx index 329ff8de5..3753bd7f0 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyStateNoRecordFoundForFilter.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyStateNoRecordFoundForFilter.tsx @@ -3,18 +3,17 @@ import { IconPlus } from 'twenty-ui'; import { useObjectLabel } from '@/object-metadata/hooks/useObjectLabel'; import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; import { RecordTableEmptyStateDisplay } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateDisplay'; -import { useCreateNewTableRecord } from '@/object-record/record-table/hooks/useCreateNewTableRecords'; +import { useCreateNewIndexRecord } from '@/object-record/record-table/hooks/useCreateNewIndexRecord'; export const RecordTableEmptyStateNoRecordFoundForFilter = () => { - const { recordTableId, objectMetadataItem } = useRecordTableContextOrThrow(); + const { objectMetadataItem } = useRecordTableContextOrThrow(); - const { createNewTableRecord } = useCreateNewTableRecord({ + const { createNewIndexRecord } = useCreateNewIndexRecord({ objectMetadataItem, - recordTableId, }); const handleButtonClick = () => { - createNewTableRecord(); + createNewIndexRecord(); }; const objectLabel = useObjectLabel(objectMetadataItem); diff --git a/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/__tests__/useUpsertTableRecordInGroup.test.tsx b/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/__tests__/useUpsertTableRecordInGroup.test.tsx deleted file mode 100644 index 84ab42477..000000000 --- a/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/__tests__/useUpsertTableRecordInGroup.test.tsx +++ /dev/null @@ -1,174 +0,0 @@ -import { renderHook } from '@testing-library/react'; -import { createState } from '@ui/utilities/state/utils/createState'; -import { ReactNode, act } from 'react'; -import { RecoilRoot } from 'recoil'; - -import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; -import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural'; -import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; -import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord'; -import { textfieldDefinition } from '@/object-record/record-field/__mocks__/fieldDefinitions'; -import { FieldContext } from '@/object-record/record-field/contexts/FieldContext'; -import { RecordTableContextProvider } from '@/object-record/record-table/components/RecordTableContextProvider'; -import { useUpsertTableRecordInGroup } from '@/object-record/record-table/hooks/internal/useUpsertTableRecordInGroup'; -import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext'; -import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope'; -import { createFamilyState } from '@/ui/utilities/state/utils/createFamilyState'; -import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; - -const draftValue = 'updated Name'; -const recordGroupId = 'recordGroupId'; - -// Todo refactor this test to inject the states in a cleaner way instead of mocking hooks -// (this is not easy to maintain while refactoring) -jest.mock('@/object-record/hooks/useCreateOneRecord', () => ({ - __esModule: true, - useCreateOneRecord: jest.fn(), -})); - -const draftValueState = createState({ - key: 'draftValueState', - defaultValue: null, -}); -jest.mock( - '@/object-record/record-field/hooks/internal/useRecordFieldInputStates', - () => ({ - __esModule: true, - useRecordFieldInputStates: jest.fn(() => ({ - getDraftValueSelector: () => draftValueState, - })), - }), -); - -const recordTablePendingRecordIdByGroupComponentFamilyState = createFamilyState< - string | null, - string ->({ - key: 'recordTablePendingRecordIdByGroupComponentFamilyState', - defaultValue: null, -}); - -const createOneRecordMock = jest.fn(); -const updateOneRecordMock = jest.fn(); -(useCreateOneRecord as jest.Mock).mockReturnValue({ - createOneRecord: createOneRecordMock, -}); - -const Wrapper = ({ - children, - pendingRecordIdMockedValue, - draftValueMockedValue, -}: { - children: ReactNode; - pendingRecordIdMockedValue: string | null; - draftValueMockedValue: string | null; -}) => ( - { - snapshot.set(objectMetadataItemsState, generatedMockObjectMetadataItems); - snapshot.set( - recordTablePendingRecordIdByGroupComponentFamilyState(recordGroupId), - pendingRecordIdMockedValue, - ); - snapshot.set(draftValueState, draftValueMockedValue); - }} - > - - - - - {children} - - - - - -); - -describe('useUpsertTableRecordInGroup', () => { - beforeEach(async () => { - createOneRecordMock.mockClear(); - updateOneRecordMock.mockClear(); - }); - - it('calls update record if there is no pending record', async () => { - /** - * { - objectNameSingular: 'person', - recordTableId: 'recordTableId', - } - */ - const { result } = renderHook( - () => useUpsertTableRecordInGroup(recordGroupId), - { - wrapper: ({ children }) => - Wrapper({ - pendingRecordIdMockedValue: null, - draftValueMockedValue: null, - children, - }), - }, - ); - - await act(async () => { - await result.current.upsertTableRecordInGroup( - updateOneRecordMock, - 'recordId', - 'name', - ); - }); - - expect(createOneRecordMock).not.toHaveBeenCalled(); - expect(updateOneRecordMock).toHaveBeenCalled(); - }); - - it('calls update record if pending record is empty', async () => { - const { result } = renderHook( - () => useUpsertTableRecordInGroup(recordGroupId), - { - wrapper: ({ children }) => - Wrapper({ - pendingRecordIdMockedValue: null, - draftValueMockedValue: draftValue, - children, - }), - }, - ); - - await act(async () => { - await result.current.upsertTableRecordInGroup( - updateOneRecordMock, - 'recordId', - 'name', - ); - }); - - expect(createOneRecordMock).not.toHaveBeenCalled(); - expect(updateOneRecordMock).toHaveBeenCalled(); - }); -}); diff --git a/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/__tests__/useUpsertTableRecordNoGroup.test.tsx b/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/__tests__/useUpsertTableRecordNoGroup.test.tsx deleted file mode 100644 index ac3e65e89..000000000 --- a/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/__tests__/useUpsertTableRecordNoGroup.test.tsx +++ /dev/null @@ -1,160 +0,0 @@ -import { renderHook } from '@testing-library/react'; -import { createState } from '@ui/utilities/state/utils/createState'; -import { ReactNode, act } from 'react'; -import { RecoilRoot } from 'recoil'; - -import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; -import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural'; -import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; -import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord'; -import { textfieldDefinition } from '@/object-record/record-field/__mocks__/fieldDefinitions'; -import { FieldContext } from '@/object-record/record-field/contexts/FieldContext'; -import { RecordTableContextProvider } from '@/object-record/record-table/components/RecordTableContextProvider'; -import { useUpsertTableRecordNoGroup } from '@/object-record/record-table/hooks/internal/useUpsertTableRecordNoGroup'; -import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext'; -import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope'; -import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; - -const draftValue = 'updated Name'; - -// Todo refactor this test to inject the states in a cleaner way instead of mocking hooks -// (this is not easy to maintain while refactoring) -jest.mock('@/object-record/hooks/useCreateOneRecord', () => ({ - __esModule: true, - useCreateOneRecord: jest.fn(), -})); - -const draftValueState = createState({ - key: 'draftValueState', - defaultValue: null, -}); -jest.mock( - '@/object-record/record-field/hooks/internal/useRecordFieldInputStates', - () => ({ - __esModule: true, - useRecordFieldInputStates: jest.fn(() => ({ - getDraftValueSelector: () => draftValueState, - })), - }), -); - -const pendingRecordIdState = createState({ - key: 'pendingRecordIdState', - defaultValue: null, -}); - -const createOneRecordMock = jest.fn(); -const updateOneRecordMock = jest.fn(); -(useCreateOneRecord as jest.Mock).mockReturnValue({ - createOneRecord: createOneRecordMock, -}); - -const Wrapper = ({ - children, - pendingRecordIdMockedValue, - draftValueMockedValue, -}: { - children: ReactNode; - pendingRecordIdMockedValue: string | null; - draftValueMockedValue: string | null; -}) => ( - { - snapshot.set(objectMetadataItemsState, generatedMockObjectMetadataItems); - snapshot.set(pendingRecordIdState, pendingRecordIdMockedValue); - snapshot.set(draftValueState, draftValueMockedValue); - }} - > - - - - - {children} - - - - - -); - -describe('useUpsertTableRecordNoGroup', () => { - beforeEach(async () => { - createOneRecordMock.mockClear(); - updateOneRecordMock.mockClear(); - }); - - it('calls update record if there is no pending record', async () => { - /** - * { - objectNameSingular: 'person', - recordTableId: 'recordTableId', - } - */ - const { result } = renderHook(() => useUpsertTableRecordNoGroup(), { - wrapper: ({ children }) => - Wrapper({ - pendingRecordIdMockedValue: null, - draftValueMockedValue: null, - children, - }), - }); - - await act(async () => { - await result.current.upsertTableRecordNoGroup( - updateOneRecordMock, - 'recordId', - 'name', - ); - }); - - expect(createOneRecordMock).not.toHaveBeenCalled(); - expect(updateOneRecordMock).toHaveBeenCalled(); - }); - - it('calls update record if pending record is empty', async () => { - const { result } = renderHook(() => useUpsertTableRecordNoGroup(), { - wrapper: ({ children }) => - Wrapper({ - pendingRecordIdMockedValue: null, - draftValueMockedValue: draftValue, - children, - }), - }); - - await act(async () => { - await result.current.upsertTableRecordNoGroup( - updateOneRecordMock, - 'recordId', - 'name', - ); - }); - - expect(createOneRecordMock).not.toHaveBeenCalled(); - expect(updateOneRecordMock).toHaveBeenCalled(); - }); -}); diff --git a/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useUpsertTableRecordInGroup.ts b/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useUpsertTableRecordInGroup.ts deleted file mode 100644 index 6175877e1..000000000 --- a/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useUpsertTableRecordInGroup.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { useRecoilCallback } from 'recoil'; - -import { getLabelIdentifierFieldMetadataItem } from '@/object-metadata/utils/getLabelIdentifierFieldMetadataItem'; -import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord'; -import { recordFieldInputDraftValueComponentSelector } from '@/object-record/record-field/states/selectors/recordFieldInputDraftValueComponentSelector'; -import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState'; -import { recordIndexRecordIdsByGroupComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordIdsByGroupComponentFamilyState'; -import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; -import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; -import { recordTablePendingRecordIdByGroupComponentFamilyState } from '@/object-record/record-table/states/recordTablePendingRecordIdByGroupComponentFamilyState'; -import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId'; -import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue'; -import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; -import { extractComponentSelector } from '@/ui/utilities/state/component-state/utils/extractComponentSelector'; -import { isDefined } from 'twenty-shared'; - -export const useUpsertTableRecordInGroup = (recordGroupId: string) => { - const { objectMetadataItem, objectNameSingular } = - useRecordTableContextOrThrow(); - - const { createOneRecord } = useCreateOneRecord({ - objectNameSingular, - shouldMatchRootQueryFilter: true, - }); - - const recordTablePendingRecordIdByGroupFamilyState = - useRecoilComponentCallbackStateV2( - recordTablePendingRecordIdByGroupComponentFamilyState, - ); - - const recordIndexRecordIdsByGroupFamilyState = - useRecoilComponentCallbackStateV2( - recordIndexRecordIdsByGroupComponentFamilyState, - ); - - const upsertTableRecordInGroup = useRecoilCallback( - ({ snapshot }) => - (persistField: () => void, recordId: string, fieldName: string) => { - const labelIdentifierFieldMetadataItem = - getLabelIdentifierFieldMetadataItem(objectMetadataItem); - - const fieldScopeId = getScopeIdFromComponentId( - `${recordId}-${fieldName}`, - ); - - const draftValueSelector = extractComponentSelector( - recordFieldInputDraftValueComponentSelector, - fieldScopeId, - ); - - const draftValue = getSnapshotValue(snapshot, draftValueSelector()); - - // We're in a record group - const recordTablePendingRecordId = getSnapshotValue( - snapshot, - recordTablePendingRecordIdByGroupFamilyState(recordGroupId), - ); - - const recordGroupDefinition = getSnapshotValue( - snapshot, - recordGroupDefinitionFamilyState(recordGroupId), - ); - - const recordGroupIds = getSnapshotValue( - snapshot, - recordIndexRecordIdsByGroupFamilyState(recordGroupId), - ); - - const recordGroupFieldMetadataItem = objectMetadataItem.fields.find( - (fieldMetadata) => - fieldMetadata.id === recordGroupDefinition?.fieldMetadataId, - ); - - const lastId = recordGroupIds?.[recordGroupIds.length - 1]; - - const objectRecord = getSnapshotValue( - snapshot, - recordStoreFamilyState(lastId), - ); - - if ( - isDefined(recordTablePendingRecordId) && - isDefined(recordGroupDefinition) && - isDefined(recordGroupFieldMetadataItem) && - isDefined(draftValue) - ) { - createOneRecord({ - id: recordTablePendingRecordId, - [labelIdentifierFieldMetadataItem?.name ?? 'name']: draftValue, - [recordGroupFieldMetadataItem.name]: recordGroupDefinition.value, - position: (objectRecord?.position ?? 0) + 0.0001, - }); - } else if (!recordTablePendingRecordId) { - persistField(); - } - }, - [ - createOneRecord, - objectMetadataItem, - recordGroupId, - recordIndexRecordIdsByGroupFamilyState, - recordTablePendingRecordIdByGroupFamilyState, - ], - ); - - return { upsertTableRecordInGroup }; -}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useUpsertTableRecordNoGroup.ts b/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useUpsertTableRecordNoGroup.ts deleted file mode 100644 index 68b682cc3..000000000 --- a/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useUpsertTableRecordNoGroup.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { useRecoilCallback } from 'recoil'; - -import { getLabelIdentifierFieldMetadataItem } from '@/object-metadata/utils/getLabelIdentifierFieldMetadataItem'; -import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord'; -import { recordFieldInputDraftValueComponentSelector } from '@/object-record/record-field/states/selectors/recordFieldInputDraftValueComponentSelector'; -import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; -import { recordTablePendingRecordIdComponentState } from '@/object-record/record-table/states/recordTablePendingRecordIdComponentState'; -import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId'; -import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue'; -import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; -import { extractComponentSelector } from '@/ui/utilities/state/component-state/utils/extractComponentSelector'; -import { isDefined } from 'twenty-shared'; - -export const useUpsertTableRecordNoGroup = () => { - const { objectMetadataItem, objectNameSingular, recordTableId } = - useRecordTableContextOrThrow(); - - const { createOneRecord } = useCreateOneRecord({ - objectNameSingular, - }); - - const recordTablePendingRecordIdState = useRecoilComponentCallbackStateV2( - recordTablePendingRecordIdComponentState, - recordTableId, - ); - - const upsertTableRecordNoGroup = useRecoilCallback( - ({ snapshot }) => - (persistField: () => void, recordId: string, fieldName: string) => { - const labelIdentifierFieldMetadataItem = - getLabelIdentifierFieldMetadataItem(objectMetadataItem); - - const fieldScopeId = getScopeIdFromComponentId( - `${recordId}-${fieldName}`, - ); - - const draftValueSelector = extractComponentSelector( - recordFieldInputDraftValueComponentSelector, - fieldScopeId, - ); - - const draftValue = getSnapshotValue(snapshot, draftValueSelector()); - - const recordTablePendingRecordId = getSnapshotValue( - snapshot, - recordTablePendingRecordIdState, - ); - - if (isDefined(recordTablePendingRecordId) && isDefined(draftValue)) { - createOneRecord({ - id: recordTablePendingRecordId, - [labelIdentifierFieldMetadataItem?.name ?? 'name']: draftValue, - position: 'first', - }); - } else if (!recordTablePendingRecordId) { - persistField(); - } - }, - [createOneRecord, objectMetadataItem, recordTablePendingRecordIdState], - ); - - return { upsertTableRecordNoGroup }; -}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/hooks/useCreateNewIndexRecord.ts b/packages/twenty-front/src/modules/object-record/record-table/hooks/useCreateNewIndexRecord.ts new file mode 100644 index 000000000..22687b331 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-table/hooks/useCreateNewIndexRecord.ts @@ -0,0 +1,71 @@ +import { useOpenRecordInCommandMenu } from '@/command-menu/hooks/useOpenRecordInCommandMenu'; +import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord'; +import { recordIndexOpenRecordInState } from '@/object-record/record-index/states/recordIndexOpenRecordInState'; +import { useRecordTitleCell } from '@/object-record/record-title-cell/hooks/useRecordTitleCell'; +import { ObjectRecord } from '@/object-record/types/ObjectRecord'; +import { AppPath } from '@/types/AppPath'; +import { ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType'; +import { useRecoilCallback } from 'recoil'; +import { v4 } from 'uuid'; +import { useNavigateApp } from '~/hooks/useNavigateApp'; + +export const useCreateNewIndexRecord = ({ + objectMetadataItem, +}: { + objectMetadataItem: ObjectMetadataItem; +}) => { + const { openRecordInCommandMenu } = useOpenRecordInCommandMenu(); + + const { createOneRecord } = useCreateOneRecord({ + objectNameSingular: objectMetadataItem.nameSingular, + shouldMatchRootQueryFilter: true, + }); + + const navigate = useNavigateApp(); + + const { openRecordTitleCell } = useRecordTitleCell(); + + const createNewIndexRecord = useRecoilCallback( + ({ snapshot }) => + async (recordInput?: Partial) => { + const recordId = v4(); + + const recordIndexOpenRecordIn = snapshot + .getLoadable(recordIndexOpenRecordInState) + .getValue(); + + await createOneRecord({ id: recordId, ...recordInput }); + + if (recordIndexOpenRecordIn === ViewOpenRecordInType.SIDE_PANEL) { + openRecordInCommandMenu({ + recordId, + objectNameSingular: objectMetadataItem.nameSingular, + isNewRecord: true, + }); + + openRecordTitleCell({ + recordId, + fieldMetadataId: objectMetadataItem.labelIdentifierFieldMetadataId, + }); + } else { + navigate(AppPath.RecordShowPage, { + objectNameSingular: objectMetadataItem.nameSingular, + objectRecordId: recordId, + }); + } + }, + [ + createOneRecord, + navigate, + objectMetadataItem.labelIdentifierFieldMetadataId, + objectMetadataItem.nameSingular, + openRecordInCommandMenu, + openRecordTitleCell, + ], + ); + + return { + createNewIndexRecord, + }; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/hooks/useCreateNewTableRecordInGroup.ts b/packages/twenty-front/src/modules/object-record/record-table/hooks/useCreateNewTableRecordInGroup.ts deleted file mode 100644 index 05b675616..000000000 --- a/packages/twenty-front/src/modules/object-record/record-table/hooks/useCreateNewTableRecordInGroup.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext'; -import { DEFAULT_CELL_SCOPE } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellV2'; -import { useSelectedTableCellEditMode } from '@/object-record/record-table/record-table-cell/hooks/useSelectedTableCellEditMode'; -import { recordTablePendingRecordIdByGroupComponentFamilyState } from '@/object-record/record-table/states/recordTablePendingRecordIdByGroupComponentFamilyState'; -import { getDropdownFocusIdForRecordField } from '@/object-record/utils/getDropdownFocusIdForRecordField'; -import { useSetActiveDropdownFocusIdAndMemorizePrevious } from '@/ui/layout/dropdown/hooks/useSetFocusedDropdownIdAndMemorizePrevious'; -import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope'; -import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; -import { useRecoilCallback } from 'recoil'; -import { isDefined } from 'twenty-shared'; -import { v4 } from 'uuid'; - -export const useCreateNewTableRecordInGroup = () => { - const { recordIndexId, objectMetadataItem } = useRecordIndexContextOrThrow(); - - const { setSelectedTableCellEditMode } = useSelectedTableCellEditMode({ - scopeId: recordIndexId, - }); - - const setHotkeyScope = useSetHotkeyScope(); - - const recordTablePendingRecordIdByGroupFamilyState = - useRecoilComponentCallbackStateV2( - recordTablePendingRecordIdByGroupComponentFamilyState, - recordIndexId, - ); - - const { setActiveDropdownFocusIdAndMemorizePrevious } = - useSetActiveDropdownFocusIdAndMemorizePrevious(); - - const createNewTableRecordInGroup = useRecoilCallback( - ({ set }) => - (recordGroupId: string) => { - const recordId = v4(); - - set( - recordTablePendingRecordIdByGroupFamilyState(recordGroupId), - recordId, - ); - setSelectedTableCellEditMode(-1, 0); - setHotkeyScope( - DEFAULT_CELL_SCOPE.scope, - DEFAULT_CELL_SCOPE.customScopes, - ); - - if (isDefined(objectMetadataItem.labelIdentifierFieldMetadataId)) { - setActiveDropdownFocusIdAndMemorizePrevious( - getDropdownFocusIdForRecordField( - recordId, - objectMetadataItem.labelIdentifierFieldMetadataId, - 'table-cell', - ), - ); - } - }, - [ - objectMetadataItem, - recordTablePendingRecordIdByGroupFamilyState, - setActiveDropdownFocusIdAndMemorizePrevious, - setHotkeyScope, - setSelectedTableCellEditMode, - ], - ); - - return { - createNewTableRecordInGroup, - }; -}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/hooks/useCreateNewTableRecords.ts b/packages/twenty-front/src/modules/object-record/record-table/hooks/useCreateNewTableRecords.ts deleted file mode 100644 index 81b8059b6..000000000 --- a/packages/twenty-front/src/modules/object-record/record-table/hooks/useCreateNewTableRecords.ts +++ /dev/null @@ -1,129 +0,0 @@ -import { useOpenRecordInCommandMenu } from '@/command-menu/hooks/useOpenRecordInCommandMenu'; -import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord'; -import { recordIndexOpenRecordInState } from '@/object-record/record-index/states/recordIndexOpenRecordInState'; -import { DEFAULT_CELL_SCOPE } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellV2'; -import { useSelectedTableCellEditMode } from '@/object-record/record-table/record-table-cell/hooks/useSelectedTableCellEditMode'; -import { recordTablePendingRecordIdByGroupComponentFamilyState } from '@/object-record/record-table/states/recordTablePendingRecordIdByGroupComponentFamilyState'; -import { useRecordTitleCell } from '@/object-record/record-title-cell/hooks/useRecordTitleCell'; -import { getDropdownFocusIdForRecordField } from '@/object-record/utils/getDropdownFocusIdForRecordField'; -import { AppPath } from '@/types/AppPath'; -import { useSetActiveDropdownFocusIdAndMemorizePrevious } from '@/ui/layout/dropdown/hooks/useSetFocusedDropdownIdAndMemorizePrevious'; -import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope'; -import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; -import { ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType'; -import { useRecoilCallback } from 'recoil'; -import { isDefined } from 'twenty-shared'; -import { v4 } from 'uuid'; -import { useNavigateApp } from '~/hooks/useNavigateApp'; - -export const useCreateNewTableRecord = ({ - objectMetadataItem, - recordTableId, -}: { - objectMetadataItem: ObjectMetadataItem; - recordTableId: string; -}) => { - const { setSelectedTableCellEditMode } = useSelectedTableCellEditMode({ - scopeId: recordTableId, - }); - - const setHotkeyScope = useSetHotkeyScope(); - - const recordTablePendingRecordIdByGroupFamilyState = - useRecoilComponentCallbackStateV2( - recordTablePendingRecordIdByGroupComponentFamilyState, - recordTableId, - ); - - const { setActiveDropdownFocusIdAndMemorizePrevious } = - useSetActiveDropdownFocusIdAndMemorizePrevious(); - - const { openRecordInCommandMenu } = useOpenRecordInCommandMenu(); - - const { createOneRecord } = useCreateOneRecord({ - objectNameSingular: objectMetadataItem.nameSingular, - shouldMatchRootQueryFilter: true, - }); - - const navigate = useNavigateApp(); - - const { openRecordTitleCell } = useRecordTitleCell(); - - const createNewTableRecord = useRecoilCallback( - ({ snapshot }) => - async () => { - const recordId = v4(); - - const recordIndexOpenRecordIn = snapshot - .getLoadable(recordIndexOpenRecordInState) - .getValue(); - - await createOneRecord({ id: recordId }); - - if (recordIndexOpenRecordIn === ViewOpenRecordInType.SIDE_PANEL) { - openRecordInCommandMenu({ - recordId, - objectNameSingular: objectMetadataItem.nameSingular, - isNewRecord: true, - }); - - openRecordTitleCell({ - recordId, - fieldMetadataId: objectMetadataItem.labelIdentifierFieldMetadataId, - }); - } else { - navigate(AppPath.RecordShowPage, { - objectNameSingular: objectMetadataItem.nameSingular, - objectRecordId: recordId, - }); - } - }, - [ - createOneRecord, - navigate, - objectMetadataItem.labelIdentifierFieldMetadataId, - objectMetadataItem.nameSingular, - openRecordInCommandMenu, - openRecordTitleCell, - ], - ); - const createNewTableRecordInGroup = useRecoilCallback( - ({ set }) => - (recordGroupId: string) => { - const recordId = v4(); - - set( - recordTablePendingRecordIdByGroupFamilyState(recordGroupId), - recordId, - ); - setSelectedTableCellEditMode(-1, 0); - setHotkeyScope( - DEFAULT_CELL_SCOPE.scope, - DEFAULT_CELL_SCOPE.customScopes, - ); - - if (isDefined(objectMetadataItem.labelIdentifierFieldMetadataId)) { - setActiveDropdownFocusIdAndMemorizePrevious( - getDropdownFocusIdForRecordField( - recordId, - objectMetadataItem.labelIdentifierFieldMetadataId, - 'table-cell', - ), - ); - } - }, - [ - objectMetadataItem, - recordTablePendingRecordIdByGroupFamilyState, - setActiveDropdownFocusIdAndMemorizePrevious, - setHotkeyScope, - setSelectedTableCellEditMode, - ], - ); - - return { - createNewTableRecord, - createNewTableRecordInGroup, - }; -}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableNoRecordGroupBody.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableNoRecordGroupBody.tsx index dec939125..02539907e 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableNoRecordGroupBody.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableNoRecordGroupBody.tsx @@ -4,7 +4,6 @@ import { RecordTableNoRecordGroupRows } from '@/object-record/record-table/compo import { RecordTableBodyDragDropContextProvider } from '@/object-record/record-table/record-table-body/components/RecordTableBodyDragDropContextProvider'; import { RecordTableBodyDroppable } from '@/object-record/record-table/record-table-body/components/RecordTableBodyDroppable'; import { RecordTableBodyLoading } from '@/object-record/record-table/record-table-body/components/RecordTableBodyLoading'; -import { RecordTablePendingRow } from '@/object-record/record-table/record-table-row/components/RecordTablePendingRow'; import { isRecordTableInitialLoadingComponentState } from '@/object-record/record-table/states/isRecordTableInitialLoadingComponentState'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; @@ -25,7 +24,6 @@ export const RecordTableNoRecordGroupBody = () => { - diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellFieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellFieldInput.tsx index 79be4f50c..7c0e919e2 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellFieldInput.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellFieldInput.tsx @@ -10,28 +10,19 @@ import { getRecordFieldInputId } from '@/object-record/utils/getRecordFieldInput export const RecordTableCellFieldInput = () => { const { recordId, fieldDefinition } = useContext(FieldContext); - const { onUpsertRecord, onMoveFocus, onCloseTableCell } = - useRecordTableBodyContextOrThrow(); + const { onMoveFocus, onCloseTableCell } = useRecordTableBodyContextOrThrow(); const isFieldReadOnly = useIsFieldValueReadOnly(); const handleEnter: FieldInputEvent = (persistField) => { - onUpsertRecord({ - persistField, - recordId, - fieldName: fieldDefinition.metadata.fieldName, - }); + persistField(); onCloseTableCell(); onMoveFocus('down'); }; const handleSubmit: FieldInputEvent = (persistField) => { - onUpsertRecord({ - persistField, - recordId, - fieldName: fieldDefinition.metadata.fieldName, - }); + persistField(); onCloseTableCell(); }; @@ -46,42 +37,26 @@ export const RecordTableCellFieldInput = () => { ) => { event.stopImmediatePropagation(); - onUpsertRecord({ - persistField, - recordId, - fieldName: fieldDefinition.metadata.fieldName, - }); + persistField(); onCloseTableCell(); }; const handleEscape: FieldInputEvent = (persistField) => { - onUpsertRecord({ - persistField, - recordId, - fieldName: fieldDefinition.metadata.fieldName, - }); + persistField(); onCloseTableCell(); }; const handleTab: FieldInputEvent = (persistField) => { - onUpsertRecord({ - persistField, - recordId, - fieldName: fieldDefinition.metadata.fieldName, - }); + persistField(); onCloseTableCell(); onMoveFocus('right'); }; const handleShiftTab: FieldInputEvent = (persistField) => { - onUpsertRecord({ - persistField, - recordId, - fieldName: fieldDefinition.metadata.fieldName, - }); + persistField(); onCloseTableCell(); onMoveFocus('left'); diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/internal/__tests__/useCloseRecordTableCellInGroup.test.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/internal/__tests__/useCloseRecordTableCellInGroup.test.tsx index 5b3da4b8f..a6e53dfa3 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/internal/__tests__/useCloseRecordTableCellInGroup.test.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/internal/__tests__/useCloseRecordTableCellInGroup.test.tsx @@ -32,7 +32,6 @@ jest.mock('@/ui/utilities/hotkey/hooks/useSetHotkeyScope', () => ({ const onColumnsChange = jest.fn(); const recordTableId = 'scopeId'; -const recordGroupId = 'recordGroupId'; const Wrapper = ({ children }: { children: React.ReactNode }) => ( { currentTableCellInEditModePosition, ); return { - ...useCloseRecordTableCellInGroup(recordGroupId), + ...useCloseRecordTableCellInGroup(), ...useDragSelect(), isTableCellInEditMode, }; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/internal/useCloseRecordTableCellInGroup.ts b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/internal/useCloseRecordTableCellInGroup.ts index 926dade1b..9a985d8af 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/internal/useCloseRecordTableCellInGroup.ts +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/internal/useCloseRecordTableCellInGroup.ts @@ -7,11 +7,9 @@ import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useC import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; import { useCloseCurrentTableCellInEditMode } from '@/object-record/record-table/hooks/internal/useCloseCurrentTableCellInEditMode'; -import { recordTablePendingRecordIdByGroupComponentFamilyState } from '@/object-record/record-table/states/recordTablePendingRecordIdByGroupComponentFamilyState'; import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope'; -import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; -export const useCloseRecordTableCellInGroup = (recordGroupId: string) => { +export const useCloseRecordTableCellInGroup = () => { const { recordTableId } = useRecordTableContextOrThrow(); const setHotkeyScope = useSetHotkeyScope(); @@ -24,26 +22,15 @@ export const useCloseRecordTableCellInGroup = (recordGroupId: string) => { const closeCurrentTableCellInEditMode = useCloseCurrentTableCellInEditMode(recordTableId); - const recordTablePendingRecordIdByGroupFamilyState = - useRecoilComponentCallbackStateV2( - recordTablePendingRecordIdByGroupComponentFamilyState, - recordTableId, - ); - const closeTableCellInGroup = useRecoilCallback( - ({ reset }) => - () => { - toggleClickOutsideListener(true); - setDragSelectionStartEnabled(true); - closeCurrentTableCellInEditMode(); - setHotkeyScope(TableHotkeyScope.TableSoftFocus); - - reset(recordTablePendingRecordIdByGroupFamilyState(recordGroupId)); - }, + () => () => { + toggleClickOutsideListener(true); + setDragSelectionStartEnabled(true); + closeCurrentTableCellInEditMode(); + setHotkeyScope(TableHotkeyScope.TableSoftFocus); + }, [ closeCurrentTableCellInEditMode, - recordGroupId, - recordTablePendingRecordIdByGroupFamilyState, setDragSelectionStartEnabled, setHotkeyScope, toggleClickOutsideListener, diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/internal/useCloseRecordTableCellNoGroup.ts b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/internal/useCloseRecordTableCellNoGroup.ts index f9138eb3f..ba3dc25dc 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/internal/useCloseRecordTableCellNoGroup.ts +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/internal/useCloseRecordTableCellNoGroup.ts @@ -1,5 +1,3 @@ -import { useResetRecoilState } from 'recoil'; - import { SOFT_FOCUS_CLICK_OUTSIDE_LISTENER_ID } from '@/object-record/record-table/constants/SoftFocusClickOutsideListenerId'; import { useDragSelect } from '@/ui/utilities/drag-select/hooks/useDragSelect'; import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope'; @@ -7,9 +5,7 @@ import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useC import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; import { useCloseCurrentTableCellInEditMode } from '@/object-record/record-table/hooks/internal/useCloseCurrentTableCellInEditMode'; -import { recordTablePendingRecordIdComponentState } from '@/object-record/record-table/states/recordTablePendingRecordIdComponentState'; import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope'; -import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; import { useCallback } from 'react'; export const useCloseRecordTableCellNoGroup = () => { @@ -26,23 +22,13 @@ export const useCloseRecordTableCellNoGroup = () => { const closeCurrentTableCellInEditMode = useCloseCurrentTableCellInEditMode(recordTableId); - const pendingRecordIdState = useRecoilComponentCallbackStateV2( - recordTablePendingRecordIdComponentState, - recordTableId, - ); - - const resetRecordTablePendingRecordId = - useResetRecoilState(pendingRecordIdState); - const closeTableCellNoGroup = useCallback(() => { toggleClickOutsideListener(true); setDragSelectionStartEnabled(true); closeCurrentTableCellInEditMode(); setHotkeyScope(TableHotkeyScope.TableSoftFocus); - resetRecordTablePendingRecordId(); }, [ closeCurrentTableCellInEditMode, - resetRecordTablePendingRecordId, setDragSelectionStartEnabled, setHotkeyScope, toggleClickOutsideListener, diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderCell.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderCell.tsx index e54e11861..599c3c7ce 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderCell.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderCell.tsx @@ -6,7 +6,7 @@ import { IconPlus, LightIconButton } from 'twenty-ui'; import { isObjectMetadataReadOnly } from '@/object-metadata/utils/isObjectMetadataReadOnly'; import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata'; import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; -import { useCreateNewTableRecord } from '@/object-record/record-table/hooks/useCreateNewTableRecords'; +import { useCreateNewIndexRecord } from '@/object-record/record-table/hooks/useCreateNewIndexRecord'; import { useTableColumns } from '@/object-record/record-table/hooks/useTableColumns'; import { RecordTableColumnHeadWithDropdown } from '@/object-record/record-table/record-table-header/components/RecordTableColumnHeadWithDropdown'; import { isRecordTableScrolledLeftComponentState } from '@/object-record/record-table/states/isRecordTableScrolledLeftComponentState'; @@ -107,7 +107,7 @@ type RecordTableHeaderCellProps = { export const RecordTableHeaderCell = ({ column, }: RecordTableHeaderCellProps) => { - const { recordTableId, objectMetadataItem } = useRecordTableContextOrThrow(); + const { objectMetadataItem } = useRecordTableContextOrThrow(); const resizeFieldOffsetState = useRecoilComponentCallbackStateV2( resizeFieldOffsetComponentState, @@ -202,13 +202,12 @@ export const RecordTableHeaderCell = ({ const disableColumnResize = column.isLabelIdentifier && isMobile && !isRecordTableScrolledLeft; - const { createNewTableRecord } = useCreateNewTableRecord({ + const { createNewIndexRecord } = useCreateNewIndexRecord({ objectMetadataItem, - recordTableId, }); const handlePlusButtonClick = () => { - createNewTableRecord(); + createNewIndexRecord(); }; const isReadOnly = isObjectMetadataReadOnly(objectMetadataItem); diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTablePendingRecordGroupRow.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTablePendingRecordGroupRow.tsx deleted file mode 100644 index 553551f00..000000000 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTablePendingRecordGroupRow.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { useCurrentRecordGroupId } from '@/object-record/record-group/hooks/useCurrentRecordGroupId'; -import { RecordTableRow } from '@/object-record/record-table/record-table-row/components/RecordTableRow'; -import { recordTablePendingRecordIdByGroupComponentFamilyState } from '@/object-record/record-table/states/recordTablePendingRecordIdByGroupComponentFamilyState'; -import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2'; - -export const RecordTablePendingRecordGroupRow = () => { - const currentRecordGroupId = useCurrentRecordGroupId(); - - const pendingRecordId = useRecoilComponentFamilyValueV2( - recordTablePendingRecordIdByGroupComponentFamilyState, - currentRecordGroupId, - ); - - if (!pendingRecordId) return <>; - - return ( - - ); -}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTablePendingRow.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTablePendingRow.tsx deleted file mode 100644 index be1583239..000000000 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTablePendingRow.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { RecordTableRow } from '@/object-record/record-table/record-table-row/components/RecordTableRow'; -import { recordTablePendingRecordIdComponentState } from '@/object-record/record-table/states/recordTablePendingRecordIdComponentState'; -import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; - -export const RecordTablePendingRow = () => { - const pendingRecordId = useRecoilComponentValueV2( - recordTablePendingRecordIdComponentState, - ); - - if (!pendingRecordId) return <>; - - return ( - - ); -}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-section/components/RecordTableRecordGroupSectionAddNew.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-section/components/RecordTableRecordGroupSectionAddNew.tsx index 3bb05820e..514ef0cb0 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-section/components/RecordTableRecordGroupSectionAddNew.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-section/components/RecordTableRecordGroupSectionAddNew.tsx @@ -1,15 +1,17 @@ import { useCurrentRecordGroupId } from '@/object-record/record-group/hooks/useCurrentRecordGroupId'; +import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState'; import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector'; import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; -import { useCreateNewTableRecord } from '@/object-record/record-table/hooks/useCreateNewTableRecords'; +import { useCreateNewIndexRecord } from '@/object-record/record-table/hooks/useCreateNewIndexRecord'; import { RecordTableActionRow } from '@/object-record/record-table/record-table-row/components/RecordTableActionRow'; import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; -import { IconPlus } from 'twenty-ui'; import { t } from '@lingui/core/macro'; +import { useRecoilValue } from 'recoil'; +import { IconPlus } from 'twenty-ui'; export const RecordTableRecordGroupSectionAddNew = () => { - const { recordTableId, objectMetadataItem } = useRecordTableContextOrThrow(); + const { objectMetadataItem } = useRecordTableContextOrThrow(); const currentRecordGroupId = useCurrentRecordGroupId(); @@ -17,16 +19,19 @@ export const RecordTableRecordGroupSectionAddNew = () => { recordIndexAllRecordIdsComponentSelector, ); + const recordGroup = useRecoilValue( + recordGroupDefinitionFamilyState(currentRecordGroupId), + ); + const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission(); - const { createNewTableRecordInGroup } = useCreateNewTableRecord({ + const { createNewIndexRecord } = useCreateNewIndexRecord({ objectMetadataItem, - recordTableId, }); - const handleAddNewRecord = () => { - createNewTableRecordInGroup(currentRecordGroupId); - }; + const fieldMetadataItem = objectMetadataItem.fields.find( + (field) => field.id === recordGroup?.fieldMetadataId, + ); if (hasObjectReadOnlyPermission) { return null; @@ -38,7 +43,16 @@ export const RecordTableRecordGroupSectionAddNew = () => { draggableIndex={recordIds.length + 2} LeftIcon={IconPlus} text={t`Add new`} - onClick={handleAddNewRecord} + onClick={() => { + if (!fieldMetadataItem) { + return; + } + + createNewIndexRecord({ + position: 'last', + [fieldMetadataItem.name]: recordGroup?.value, + }); + }} /> ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-table/states/recordTablePendingRecordIdByGroupComponentFamilyState.ts b/packages/twenty-front/src/modules/object-record/record-table/states/recordTablePendingRecordIdByGroupComponentFamilyState.ts deleted file mode 100644 index 9eaa9c938..000000000 --- a/packages/twenty-front/src/modules/object-record/record-table/states/recordTablePendingRecordIdByGroupComponentFamilyState.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { RecordGroupDefinition } from '@/object-record/record-group/types/RecordGroupDefinition'; -import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext'; -import { createComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentFamilyStateV2'; - -export const recordTablePendingRecordIdByGroupComponentFamilyState = - createComponentFamilyStateV2({ - key: 'recordTablePendingRecordIdByGroupComponentFamilyState', - defaultValue: null, - componentInstanceContext: RecordTableComponentInstanceContext, - }); diff --git a/packages/twenty-front/src/modules/object-record/record-table/states/recordTablePendingRecordIdComponentState.ts b/packages/twenty-front/src/modules/object-record/record-table/states/recordTablePendingRecordIdComponentState.ts deleted file mode 100644 index 3c84a42a2..000000000 --- a/packages/twenty-front/src/modules/object-record/record-table/states/recordTablePendingRecordIdComponentState.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext'; -import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2'; - -export const recordTablePendingRecordIdComponentState = createComponentStateV2< - string | null ->({ - key: 'recordTablePendingRecordIdComponentState', - defaultValue: null, - componentInstanceContext: RecordTableComponentInstanceContext, -}); diff --git a/packages/twenty-front/src/modules/object-record/record-table/states/selectors/hasPendingRecordComponentSelector.ts b/packages/twenty-front/src/modules/object-record/record-table/states/selectors/hasPendingRecordComponentSelector.ts deleted file mode 100644 index 11593254b..000000000 --- a/packages/twenty-front/src/modules/object-record/record-table/states/selectors/hasPendingRecordComponentSelector.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { recordGroupIdsComponentState } from '@/object-record/record-group/states/recordGroupIdsComponentState'; -import { hasRecordGroupsComponentSelector } from '@/object-record/record-group/states/selectors/hasRecordGroupsComponentSelector'; -import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext'; -import { recordTablePendingRecordIdByGroupComponentFamilyState } from '@/object-record/record-table/states/recordTablePendingRecordIdByGroupComponentFamilyState'; -import { recordTablePendingRecordIdComponentState } from '@/object-record/record-table/states/recordTablePendingRecordIdComponentState'; -import { createComponentSelectorV2 } from '@/ui/utilities/state/component-state/utils/createComponentSelectorV2'; -import { isDefined } from 'twenty-shared'; - -export const hasPendingRecordComponentSelector = createComponentSelectorV2({ - key: 'hasPendingRecordComponentSelector', - componentInstanceContext: RecordTableComponentInstanceContext, - get: - ({ instanceId }) => - ({ get }) => { - const hasRecordGroups = get( - hasRecordGroupsComponentSelector.selectorFamily({ instanceId }), - ); - - if (!hasRecordGroups) { - const pendingRecordId = get( - recordTablePendingRecordIdComponentState.atomFamily({ instanceId }), - ); - - return isDefined(pendingRecordId); - } - - const recordGroupIds = get( - recordGroupIdsComponentState.atomFamily({ instanceId }), - ); - - for (const recordGroupId of recordGroupIds) { - const pendingRecordId = get( - recordTablePendingRecordIdByGroupComponentFamilyState.atomFamily({ - instanceId, - familyKey: recordGroupId, - }), - ); - - if (isDefined(pendingRecordId)) { - return true; - } - } - - return false; - }, -}); diff --git a/packages/twenty-front/src/pages/object-record/RecordShowPage.tsx b/packages/twenty-front/src/pages/object-record/RecordShowPage.tsx index 47284649b..d77006f11 100644 --- a/packages/twenty-front/src/pages/object-record/RecordShowPage.tsx +++ b/packages/twenty-front/src/pages/object-record/RecordShowPage.tsx @@ -8,14 +8,15 @@ import { ContextStoreComponentInstanceContext } from '@/context-store/states/con import { RecordFilterGroupsComponentInstanceContext } from '@/object-record/record-filter-group/states/context/RecordFilterGroupsComponentInstanceContext'; import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext'; import { RecordShowContainer } from '@/object-record/record-show/components/RecordShowContainer'; +import { RecordShowEffect } from '@/object-record/record-show/components/RecordShowEffect'; import { useRecordShowPage } from '@/object-record/record-show/hooks/useRecordShowPage'; import { RecordSortsComponentInstanceContext } from '@/object-record/record-sort/states/context/RecordSortsComponentInstanceContext'; import { RecordValueSetterEffect } from '@/object-record/record-store/components/RecordValueSetterEffect'; import { RecordFieldValueSelectorContextProvider } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext'; import { PageBody } from '@/ui/layout/page/components/PageBody'; import { PageContainer } from '@/ui/layout/page/components/PageContainer'; -import { PageTitle } from '@/ui/utilities/page-title/components/PageTitle'; import { RecordShowPageHeader } from '~/pages/object-record/RecordShowPageHeader'; +import { RecordShowPageTitle } from '~/pages/object-record/RecordShowPageTitle'; export const RecordShowPage = () => { const parameters = useParams<{ @@ -23,14 +24,7 @@ export const RecordShowPage = () => { objectRecordId: string; }>(); - const { - pageTitle, - objectNameSingular, - objectRecordId, - headerIcon, - loading, - pageName, - } = useRecordShowPage( + const { objectNameSingular, objectRecordId, headerIcon } = useRecordShowPage( parameters.objectNameSingular ?? '', parameters.objectRecordId ?? '', ); @@ -54,7 +48,10 @@ export const RecordShowPage = () => { > - + { + diff --git a/packages/twenty-front/src/pages/object-record/RecordShowPageTitle.tsx b/packages/twenty-front/src/pages/object-record/RecordShowPageTitle.tsx new file mode 100644 index 000000000..809583d77 --- /dev/null +++ b/packages/twenty-front/src/pages/object-record/RecordShowPageTitle.tsx @@ -0,0 +1,35 @@ +import { useLabelIdentifierFieldMetadataItem } from '@/object-metadata/hooks/useLabelIdentifierFieldMetadataItem'; +import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; +import { PageTitle } from '@/ui/utilities/page-title/components/PageTitle'; +import { useRecoilValue } from 'recoil'; +import { FieldMetadataType, capitalize, isDefined } from 'twenty-shared'; + +export const RecordShowPageTitle = ({ + objectNameSingular, + objectRecordId, +}: { + objectNameSingular: string; + objectRecordId: string; +}) => { + const { labelIdentifierFieldMetadataItem } = + useLabelIdentifierFieldMetadataItem({ objectNameSingular }); + + const record = useRecoilValue(recordStoreFamilyState(objectRecordId)); + const labelIdentifierFieldValue = record?.labelIdentifierFieldValue; + + const pageName = + labelIdentifierFieldMetadataItem?.type === FieldMetadataType.FULL_NAME + ? [ + labelIdentifierFieldValue?.firstName, + labelIdentifierFieldValue?.lastName, + ].join(' ') + : isDefined(labelIdentifierFieldValue) + ? `${labelIdentifierFieldValue}` + : ''; + + const pageTitle = pageName.trim() + ? `${pageName} - ${capitalize(objectNameSingular)}` + : capitalize(objectNameSingular); + + return ; +}; diff --git a/packages/twenty-front/src/testing/decorators/RecordTableDecorator.tsx b/packages/twenty-front/src/testing/decorators/RecordTableDecorator.tsx index 5c26e4689..3a1f6895f 100644 --- a/packages/twenty-front/src/testing/decorators/RecordTableDecorator.tsx +++ b/packages/twenty-front/src/testing/decorators/RecordTableDecorator.tsx @@ -46,7 +46,6 @@ export const RecordTableDecorator: Decorator = (Story) => { onActionMenuDropdownOpened: () => {}, onMoveFocus: () => {}, onMoveSoftFocusToCurrentCell: () => {}, - onUpsertRecord: () => {}, }} > diff --git a/packages/twenty-ui/src/input/components/Checkbox.tsx b/packages/twenty-ui/src/input/components/Checkbox.tsx index 41d0a50cd..d6c328b8c 100644 --- a/packages/twenty-ui/src/input/components/Checkbox.tsx +++ b/packages/twenty-ui/src/input/components/Checkbox.tsx @@ -104,7 +104,7 @@ const StyledInput = styled.input` disabled && isChecked ? theme.adaptiveColors.blue3 : indeterminate || isChecked - ? theme.adaptiveColors.blue3 + ? theme.color.blue : 'transparent'}; border-color: ${({ theme,