mirror of
https://github.com/twentyhq/twenty.git
synced 2024-11-27 11:03:40 +03:00
Improve opportunity behavior (#3487)
* 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 <guigon.thais@gmail.com> * Fix --------- Co-authored-by: Thaïs <guigon.thais@gmail.com>
This commit is contained in:
parent
bb91917ff8
commit
f3f20ad974
@ -229,6 +229,7 @@ export const CompanyBoardCard = () => {
|
||||
<FieldContext.Provider
|
||||
value={{
|
||||
entityId: boardCardId,
|
||||
maxWidth: 156,
|
||||
recoilScopeId: boardCardId + viewField.fieldMetadataId,
|
||||
isLabelIdentifier: false,
|
||||
fieldDefinition: {
|
||||
|
@ -260,6 +260,7 @@ export const RecordShowPage = () => {
|
||||
key={record.id + fieldMetadataItem.id}
|
||||
value={{
|
||||
entityId: record.id,
|
||||
maxWidth: 272,
|
||||
recoilScopeId: record.id + fieldMetadataItem.id,
|
||||
isLabelIdentifier: false,
|
||||
fieldDefinition:
|
||||
|
@ -28,6 +28,7 @@ export type GenericFieldContextType = {
|
||||
isLabelIdentifier: boolean;
|
||||
basePathToShowPage?: string;
|
||||
clearable?: boolean;
|
||||
maxWidth?: number;
|
||||
};
|
||||
|
||||
export const FieldContext = createContext<GenericFieldContextType>(
|
||||
|
@ -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 <TextDisplay text={fieldValue} />;
|
||||
return <TextDisplay text={fieldValue} maxWidth={maxWidth} />;
|
||||
};
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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 = ({
|
||||
<FieldDisplay />
|
||||
</FieldContextProvider>
|
||||
{/* TODO: temporary to prevent removing a company from an opportunity */}
|
||||
{isOpportunityCompanyRelation && (
|
||||
{!isOpportunityCompanyRelation && (
|
||||
<DropdownScope dropdownScopeId={dropdownScopeId}>
|
||||
<Dropdown
|
||||
dropdownId={dropdownScopeId}
|
||||
|
@ -20,6 +20,7 @@ import { SingleEntitySelectMenuItemsWithSearch } from '@/object-record/relation-
|
||||
import { useRelationPicker } from '@/object-record/relation-picker/hooks/useRelationPicker';
|
||||
import { RelationPickerScope } from '@/object-record/relation-picker/scopes/RelationPickerScope';
|
||||
import { EntityForSelect } from '@/object-record/relation-picker/types/EntityForSelect';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { useFilteredSearchEntityQuery } from '@/search/hooks/useFilteredSearchEntityQuery';
|
||||
import { IconForbid, IconPlus } from '@/ui/display/icon';
|
||||
import { LightIconButton } from '@/ui/input/button/components/LightIconButton';
|
||||
@ -111,12 +112,11 @@ export const RecordRelationFieldCardSection = () => {
|
||||
|
||||
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 = () => {
|
||||
</StyledHeader>
|
||||
{!!relationRecords.length && (
|
||||
<Card>
|
||||
{relationRecords
|
||||
.slice(0, 5)
|
||||
.map((relationRecord: any, index: number) => (
|
||||
<RecordRelationFieldCardContent
|
||||
key={`${relationRecord.id}${relationLabelIdentifierFieldMetadata?.id}`}
|
||||
divider={index < relationRecords.length - 1}
|
||||
relationRecord={relationRecord}
|
||||
/>
|
||||
))}
|
||||
{relationRecords.slice(0, 5).map((relationRecord, index) => (
|
||||
<RecordRelationFieldCardContent
|
||||
key={`${relationRecord.id}${relationLabelIdentifierFieldMetadata?.id}`}
|
||||
divider={index < relationRecords.length - 1}
|
||||
relationRecord={relationRecord}
|
||||
/>
|
||||
))}
|
||||
</Card>
|
||||
)}
|
||||
</RelationPickerScope>
|
||||
|
@ -70,17 +70,15 @@ export const RelationPicker = ({
|
||||
onSubmit(selectedEntity ?? null);
|
||||
|
||||
return (
|
||||
<>
|
||||
<SingleEntitySelect
|
||||
EmptyIcon={IconForbid}
|
||||
emptyLabel={'No ' + fieldDefinition.label}
|
||||
entitiesToSelect={entities.entitiesToSelect}
|
||||
loading={entities.loading}
|
||||
onCancel={onCancel}
|
||||
onEntitySelected={handleEntitySelected}
|
||||
selectedEntity={entities.selectedEntities[0]}
|
||||
width={width}
|
||||
/>
|
||||
</>
|
||||
<SingleEntitySelect
|
||||
EmptyIcon={IconForbid}
|
||||
emptyLabel={'No ' + fieldDefinition.label}
|
||||
entitiesToSelect={entities.entitiesToSelect}
|
||||
loading={entities.loading}
|
||||
onCancel={onCancel}
|
||||
onEntitySelected={handleEntitySelected}
|
||||
selectedEntity={entities.selectedEntities[0]}
|
||||
width={width}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -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) => (
|
||||
<StyledEllipsisDisplay style={{ maxWidth }}>{children}</StyledEllipsisDisplay>
|
||||
);
|
||||
|
@ -2,8 +2,9 @@ import { EllipsisDisplay } from './EllipsisDisplay';
|
||||
|
||||
type TextDisplayProps = {
|
||||
text: string;
|
||||
maxWidth?: number;
|
||||
};
|
||||
|
||||
export const TextDisplay = ({ text }: TextDisplayProps) => (
|
||||
<EllipsisDisplay>{text}</EllipsisDisplay>
|
||||
export const TextDisplay = ({ text, maxWidth }: TextDisplayProps) => (
|
||||
<EllipsisDisplay maxWidth={maxWidth}>{text}</EllipsisDisplay>
|
||||
);
|
||||
|
@ -163,7 +163,6 @@ export const SettingsObjectNewFieldStep2 = () => {
|
||||
};
|
||||
|
||||
modifyViewFromCache(view.id, {
|
||||
// Todo fix typing
|
||||
viewFields: (viewFieldsRef, { readField }) => {
|
||||
const edges = readField<{ node: Reference }[]>(
|
||||
'edges',
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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',
|
||||
|
Loading…
Reference in New Issue
Block a user