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 000000000..81ec3e149 --- /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 7dc5dee5c..2881134b0 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 21be4617c..389258ad5 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 cb74b1448..d8a121c70 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 96575ad20..5fcb8f40a 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 c932272bb..0c14f8320 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 000000000..55b9f3d69 --- /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');