From f3f20ad9747031582c3955f9b445d1dd8a267625 Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Tue, 16 Jan 2024 15:43:19 +0100 Subject: [PATCH] Improve opportunity behavior (#3487) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix opportunity relation * Fix * Fix * Fix tests * Fix * Fix * Fix opportunities * Fix Opportunity standard object and apply maxWidth to text ellipsis * Update packages/twenty-front/src/modules/ui/field/display/components/EllipsisDisplay.tsx Co-authored-by: Thaïs * Fix --------- Co-authored-by: Thaïs --- .../companies/components/CompanyBoardCard.tsx | 1 + .../components/RecordShowPage.tsx | 1 + .../field/contexts/FieldContext.ts | 1 + .../display/components/TextFieldDisplay.tsx | 4 +-- .../field/meta-types/hooks/useTextField.ts | 4 ++- .../useRecordBoardCardFieldsInternal.ts | 27 +++++++--------- .../RecordRelationFieldCardContent.tsx | 6 ++-- .../RecordRelationFieldCardSection.tsx | 32 +++++++++---------- .../components/RelationPicker.tsx | 22 ++++++------- .../display/components/EllipsisDisplay.tsx | 15 +++++++-- .../field/display/components/TextDisplay.tsx | 5 +-- .../SettingsObjectNewFieldStep2.tsx | 1 - .../favorite.object-metadata.ts | 11 +++++++ .../opportunity.object-metadata.ts | 14 ++++++++ 14 files changed, 88 insertions(+), 56 deletions(-) diff --git a/packages/twenty-front/src/modules/companies/components/CompanyBoardCard.tsx b/packages/twenty-front/src/modules/companies/components/CompanyBoardCard.tsx index 4e05fd3e6..e272d9e68 100644 --- a/packages/twenty-front/src/modules/companies/components/CompanyBoardCard.tsx +++ b/packages/twenty-front/src/modules/companies/components/CompanyBoardCard.tsx @@ -229,6 +229,7 @@ export const CompanyBoardCard = () => { { key={record.id + fieldMetadataItem.id} value={{ entityId: record.id, + maxWidth: 272, recoilScopeId: record.id + fieldMetadataItem.id, isLabelIdentifier: false, fieldDefinition: diff --git a/packages/twenty-front/src/modules/object-record/field/contexts/FieldContext.ts b/packages/twenty-front/src/modules/object-record/field/contexts/FieldContext.ts index c53dbbe17..03be55d34 100644 --- a/packages/twenty-front/src/modules/object-record/field/contexts/FieldContext.ts +++ b/packages/twenty-front/src/modules/object-record/field/contexts/FieldContext.ts @@ -28,6 +28,7 @@ export type GenericFieldContextType = { isLabelIdentifier: boolean; basePathToShowPage?: string; clearable?: boolean; + maxWidth?: number; }; export const FieldContext = createContext( diff --git a/packages/twenty-front/src/modules/object-record/field/meta-types/display/components/TextFieldDisplay.tsx b/packages/twenty-front/src/modules/object-record/field/meta-types/display/components/TextFieldDisplay.tsx index aa4de6ffd..a68f70404 100644 --- a/packages/twenty-front/src/modules/object-record/field/meta-types/display/components/TextFieldDisplay.tsx +++ b/packages/twenty-front/src/modules/object-record/field/meta-types/display/components/TextFieldDisplay.tsx @@ -3,7 +3,7 @@ import { TextDisplay } from '@/ui/field/display/components/TextDisplay'; import { useTextField } from '../../hooks/useTextField'; export const TextFieldDisplay = () => { - const { fieldValue } = useTextField(); + const { fieldValue, maxWidth } = useTextField(); - return ; + return ; }; diff --git a/packages/twenty-front/src/modules/object-record/field/meta-types/hooks/useTextField.ts b/packages/twenty-front/src/modules/object-record/field/meta-types/hooks/useTextField.ts index 8c8471d86..8926cf0c5 100644 --- a/packages/twenty-front/src/modules/object-record/field/meta-types/hooks/useTextField.ts +++ b/packages/twenty-front/src/modules/object-record/field/meta-types/hooks/useTextField.ts @@ -9,7 +9,8 @@ import { isFieldText } from '../../types/guards/isFieldText'; import { isFieldTextValue } from '../../types/guards/isFieldTextValue'; export const useTextField = () => { - const { entityId, fieldDefinition, hotkeyScope } = useContext(FieldContext); + const { entityId, fieldDefinition, hotkeyScope, maxWidth } = + useContext(FieldContext); assertFieldMetadata('TEXT', isFieldText, fieldDefinition); @@ -30,6 +31,7 @@ export const useTextField = () => { : fieldInitialValue?.value ?? fieldTextValue; return { + maxWidth, fieldDefinition, fieldValue: fieldTextValue, initialValue, diff --git a/packages/twenty-front/src/modules/object-record/record-board/hooks/internal/useRecordBoardCardFieldsInternal.ts b/packages/twenty-front/src/modules/object-record/record-board/hooks/internal/useRecordBoardCardFieldsInternal.ts index 59f656adb..69b066b0a 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/hooks/internal/useRecordBoardCardFieldsInternal.ts +++ b/packages/twenty-front/src/modules/object-record/record-board/hooks/internal/useRecordBoardCardFieldsInternal.ts @@ -39,25 +39,20 @@ export const useRecordBoardCardFieldsInternal = ( .getLoadable(recordBoardCardFieldsScopedState({ scopeId })) .getValue(); - const existingFieldsUpdated = existingFields.map((previousField) => - previousField.fieldMetadataId === field.fieldMetadataId - ? { ...previousField, isVisible: !field.isVisible } - : previousField, - ); - - const isNewField = !existingFields.find( + const fieldIndex = existingFields.findIndex( ({ fieldMetadataId }) => field.fieldMetadataId === fieldMetadataId, ); + const fields = [...existingFields]; - const fields = isNewField - ? [ - ...existingFieldsUpdated, - { - ...field, - position: existingFieldsUpdated.length, - }, - ] - : existingFieldsUpdated; + if (fieldIndex === -1) { + fields.push({ ...field, position: existingFields.length }); + } else { + fields[fieldIndex] = { + ...field, + isVisible: !field.isVisible, + position: existingFields.length, + }; + } setSavedBoardCardFields(fields); setBoardCardFields(fields); 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 4f38d501d..2048e9e56 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 @@ -75,7 +75,7 @@ export const RecordRelationFieldCardContent = ({ objectNameSingular: objectMetadataNameSingular ?? '', }); - const modifyObjectMetadataInCache = useModifyRecordFromCache({ + const modifyRecordFromCache = useModifyRecordFromCache({ objectMetadataItem, }); @@ -136,7 +136,7 @@ export const RecordRelationFieldCardContent = ({ }, }); - modifyObjectMetadataInCache(entityId, { + modifyRecordFromCache(entityId, { [fieldName]: (relationRef, { readField }) => { const edges = readField<{ node: Reference }[]>('edges', relationRef); @@ -168,7 +168,7 @@ export const RecordRelationFieldCardContent = ({ {/* TODO: temporary to prevent removing a company from an opportunity */} - {isOpportunityCompanyRelation && ( + {!isOpportunityCompanyRelation && ( { const isToOneObject = relationType === 'TO_ONE_OBJECT'; - const relationRecords = !isToOneObject - ? fieldValue?.edges.map(({ node }: { node: any }) => node) ?? [] - : fieldValue + const relationRecords: ObjectRecord[] = + fieldValue && isToOneObject ? [fieldValue] - : []; - const relationRecordIds = relationRecords.map(({ id }: { id: string }) => id); + : fieldValue?.edges.map(({ node }: { node: ObjectRecord }) => node) ?? []; + const relationRecordIds = relationRecords.map(({ id }) => id); const dropdownId = `record-field-card-relation-picker-${fieldDefinition.label}`; @@ -138,7 +138,7 @@ export const RecordRelationFieldCardSection = () => { }, ], orderByField: 'createdAt', - mappingFunction: (recordToMap: any) => + mappingFunction: (recordToMap) => identifiersMapper?.(recordToMap, relationObjectMetadataNameSingular), selectedIds: relationRecordIds, excludeEntityIds: relationRecordIds, @@ -154,7 +154,7 @@ export const RecordRelationFieldCardSection = () => { objectNameSingular: relationObjectMetadataNameSingular, }); - const modifyObjectMetadataInCache = useModifyRecordFromCache({ + const modifyRecordFromCache = useModifyRecordFromCache({ objectMetadataItem, }); @@ -180,7 +180,7 @@ export const RecordRelationFieldCardSection = () => { }, }); - modifyObjectMetadataInCache(entityId, { + modifyRecordFromCache(entityId, { [fieldName]: (relationRef, { readField }) => { const edges = readField<{ node: Reference }[]>('edges', relationRef); @@ -248,15 +248,13 @@ export const RecordRelationFieldCardSection = () => { {!!relationRecords.length && ( - {relationRecords - .slice(0, 5) - .map((relationRecord: any, index: number) => ( - - ))} + {relationRecords.slice(0, 5).map((relationRecord, index) => ( + + ))} )} diff --git a/packages/twenty-front/src/modules/object-record/relation-picker/components/RelationPicker.tsx b/packages/twenty-front/src/modules/object-record/relation-picker/components/RelationPicker.tsx index cb50951b1..38ebc53a7 100644 --- a/packages/twenty-front/src/modules/object-record/relation-picker/components/RelationPicker.tsx +++ b/packages/twenty-front/src/modules/object-record/relation-picker/components/RelationPicker.tsx @@ -70,17 +70,15 @@ export const RelationPicker = ({ onSubmit(selectedEntity ?? null); return ( - <> - - + ); }; diff --git a/packages/twenty-front/src/modules/ui/field/display/components/EllipsisDisplay.tsx b/packages/twenty-front/src/modules/ui/field/display/components/EllipsisDisplay.tsx index 998f5ab16..e8cb03fe5 100644 --- a/packages/twenty-front/src/modules/ui/field/display/components/EllipsisDisplay.tsx +++ b/packages/twenty-front/src/modules/ui/field/display/components/EllipsisDisplay.tsx @@ -1,10 +1,21 @@ import styled from '@emotion/styled'; -const StyledEllipsisDisplay = styled.div` +const StyledEllipsisDisplay = styled.div<{ maxWidth?: number }>` + max-width: ${({ maxWidth }) => maxWidth ?? '100%'}; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; width: 100%; `; -export { StyledEllipsisDisplay as EllipsisDisplay }; +type EllipsisDisplayProps = { + children: React.ReactNode; + maxWidth?: number; +}; + +export const EllipsisDisplay = ({ + children, + maxWidth, +}: EllipsisDisplayProps) => ( + {children} +); diff --git a/packages/twenty-front/src/modules/ui/field/display/components/TextDisplay.tsx b/packages/twenty-front/src/modules/ui/field/display/components/TextDisplay.tsx index fea7d6fdd..d7fa508a8 100644 --- a/packages/twenty-front/src/modules/ui/field/display/components/TextDisplay.tsx +++ b/packages/twenty-front/src/modules/ui/field/display/components/TextDisplay.tsx @@ -2,8 +2,9 @@ import { EllipsisDisplay } from './EllipsisDisplay'; type TextDisplayProps = { text: string; + maxWidth?: number; }; -export const TextDisplay = ({ text }: TextDisplayProps) => ( - {text} +export const TextDisplay = ({ text, maxWidth }: TextDisplayProps) => ( + {text} ); diff --git a/packages/twenty-front/src/pages/settings/data-model/SettingsObjectNewField/SettingsObjectNewFieldStep2.tsx b/packages/twenty-front/src/pages/settings/data-model/SettingsObjectNewField/SettingsObjectNewFieldStep2.tsx index 7039b338c..ac787e795 100644 --- a/packages/twenty-front/src/pages/settings/data-model/SettingsObjectNewField/SettingsObjectNewFieldStep2.tsx +++ b/packages/twenty-front/src/pages/settings/data-model/SettingsObjectNewField/SettingsObjectNewFieldStep2.tsx @@ -163,7 +163,6 @@ export const SettingsObjectNewFieldStep2 = () => { }; modifyViewFromCache(view.id, { - // Todo fix typing viewFields: (viewFieldsRef, { readField }) => { const edges = readField<{ node: Reference }[]>( 'edges', diff --git a/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/favorite.object-metadata.ts b/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/favorite.object-metadata.ts index 585387aa1..b7ae721e1 100644 --- a/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/favorite.object-metadata.ts +++ b/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/favorite.object-metadata.ts @@ -5,6 +5,7 @@ import { IsSystem } from 'src/workspace/workspace-sync-metadata/decorators/is-sy import { ObjectMetadata } from 'src/workspace/workspace-sync-metadata/decorators/object-metadata.decorator'; import { BaseObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/base.object-metadata'; import { CompanyObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/company.object-metadata'; +import { OpportunityObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/opportunity.object-metadata'; import { PersonObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/person.object-metadata'; import { WorkspaceMemberObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/workspace-member.object-metadata'; @@ -55,4 +56,14 @@ export class FavoriteObjectMetadata extends BaseObjectMetadata { }) @IsNullable() company: CompanyObjectMetadata; + + @FieldMetadata({ + type: FieldMetadataType.RELATION, + label: 'Opportunity', + description: 'Favorite opportunity', + icon: 'IconTargetArrow', + joinColumn: 'opportunityId', + }) + @IsNullable() + opportunity: OpportunityObjectMetadata; } diff --git a/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/opportunity.object-metadata.ts b/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/opportunity.object-metadata.ts index 7bd141df4..7e7fb8568 100644 --- a/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/opportunity.object-metadata.ts +++ b/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/opportunity.object-metadata.ts @@ -8,6 +8,7 @@ import { RelationMetadata } from 'src/workspace/workspace-sync-metadata/decorato import { ActivityTargetObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/activity-target.object-metadata'; import { BaseObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/base.object-metadata'; import { CompanyObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/company.object-metadata'; +import { FavoriteObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/favorite.object-metadata'; import { PersonObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/person.object-metadata'; import { PipelineStepObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/pipeline-step.object-metadata'; @@ -86,6 +87,19 @@ export class OpportunityObjectMetadata extends BaseObjectMetadata { @IsNullable() company: CompanyObjectMetadata; + @FieldMetadata({ + type: FieldMetadataType.RELATION, + label: 'Favorites', + description: 'Favorites linked to the opportunity', + icon: 'IconHeart', + }) + @RelationMetadata({ + type: RelationMetadataType.ONE_TO_MANY, + objectName: 'favorite', + }) + @IsNullable() + favorites: FavoriteObjectMetadata[]; + @FieldMetadata({ type: FieldMetadataType.RELATION, label: 'Activities',