fix relation fields preview in settings (#11745)
Fixes : - display relation fields in preview settings - display note and task relation field in preview settings - - fix design (align and background color) closes https://github.com/twentyhq/twenty/issues/7084 --------- Co-authored-by: guillim <guigloo@msn.com>
This commit is contained in:
@ -9,6 +9,7 @@ import {
|
||||
AvatarChip,
|
||||
AvatarChipVariant,
|
||||
ChipSize,
|
||||
ChipVariant,
|
||||
LinkAvatarChip,
|
||||
} from 'twenty-ui/components';
|
||||
import { isModifiedEvent } from 'twenty-ui/utilities';
|
||||
@ -56,6 +57,7 @@ export const RecordChip = ({
|
||||
avatarType={recordChipData.avatarType}
|
||||
avatarUrl={recordChipData.avatarUrl ?? ''}
|
||||
className={className}
|
||||
variant={ChipVariant.Static}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@ -9,6 +9,7 @@ export const ChipFieldDisplay = () => {
|
||||
objectNameSingular,
|
||||
labelIdentifierLink,
|
||||
isLabelIdentifierCompact,
|
||||
isReadOnly,
|
||||
} = useChipFieldDisplay();
|
||||
|
||||
if (!isDefined(recordValue)) {
|
||||
@ -22,6 +23,7 @@ export const ChipFieldDisplay = () => {
|
||||
size={ChipSize.Small}
|
||||
to={labelIdentifierLink}
|
||||
isLabelHidden={isLabelIdentifierCompact}
|
||||
forceDisableClick={isReadOnly}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@ -3,14 +3,19 @@ import { NoteTarget } from '@/activities/types/NoteTarget';
|
||||
import { TaskTarget } from '@/activities/types/TaskTarget';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { RecordChip } from '@/object-record/components/RecordChip';
|
||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||
import { useFieldFocus } from '@/object-record/record-field/hooks/useFieldFocus';
|
||||
import { useRelationFromManyFieldDisplay } from '@/object-record/record-field/meta-types/hooks/useRelationFromManyFieldDisplay';
|
||||
|
||||
import { ExpandableList } from '@/ui/layout/expandable-list/components/ExpandableList';
|
||||
import { useContext } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { pascalCase } from '~/utils/string/pascalCase';
|
||||
|
||||
export const RelationFromManyFieldDisplay = () => {
|
||||
const { fieldValue, fieldDefinition } = useRelationFromManyFieldDisplay();
|
||||
const { isFocused } = useFieldFocus();
|
||||
const { isReadOnly } = useContext(FieldContext);
|
||||
|
||||
const { fieldName, objectMetadataNameSingular } = fieldDefinition.metadata;
|
||||
|
||||
@ -45,19 +50,36 @@ export const RelationFromManyFieldDisplay = () => {
|
||||
: CoreObjectNameSingular.Task;
|
||||
|
||||
const relationFieldName = fieldName === 'noteTargets' ? 'note' : 'task';
|
||||
const formattedRecords = fieldValue.map((record) => {
|
||||
if (!isDefined(record[relationFieldName])) {
|
||||
return {
|
||||
...record,
|
||||
[relationFieldName]: {
|
||||
id: 'fallback-id',
|
||||
title: pascalCase(relationFieldName),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return record;
|
||||
});
|
||||
return (
|
||||
<ExpandableList isChipCountDisplayed={isFocused}>
|
||||
{fieldValue
|
||||
.map((record) =>
|
||||
isDefined(record) && isDefined(record[relationFieldName]) ? (
|
||||
{formattedRecords
|
||||
.map((record) => {
|
||||
if (!isDefined(record)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return (
|
||||
<RecordChip
|
||||
key={record.id}
|
||||
objectNameSingular={objectNameSingular}
|
||||
record={record[relationFieldName]}
|
||||
forceDisableClick={isReadOnly}
|
||||
/>
|
||||
) : undefined,
|
||||
)
|
||||
);
|
||||
})
|
||||
.filter(isDefined)}
|
||||
</ExpandableList>
|
||||
);
|
||||
@ -69,6 +91,7 @@ export const RelationFromManyFieldDisplay = () => {
|
||||
key={record.targetObject.id}
|
||||
objectNameSingular={record.targetObjectMetadataItem.nameSingular}
|
||||
record={record.targetObject}
|
||||
forceDisableClick={isReadOnly}
|
||||
/>
|
||||
))}
|
||||
</ExpandableList>
|
||||
@ -81,6 +104,7 @@ export const RelationFromManyFieldDisplay = () => {
|
||||
key={record.id}
|
||||
objectNameSingular={objectNameSingular}
|
||||
record={record}
|
||||
forceDisableClick={isReadOnly}
|
||||
/>
|
||||
))}
|
||||
</ExpandableList>
|
||||
|
||||
@ -1,12 +1,16 @@
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { RecordChip } from '@/object-record/components/RecordChip';
|
||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||
import { useRelationToOneFieldDisplay } from '@/object-record/record-field/meta-types/hooks/useRelationToOneFieldDisplay';
|
||||
import { useContext } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const RelationToOneFieldDisplay = () => {
|
||||
const { fieldValue, fieldDefinition, generateRecordChipData } =
|
||||
useRelationToOneFieldDisplay();
|
||||
|
||||
const { isReadOnly } = useContext(FieldContext);
|
||||
|
||||
if (
|
||||
!isDefined(fieldValue) ||
|
||||
!isDefined(fieldDefinition?.metadata.relationObjectMetadataNameSingular)
|
||||
@ -24,7 +28,7 @@ export const RelationToOneFieldDisplay = () => {
|
||||
key={recordChipData.recordId}
|
||||
objectNameSingular={recordChipData.objectNameSingular}
|
||||
record={fieldValue}
|
||||
forceDisableClick={isWorkspaceMemberFieldMetadataRelation}
|
||||
forceDisableClick={isWorkspaceMemberFieldMetadataRelation || isReadOnly}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@ -18,6 +18,7 @@ export const useChipFieldDisplay = () => {
|
||||
isLabelIdentifier,
|
||||
labelIdentifierLink,
|
||||
isLabelIdentifierCompact,
|
||||
isReadOnly,
|
||||
} = useContext(FieldContext);
|
||||
|
||||
const { chipGeneratorPerObjectPerField } = useContext(
|
||||
@ -48,5 +49,6 @@ export const useChipFieldDisplay = () => {
|
||||
isLabelIdentifier,
|
||||
labelIdentifierLink,
|
||||
isLabelIdentifierCompact,
|
||||
isReadOnly,
|
||||
};
|
||||
};
|
||||
|
||||
@ -8,10 +8,10 @@ import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { FIELD_EDIT_BUTTON_WIDTH } from '@/ui/field/display/constants/FieldEditButtonWidth';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { FieldContext } from '../../contexts/FieldContext';
|
||||
import { assertFieldMetadata } from '../../types/guards/assertFieldMetadata';
|
||||
import { isFieldRelation } from '../../types/guards/isFieldRelation';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const useRelationFromManyFieldDisplay = () => {
|
||||
const { recordId, fieldDefinition, maxWidth } = useContext(FieldContext);
|
||||
|
||||
@ -17,6 +17,7 @@ import {
|
||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||
import {
|
||||
FieldMetadataType,
|
||||
RelationDefinition,
|
||||
RelationDefinitionType,
|
||||
} from '~/generated-metadata/graphql';
|
||||
type SettingsDataModelFieldRelationSettingsFormCardProps = {
|
||||
@ -80,12 +81,22 @@ export const SettingsDataModelFieldRelationSettingsFormCard = ({
|
||||
const relationType = watchFormValue('relation.type', initialRelationType);
|
||||
const relationTypeConfig = RELATION_TYPES[relationType];
|
||||
|
||||
const oppositeRelationType =
|
||||
relationType === RelationDefinitionType.MANY_TO_ONE
|
||||
? RelationDefinitionType.ONE_TO_MANY
|
||||
: RelationDefinitionType.MANY_TO_ONE;
|
||||
|
||||
return (
|
||||
<SettingsDataModelPreviewFormCard
|
||||
preview={
|
||||
<StyledPreviewContent isMobile={isMobile}>
|
||||
<StyledFieldPreviewCard
|
||||
fieldMetadataItem={fieldMetadataItem}
|
||||
fieldMetadataItem={{
|
||||
...fieldMetadataItem,
|
||||
relationDefinition: {
|
||||
direction: relationType,
|
||||
} as RelationDefinition,
|
||||
}}
|
||||
shrink
|
||||
objectMetadataItem={objectMetadataItem}
|
||||
relationObjectMetadataItem={relationObjectMetadataItem}
|
||||
@ -113,6 +124,9 @@ export const SettingsDataModelFieldRelationSettingsFormCard = ({
|
||||
initialRelationFieldMetadataItem.label,
|
||||
) || 'Field name',
|
||||
type: FieldMetadataType.RELATION,
|
||||
relationDefinition: {
|
||||
direction: oppositeRelationType,
|
||||
} as RelationDefinition,
|
||||
}}
|
||||
shrink
|
||||
objectMetadataItem={relationObjectMetadataItem}
|
||||
|
||||
@ -13,13 +13,20 @@ import { SettingsDataModelSetFieldValueEffect } from '@/settings/data-model/fiel
|
||||
import { SettingsDataModelSetPreviewRecordEffect } from '@/settings/data-model/fields/preview/components/SettingsDataModelSetRecordEffect';
|
||||
import { useFieldPreviewValue } from '@/settings/data-model/fields/preview/hooks/useFieldPreviewValue';
|
||||
import { usePreviewRecord } from '@/settings/data-model/fields/preview/hooks/usePreviewRecord';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { useIcons } from 'twenty-ui/display';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
|
||||
export type SettingsDataModelFieldPreviewProps = {
|
||||
fieldMetadataItem: Pick<
|
||||
FieldMetadataItem,
|
||||
'icon' | 'label' | 'type' | 'defaultValue' | 'options' | 'settings'
|
||||
| 'icon'
|
||||
| 'label'
|
||||
| 'type'
|
||||
| 'defaultValue'
|
||||
| 'options'
|
||||
| 'settings'
|
||||
| 'relationDefinition'
|
||||
> & {
|
||||
id?: string;
|
||||
name?: string;
|
||||
@ -31,7 +38,7 @@ export type SettingsDataModelFieldPreviewProps = {
|
||||
};
|
||||
|
||||
const StyledFieldPreview = styled.div<{ shrink?: boolean }>`
|
||||
align-items: flex-start;
|
||||
align-items: center;
|
||||
background-color: ${({ theme }) => theme.background.primary};
|
||||
border: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
@ -95,7 +102,7 @@ export const SettingsDataModelFieldPreview = ({
|
||||
fieldMetadataItem.name || `${fieldMetadataItem.type}-new-field`;
|
||||
const recordId =
|
||||
previewRecord?.id ??
|
||||
`${objectMetadataItem.nameSingular}-${fieldName}-preview`;
|
||||
`${objectMetadataItem.nameSingular}-${fieldName}-${fieldMetadataItem.relationDefinition?.direction}-preview`;
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -104,7 +111,7 @@ export const SettingsDataModelFieldPreview = ({
|
||||
instanceId: 'record-field-component-instance-id',
|
||||
}}
|
||||
>
|
||||
{previewRecord ? (
|
||||
{isDefined(previewRecord) ? (
|
||||
<SettingsDataModelSetPreviewRecordEffect
|
||||
fieldName={fieldName}
|
||||
record={previewRecord}
|
||||
@ -142,10 +149,11 @@ export const SettingsDataModelFieldPreview = ({
|
||||
relationObjectMetadataItem?.nameSingular,
|
||||
options: fieldMetadataItem.options ?? [],
|
||||
settings: fieldMetadataItem.settings,
|
||||
relationType: fieldMetadataItem.relationDefinition?.direction,
|
||||
},
|
||||
defaultValue: fieldMetadataItem.defaultValue,
|
||||
},
|
||||
isReadOnly: false,
|
||||
isReadOnly: true,
|
||||
}}
|
||||
>
|
||||
{fieldMetadataItem.type === FieldMetadataType.BOOLEAN ? (
|
||||
|
||||
@ -92,14 +92,16 @@ describe('useFieldPreviewValue', () => {
|
||||
);
|
||||
|
||||
// Then
|
||||
expect(result.current).toEqual({
|
||||
__typename: 'Person',
|
||||
id: '',
|
||||
name: {
|
||||
firstName: 'John',
|
||||
lastName: 'Doe',
|
||||
expect(result.current).toEqual([
|
||||
{
|
||||
__typename: 'Person',
|
||||
id: '',
|
||||
name: {
|
||||
firstName: 'John',
|
||||
lastName: 'Doe',
|
||||
},
|
||||
},
|
||||
});
|
||||
]);
|
||||
});
|
||||
|
||||
it("returns the field's preview value for a Select field", () => {
|
||||
|
||||
@ -8,12 +8,15 @@ import { getFieldPreviewValue } from '@/settings/data-model/fields/preview/utils
|
||||
import { getMultiSelectFieldPreviewValue } from '@/settings/data-model/fields/preview/utils/getMultiSelectFieldPreviewValue';
|
||||
import { getPhonesFieldPreviewValue } from '@/settings/data-model/fields/preview/utils/getPhonesFieldPreviewValue';
|
||||
import { getSelectFieldPreviewValue } from '@/settings/data-model/fields/preview/utils/getSelectFieldPreviewValue';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
import {
|
||||
FieldMetadataType,
|
||||
RelationDefinitionType,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
type UseFieldPreviewParams = {
|
||||
fieldMetadataItem: Pick<
|
||||
FieldMetadataItem,
|
||||
'type' | 'options' | 'defaultValue'
|
||||
'type' | 'options' | 'defaultValue' | 'relationDefinition'
|
||||
>;
|
||||
relationObjectMetadataItem?: ObjectMetadataItem;
|
||||
skip?: boolean;
|
||||
@ -43,7 +46,10 @@ export const useFieldPreviewValue = ({
|
||||
case FieldMetadataType.CURRENCY:
|
||||
return getCurrencyFieldPreviewValue({ fieldMetadataItem });
|
||||
case FieldMetadataType.RELATION:
|
||||
return relationFieldPreviewValue;
|
||||
return fieldMetadataItem.relationDefinition?.direction ===
|
||||
RelationDefinitionType.MANY_TO_ONE
|
||||
? relationFieldPreviewValue
|
||||
: [relationFieldPreviewValue];
|
||||
case FieldMetadataType.SELECT:
|
||||
return getSelectFieldPreviewValue({ fieldMetadataItem });
|
||||
case FieldMetadataType.MULTI_SELECT:
|
||||
|
||||
@ -1,12 +1,13 @@
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { getLabelIdentifierFieldMetadataItem } from '@/object-metadata/utils/getLabelIdentifierFieldMetadataItem';
|
||||
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
||||
import { isFieldValueEmpty } from '@/object-record/record-field/utils/isFieldValueEmpty';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { getFieldPreviewValue } from '@/settings/data-model/fields/preview/utils/getFieldPreviewValue';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
import { pascalCase } from '~/utils/string/pascalCase';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
type UsePreviewRecordParams = {
|
||||
objectMetadataItem: Pick<
|
||||
@ -27,8 +28,15 @@ export const usePreviewRecord = ({
|
||||
getLabelIdentifierFieldMetadataItem(objectMetadataItem);
|
||||
const skip = skipFromProps || !labelIdentifierFieldMetadataItem;
|
||||
|
||||
let recordGqlFields: Record<string, boolean> | undefined = undefined;
|
||||
if (objectMetadataItem.nameSingular === CoreObjectNameSingular.NoteTarget)
|
||||
recordGqlFields = { id: true, note: true };
|
||||
if (objectMetadataItem.nameSingular === CoreObjectNameSingular.TaskTarget)
|
||||
recordGqlFields = { id: true, task: true };
|
||||
|
||||
const { records } = useFindManyRecords({
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
recordGqlFields,
|
||||
limit: 1,
|
||||
skip,
|
||||
});
|
||||
|
||||
@ -14,10 +14,11 @@ export const AvatarChip = ({
|
||||
maxWidth,
|
||||
placeholderColorSeed,
|
||||
size,
|
||||
variant = ChipVariant.Transparent,
|
||||
}: AvatarChipProps) => (
|
||||
<Chip
|
||||
label={name}
|
||||
variant={ChipVariant.Transparent}
|
||||
variant={variant}
|
||||
size={size}
|
||||
leftComponent={
|
||||
<AvatarChipsLeftComponent
|
||||
|
||||
@ -4,7 +4,10 @@ import { AvatarChipVariant } from '@ui/components/avatar-chip/types/AvatarChipsV
|
||||
import { ChipVariant } from '@ui/components/chip/Chip';
|
||||
import { LinkChip, LinkChipProps } from '@ui/components/chip/LinkChip';
|
||||
|
||||
export type LinkAvatarChipProps = Omit<AvatarChipsCommonProps, 'clickable'> & {
|
||||
export type LinkAvatarChipProps = Omit<
|
||||
AvatarChipsCommonProps,
|
||||
'clickable' | 'variant'
|
||||
> & {
|
||||
to: string;
|
||||
onClick?: LinkChipProps['onClick'];
|
||||
variant?: AvatarChipVariant;
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import { AvatarChipsLeftComponentProps } from '@ui/components/avatar-chip/AvatarChipLeftComponent';
|
||||
import { ChipSize } from '@ui/components/chip/Chip';
|
||||
import { ChipSize, ChipVariant } from '@ui/components/chip/Chip';
|
||||
|
||||
export type AvatarChipsCommonProps = {
|
||||
size?: ChipSize;
|
||||
className?: string;
|
||||
maxWidth?: number;
|
||||
variant?: ChipVariant;
|
||||
} & AvatarChipsLeftComponentProps;
|
||||
|
||||
@ -19,6 +19,7 @@ export enum ChipVariant {
|
||||
Regular = 'regular',
|
||||
Transparent = 'transparent',
|
||||
Rounded = 'rounded',
|
||||
Static = 'static',
|
||||
}
|
||||
|
||||
export type ChipProps = {
|
||||
@ -89,7 +90,9 @@ const StyledContainer = withTheme(styled.div<
|
||||
? theme.background.transparent.light
|
||||
: variant === ChipVariant.Highlighted
|
||||
? theme.background.transparent.medium
|
||||
: 'inherit'};
|
||||
: variant === ChipVariant.Static
|
||||
? theme.background.transparent.light
|
||||
: 'inherit'};
|
||||
}
|
||||
|
||||
&:active {
|
||||
@ -98,11 +101,13 @@ const StyledContainer = withTheme(styled.div<
|
||||
? theme.background.transparent.medium
|
||||
: variant === ChipVariant.Highlighted
|
||||
? theme.background.transparent.strong
|
||||
: 'inherit'};
|
||||
: variant === ChipVariant.Static
|
||||
? theme.background.transparent.light
|
||||
: 'inherit'};
|
||||
}
|
||||
|
||||
background-color: ${({ theme, variant }) =>
|
||||
variant === ChipVariant.Highlighted
|
||||
variant === ChipVariant.Highlighted || variant === ChipVariant.Static
|
||||
? theme.background.transparent.light
|
||||
: variant === ChipVariant.Rounded
|
||||
? theme.background.transparent.lighter
|
||||
|
||||
Reference in New Issue
Block a user