From 1f2c40af61acd0a7c02691c5fbb2c5433348f9ed Mon Sep 17 00:00:00 2001 From: Abdul Rahman <81605929+abdulrahmancodes@users.noreply.github.com> Date: Mon, 9 Jun 2025 18:50:13 +0530 Subject: [PATCH] fix: prevent duplicate dropdowns in activity targets when editing in different contexts (#12462) Closes #12361 ### Changes Made - In `ActivityTargetsInlineCell`, we now use different component instance IDs based on context. This ensures that each instance of the component (whether in right drawer or main view) has its own isolated state, preventing state conflicts and duplicate dropdowns. - The `MultipleRecordPicker` component now properly resets its state when closed, preventing state leakage between instances. https://github.com/user-attachments/assets/deb99687-a803-417e-a339-cab061026739 --- .../useActivityTargetsComponentInstanceId.ts | 11 ++++++++ .../activities/notes/components/NoteCard.tsx | 8 +++++- .../activities/tasks/components/TaskRow.tsx | 8 +++++- .../components/MultipleRecordPicker.tsx | 28 ++++++++++++++++++- .../record-show/components/FieldsCard.tsx | 7 ++++- .../components/RecordShowContainer.tsx | 5 ++-- .../contexts/RightDrawerContext.tsx | 8 ++++++ 7 files changed, 69 insertions(+), 6 deletions(-) create mode 100644 packages/twenty-front/src/modules/activities/inline-cell/hooks/useActivityTargetsComponentInstanceId.ts create mode 100644 packages/twenty-front/src/modules/ui/layout/right-drawer/contexts/RightDrawerContext.tsx diff --git a/packages/twenty-front/src/modules/activities/inline-cell/hooks/useActivityTargetsComponentInstanceId.ts b/packages/twenty-front/src/modules/activities/inline-cell/hooks/useActivityTargetsComponentInstanceId.ts new file mode 100644 index 0000000000..81ec3e1499 --- /dev/null +++ b/packages/twenty-front/src/modules/activities/inline-cell/hooks/useActivityTargetsComponentInstanceId.ts @@ -0,0 +1,11 @@ +import { useIsInRightDrawerOrThrow } from '@/ui/layout/right-drawer/contexts/RightDrawerContext'; + +export const useActivityTargetsComponentInstanceId = ( + baseComponentInstanceId: string, +) => { + const { isInRightDrawer } = useIsInRightDrawerOrThrow(); + + return isInRightDrawer + ? `${baseComponentInstanceId}-right-drawer` + : baseComponentInstanceId; +}; 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 7dc5dee5c2..2881134b06 100644 --- a/packages/twenty-front/src/modules/activities/notes/components/NoteCard.tsx +++ b/packages/twenty-front/src/modules/activities/notes/components/NoteCard.tsx @@ -1,6 +1,7 @@ import styled from '@emotion/styled'; import { ActivityTargetsInlineCell } from '@/activities/inline-cell/components/ActivityTargetsInlineCell'; +import { useActivityTargetsComponentInstanceId } from '@/activities/inline-cell/hooks/useActivityTargetsComponentInstanceId'; import { Note } from '@/activities/types/Note'; import { getActivityPreview } from '@/activities/utils/getActivityPreview'; import { useOpenRecordInCommandMenu } from '@/command-menu/hooks/useOpenRecordInCommandMenu'; @@ -72,6 +73,11 @@ export const NoteCard = ({ const body = getActivityPreview(note?.bodyV2?.blocknote ?? null); + const baseComponentInstanceId = `note-card-${note.id}-targets`; + const componentInstanceId = useActivityTargetsComponentInstanceId( + baseComponentInstanceId, + ); + return ( 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 21be4617c8..389258ad53 100644 --- a/packages/twenty-front/src/modules/activities/tasks/components/TaskRow.tsx +++ b/packages/twenty-front/src/modules/activities/tasks/components/TaskRow.tsx @@ -6,6 +6,7 @@ import { getActivitySummary } from '@/activities/utils/getActivitySummary'; import { beautifyExactDate, hasDatePassed } from '~/utils/date-utils'; import { ActivityRow } from '@/activities/components/ActivityRow'; +import { useActivityTargetsComponentInstanceId } from '@/activities/inline-cell/hooks/useActivityTargetsComponentInstanceId'; import { Task } from '@/activities/types/Task'; import { useOpenRecordInCommandMenu } from '@/command-menu/hooks/useOpenRecordInCommandMenu'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; @@ -81,6 +82,11 @@ export const TaskRow = ({ task }: { task: Task }) => { const { completeTask } = useCompleteTask(task); + const baseComponentInstanceId = `task-row-targets-${task.id}`; + const componentInstanceId = useActivityTargetsComponentInstanceId( + baseComponentInstanceId, + ); + return ( { @@ -131,7 +137,7 @@ export const TaskRow = ({ task }: { task: Task }) => { activityRecordId={task.id} showLabel={false} maxWidth={200} - componentInstanceId={`task-row-targets-${task.id}`} + componentInstanceId={componentInstanceId} /> diff --git a/packages/twenty-front/src/modules/object-record/record-picker/multiple-record-picker/components/MultipleRecordPicker.tsx b/packages/twenty-front/src/modules/object-record/record-picker/multiple-record-picker/components/MultipleRecordPicker.tsx index cb74b1448d..d8a121c706 100644 --- a/packages/twenty-front/src/modules/object-record/record-picker/multiple-record-picker/components/MultipleRecordPicker.tsx +++ b/packages/twenty-front/src/modules/object-record/record-picker/multiple-record-picker/components/MultipleRecordPicker.tsx @@ -2,6 +2,7 @@ import { MultipleRecordPickerItemsDisplay } from '@/object-record/record-picker/ import { MultipleRecordPickerOnClickOutsideEffect } from '@/object-record/record-picker/multiple-record-picker/components/MultipleRecordPickerOnClickOutsideEffect'; import { MultipleRecordPickerSearchInput } from '@/object-record/record-picker/multiple-record-picker/components/MultipleRecordPickerSearchInput'; import { MultipleRecordPickerComponentInstanceContext } from '@/object-record/record-picker/multiple-record-picker/states/contexts/MultipleRecordPickerComponentInstanceContext'; +import { multipleRecordPickerPickableMorphItemsComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerPickableMorphItemsComponentState'; import { multipleRecordPickerSearchFilterComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerSearchFilterComponentState'; import { MultipleRecordPickerHotkeyScope } from '@/object-record/record-picker/multiple-record-picker/types/MultipleRecordPickerHotkeyScope'; import { getMultipleRecordPickerSelectableListId } from '@/object-record/record-picker/multiple-record-picker/utils/getMultipleRecordPickerSelectableListId'; @@ -60,12 +61,37 @@ export const MultipleRecordPicker = ({ componentInstanceId, ); + const multipleRecordPickerPickableMorphItemsState = + useRecoilComponentCallbackStateV2( + multipleRecordPickerPickableMorphItemsComponentState, + componentInstanceId, + ); + const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission(); + const resetState = useRecoilCallback( + ({ set }) => { + return () => { + set(multipleRecordPickerPickableMorphItemsState, []); + set(multipleRecordPickerSearchFilterState, ''); + }; + }, + [ + multipleRecordPickerPickableMorphItemsState, + multipleRecordPickerSearchFilterState, + ], + ); + const handleSubmit = () => { onSubmit?.(); goBackToPreviousHotkeyScope(); resetSelectedItem(); + resetState(); + }; + + const handleClickOutside = () => { + onClickOutside(); + resetState(); }; useScopedHotkeys( @@ -108,7 +134,7 @@ export const MultipleRecordPicker = ({ > {layoutDirection === 'search-bar-on-bottom' && ( 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 96575ad20e..5fcb8f40a1 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 @@ -20,6 +20,7 @@ import { RecordDetailDuplicatesSection } from '@/object-record/record-show/recor import { RecordDetailRelationSection } from '@/object-record/record-show/record-detail-section/components/RecordDetailRelationSection'; import { getRecordFieldInputId } from '@/object-record/utils/getRecordFieldInputId'; import { isFieldCellSupported } from '@/object-record/utils/isFieldCellSupported'; +import { useIsInRightDrawerOrThrow } from '@/ui/layout/right-drawer/contexts/RightDrawerContext'; import { FieldMetadataType } from '~/generated/graphql'; type FieldsCardProps = { @@ -48,6 +49,8 @@ export const FieldsCard = ({ objectRecordId, }); + const { isInRightDrawer } = useIsInRightDrawerOrThrow(); + const availableFieldMetadataItems = objectMetadataItem.fields .filter( (fieldMetadataItem) => @@ -139,7 +142,9 @@ export const FieldsCard = ({ componentInstanceId={getRecordFieldInputId( objectRecordId, fieldMetadataItem.name, - 'fields-card', + isInRightDrawer + ? 'right-drawer-fields-card' + : 'fields-card', )} activityObjectNameSingular={ objectNameSingular as 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 c932272bb6..0c14f8320c 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 @@ -1,5 +1,6 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { ShowPageContainer } from '@/ui/layout/page/components/ShowPageContainer'; +import { RightDrawerProvider } from '@/ui/layout/right-drawer/contexts/RightDrawerContext'; import { InformationBannerDeletedRecord } from '@/information-banner/components/deleted-record/InformationBannerDeletedRecord'; @@ -49,7 +50,7 @@ export const RecordShowContainer = ({ ); return ( - <> + @@ -71,6 +72,6 @@ export const RecordShowContainer = ({ loading={isPrefetchLoading || loading || recordLoading} /> - + ); }; diff --git a/packages/twenty-front/src/modules/ui/layout/right-drawer/contexts/RightDrawerContext.tsx b/packages/twenty-front/src/modules/ui/layout/right-drawer/contexts/RightDrawerContext.tsx new file mode 100644 index 0000000000..55b9f3d691 --- /dev/null +++ b/packages/twenty-front/src/modules/ui/layout/right-drawer/contexts/RightDrawerContext.tsx @@ -0,0 +1,8 @@ +import { createRequiredContext } from '~/utils/createRequiredContext'; + +type RightDrawerContextType = { + isInRightDrawer: boolean; +}; + +export const [RightDrawerProvider, useIsInRightDrawerOrThrow] = + createRequiredContext('RightDrawer');