From 0dc39db314bde95b90a5a34ba2b69dcdf99256e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tha=C3=AFs?= Date: Fri, 12 Jan 2024 08:10:42 -0300 Subject: [PATCH] feat: detach records from Relation field card in Show Page (#3350) * feat: detach records from Relation field card in Show Page Closes #3128 * Fix TS --------- Co-authored-by: Lucas Bordeau --- packages/twenty-front/.eslintrc.cjs | 2 +- .../RecordRelationFieldCardContent.tsx | 116 ++++++++++++++++-- .../RecordRelationFieldCardSection.tsx | 6 +- .../src/modules/ui/display/icon/index.ts | 1 + 4 files changed, 110 insertions(+), 15 deletions(-) diff --git a/packages/twenty-front/.eslintrc.cjs b/packages/twenty-front/.eslintrc.cjs index f62981fbc..68b808b3c 100644 --- a/packages/twenty-front/.eslintrc.cjs +++ b/packages/twenty-front/.eslintrc.cjs @@ -30,7 +30,7 @@ module.exports = { patterns: [ { group: ['@tabler/icons-react'], - message: 'Icon imports are only allowed for `@/ui/icon`', + message: 'Icon imports are only allowed for `@/ui/display/icon`', }, { group: ['react-hotkeys-web-hook'], diff --git a/packages/twenty-front/src/modules/object-record/record-relation-card/components/RecordRelationFieldCardContent.tsx b/packages/twenty-front/src/modules/object-record/record-relation-card/components/RecordRelationFieldCardContent.tsx index 3720a502c..8545e61aa 100644 --- a/packages/twenty-front/src/modules/object-record/record-relation-card/components/RecordRelationFieldCardContent.tsx +++ b/packages/twenty-front/src/modules/object-record/record-relation-card/components/RecordRelationFieldCardContent.tsx @@ -1,52 +1,146 @@ import { useContext } from 'react'; +import { css } from '@emotion/react'; import styled from '@emotion/styled'; +import { LightIconButton, MenuItem } from 'tsup.ui.index'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { FieldDisplay } from '@/object-record/field/components/FieldDisplay'; import { FieldContext } from '@/object-record/field/contexts/FieldContext'; +import { usePersistField } from '@/object-record/field/hooks/usePersistField'; import { FieldRelationMetadata } from '@/object-record/field/types/FieldMetadata'; import { useFieldContext } from '@/object-record/hooks/useFieldContext'; +import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord'; +import { ObjectRecord } from '@/object-record/types/ObjectRecord'; +import { IconDotsVertical, IconUnlink } from '@/ui/display/icon'; import { CardContent } from '@/ui/layout/card/components/CardContent'; +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 { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope'; -const StyledCardContent = styled(CardContent)` +const StyledCardContent = styled(CardContent)<{ + isDropdownOpen?: boolean; +}>` align-items: center; + justify-content: space-between; + gap: ${({ theme }) => theme.spacing(1)}; display: flex; height: ${({ theme }) => theme.spacing(10)}; padding: ${({ theme }) => theme.spacing(0, 2, 0, 3)}; + + ${({ isDropdownOpen, theme }) => + isDropdownOpen + ? '' + : css` + .displayOnHover { + opacity: 0; + pointer-events: none; + transition: opacity ${theme.animation.duration.instant}s ease; + } + `} + + &:hover { + .displayOnHover { + opacity: 1; + pointer-events: auto; + } + } `; type RecordRelationFieldCardContentProps = { divider?: boolean; - relationRecordId: string; + relationRecord: ObjectRecord; }; export const RecordRelationFieldCardContent = ({ divider, - relationRecordId, + relationRecord, }: RecordRelationFieldCardContentProps) => { const { fieldDefinition } = useContext(FieldContext); - const { relationObjectMetadataNameSingular } = - fieldDefinition.metadata as FieldRelationMetadata; - const { labelIdentifierFieldMetadata: relationLabelIdentifierFieldMetadata } = - useObjectMetadataItem({ - objectNameSingular: relationObjectMetadataNameSingular, - }); + const { + relationFieldMetadataId, + relationObjectMetadataNameSingular, + relationType, + } = fieldDefinition.metadata as FieldRelationMetadata; + const isToOneObject = relationType === 'TO_ONE_OBJECT'; + const { + labelIdentifierFieldMetadata: relationLabelIdentifierFieldMetadata, + objectMetadataItem: relationObjectMetadataItem, + } = useObjectMetadataItem({ + objectNameSingular: relationObjectMetadataNameSingular, + }); + const persistField = usePersistField(); + const { updateOneRecord: updateOneRelationRecord } = useUpdateOneRecord({ + objectNameSingular: relationObjectMetadataNameSingular, + }); const { FieldContextProvider } = useFieldContext({ fieldMetadataName: relationLabelIdentifierFieldMetadata?.name || '', fieldPosition: 0, isLabelIdentifier: true, objectNameSingular: relationObjectMetadataNameSingular, - objectRecordId: relationRecordId, + objectRecordId: relationRecord.id, }); + const dropdownScopeId = `record-field-card-menu-${relationRecord.id}`; + + const { closeDropdown, isDropdownOpen } = useDropdown(dropdownScopeId); + if (!FieldContextProvider) return null; + const handleDetach = () => { + closeDropdown(); + + const relationFieldMetadataItem = relationObjectMetadataItem.fields.find( + ({ id }) => id === relationFieldMetadataId, + ); + + if (!relationFieldMetadataItem?.name) return; + + if (isToOneObject) { + persistField(null); + return; + } + + updateOneRelationRecord({ + idToUpdate: relationRecord.id, + updateOneRecordInput: { + [`${relationFieldMetadataItem.name}Id`]: null, + [relationFieldMetadataItem.name]: null, + }, + }); + }; + return ( - + + + + } + dropdownComponents={ + + + + } + dropdownHotkeyScope={{ + scope: dropdownScopeId, + }} + /> + ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-relation-card/components/RecordRelationFieldCardSection.tsx b/packages/twenty-front/src/modules/object-record/record-relation-card/components/RecordRelationFieldCardSection.tsx index 742418304..8650ed97c 100644 --- a/packages/twenty-front/src/modules/object-record/record-relation-card/components/RecordRelationFieldCardSection.tsx +++ b/packages/twenty-front/src/modules/object-record/record-relation-card/components/RecordRelationFieldCardSection.tsx @@ -183,7 +183,7 @@ export const RecordRelationFieldCardSection = () => { }, [setRelationPickerSearchFilter]); const persistField = usePersistField(); - const { updateOneRecord } = useUpdateOneRecord({ + const { updateOneRecord: updateOneRelationRecord } = useUpdateOneRecord({ objectNameSingular: relationObjectMetadataNameSingular, }); @@ -201,7 +201,7 @@ export const RecordRelationFieldCardSection = () => { if (!relationFieldMetadataItem?.name) return; - updateOneRecord({ + updateOneRelationRecord({ idToUpdate: selectedRelationEntity.id, updateOneRecordInput: { [`${relationFieldMetadataItem.name}Id`]: entityId, @@ -267,7 +267,7 @@ export const RecordRelationFieldCardSection = () => { ))} diff --git a/packages/twenty-front/src/modules/ui/display/icon/index.ts b/packages/twenty-front/src/modules/ui/display/icon/index.ts index 8c2029c96..e30d8cf34 100644 --- a/packages/twenty-front/src/modules/ui/display/icon/index.ts +++ b/packages/twenty-front/src/modules/ui/display/icon/index.ts @@ -108,6 +108,7 @@ export { IconTextSize, IconTimelineEvent, IconTrash, + IconUnlink, IconUpload, IconUser, IconUserCircle,