mirror of
https://github.com/twentyhq/twenty.git
synced 2024-11-27 11:03:40 +03:00
feat: display identifier field in Object Detail page for custom objects (#3329)
* feat: display identifier field in Object Detail page for custom objects Closes #3301 * fix: show Name as object label identifier by default * Minor improvements --------- Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
parent
0dc39db314
commit
09a2a656e2
@ -1,5 +1,6 @@
|
|||||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||||
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
export const DEFAULT_LABEL_IDENTIFIER_FIELD_NAME = 'name';
|
export const DEFAULT_LABEL_IDENTIFIER_FIELD_NAME = 'name';
|
||||||
|
|
||||||
@ -7,12 +8,13 @@ export const isLabelIdentifierField = ({
|
|||||||
fieldMetadataItem,
|
fieldMetadataItem,
|
||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
}: {
|
}: {
|
||||||
fieldMetadataItem: FieldMetadataItem;
|
fieldMetadataItem: Pick<FieldMetadataItem, 'id' | 'name'>;
|
||||||
objectMetadataItem: ObjectMetadataItem;
|
objectMetadataItem: Pick<
|
||||||
|
ObjectMetadataItem,
|
||||||
|
'labelIdentifierFieldMetadataId'
|
||||||
|
>;
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return isDefined(objectMetadataItem.labelIdentifierFieldMetadataId)
|
||||||
fieldMetadataItem.id ===
|
? fieldMetadataItem.id === objectMetadataItem.labelIdentifierFieldMetadataId
|
||||||
objectMetadataItem.labelIdentifierFieldMetadataId ||
|
: fieldMetadataItem.name === DEFAULT_LABEL_IDENTIFIER_FIELD_NAME;
|
||||||
fieldMetadataItem.name === DEFAULT_LABEL_IDENTIFIER_FIELD_NAME
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
@ -6,9 +6,11 @@ import styled from '@emotion/styled';
|
|||||||
import { useRelationMetadata } from '@/object-metadata/hooks/useRelationMetadata';
|
import { useRelationMetadata } from '@/object-metadata/hooks/useRelationMetadata';
|
||||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||||
import { getObjectSlug } from '@/object-metadata/utils/getObjectSlug';
|
import { getObjectSlug } from '@/object-metadata/utils/getObjectSlug';
|
||||||
|
import { FieldIdentifierType } from '@/settings/data-model/types/FieldIdentifierType';
|
||||||
import { useIcons } from '@/ui/display/icon/hooks/useIcons';
|
import { useIcons } from '@/ui/display/icon/hooks/useIcons';
|
||||||
import { TableCell } from '@/ui/layout/table/components/TableCell';
|
import { TableCell } from '@/ui/layout/table/components/TableCell';
|
||||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||||
|
import { Nullable } from '~/types/Nullable';
|
||||||
|
|
||||||
import { relationTypes } from '../../constants/relationTypes';
|
import { relationTypes } from '../../constants/relationTypes';
|
||||||
import { settingsFieldMetadataTypes } from '../../constants/settingsFieldMetadataTypes';
|
import { settingsFieldMetadataTypes } from '../../constants/settingsFieldMetadataTypes';
|
||||||
@ -18,6 +20,8 @@ import { SettingsObjectFieldDataType } from './SettingsObjectFieldDataType';
|
|||||||
type SettingsObjectFieldItemTableRowProps = {
|
type SettingsObjectFieldItemTableRowProps = {
|
||||||
ActionIcon: ReactNode;
|
ActionIcon: ReactNode;
|
||||||
fieldMetadataItem: FieldMetadataItem;
|
fieldMetadataItem: FieldMetadataItem;
|
||||||
|
identifierType?: Nullable<FieldIdentifierType>;
|
||||||
|
variant?: 'field-type' | 'identifier';
|
||||||
};
|
};
|
||||||
|
|
||||||
export const StyledObjectFieldTableRow = styled(TableRow)`
|
export const StyledObjectFieldTableRow = styled(TableRow)`
|
||||||
@ -37,6 +41,8 @@ const StyledIconTableCell = styled(TableCell)`
|
|||||||
export const SettingsObjectFieldItemTableRow = ({
|
export const SettingsObjectFieldItemTableRow = ({
|
||||||
ActionIcon,
|
ActionIcon,
|
||||||
fieldMetadataItem,
|
fieldMetadataItem,
|
||||||
|
identifierType,
|
||||||
|
variant = 'field-type',
|
||||||
}: SettingsObjectFieldItemTableRowProps) => {
|
}: SettingsObjectFieldItemTableRowProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { getIcon } = useIcons();
|
const { getIcon } = useIcons();
|
||||||
@ -66,7 +72,11 @@ export const SettingsObjectFieldItemTableRow = ({
|
|||||||
{fieldMetadataItem.label}
|
{fieldMetadataItem.label}
|
||||||
</StyledNameTableCell>
|
</StyledNameTableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
{fieldMetadataItem.isCustom ? 'Custom' : 'Standard'}
|
{variant === 'field-type' &&
|
||||||
|
(fieldMetadataItem.isCustom ? 'Custom' : 'Standard')}
|
||||||
|
{variant === 'identifier' &&
|
||||||
|
!!identifierType &&
|
||||||
|
(identifierType === 'label' ? 'Record text' : 'Record image')}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<SettingsObjectFieldDataType
|
<SettingsObjectFieldDataType
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
export type FieldIdentifierType = 'label' | 'image';
|
@ -0,0 +1,28 @@
|
|||||||
|
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||||
|
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||||
|
import { isLabelIdentifierField } from '@/object-metadata/utils/isLabelIdentifierField';
|
||||||
|
import { FieldIdentifierType } from '@/settings/data-model/types/FieldIdentifierType';
|
||||||
|
|
||||||
|
export const getFieldIdentifierType = (
|
||||||
|
activeFieldMetadatItem: FieldMetadataItem,
|
||||||
|
activeObjectMetadataItem: ObjectMetadataItem,
|
||||||
|
): FieldIdentifierType | undefined => {
|
||||||
|
const fieldIsLabelIdentifier = isLabelIdentifierField({
|
||||||
|
fieldMetadataItem: activeFieldMetadatItem,
|
||||||
|
objectMetadataItem: activeObjectMetadataItem,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (fieldIsLabelIdentifier) {
|
||||||
|
return 'label';
|
||||||
|
}
|
||||||
|
|
||||||
|
const fieldIsImageIdentifier =
|
||||||
|
activeObjectMetadataItem.imageIdentifierFieldMetadataId ===
|
||||||
|
activeFieldMetadatItem.id;
|
||||||
|
|
||||||
|
if (fieldIsImageIdentifier) {
|
||||||
|
return 'image';
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
};
|
@ -15,6 +15,7 @@ import {
|
|||||||
SettingsObjectFieldItemTableRow,
|
SettingsObjectFieldItemTableRow,
|
||||||
StyledObjectFieldTableRow,
|
StyledObjectFieldTableRow,
|
||||||
} from '@/settings/data-model/object-details/components/SettingsObjectFieldItemTableRow';
|
} from '@/settings/data-model/object-details/components/SettingsObjectFieldItemTableRow';
|
||||||
|
import { getFieldIdentifierType } from '@/settings/data-model/utils/getFieldIdentifierType';
|
||||||
import { AppPath } from '@/types/AppPath';
|
import { AppPath } from '@/types/AppPath';
|
||||||
import { IconPlus, IconSettings } from '@/ui/display/icon';
|
import { IconPlus, IconSettings } from '@/ui/display/icon';
|
||||||
import { H2Title } from '@/ui/display/typography/components/H2Title';
|
import { H2Title } from '@/ui/display/typography/components/H2Title';
|
||||||
@ -25,7 +26,6 @@ import { Table } from '@/ui/layout/table/components/Table';
|
|||||||
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
|
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
|
||||||
import { TableSection } from '@/ui/layout/table/components/TableSection';
|
import { TableSection } from '@/ui/layout/table/components/TableSection';
|
||||||
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
|
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
|
||||||
import { sortFieldMetadataItem } from '~/utils/sortFieldMetadataItem';
|
|
||||||
|
|
||||||
const StyledDiv = styled.div`
|
const StyledDiv = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -52,16 +52,12 @@ export const SettingsObjectDetail = () => {
|
|||||||
|
|
||||||
if (!activeObjectMetadataItem) return null;
|
if (!activeObjectMetadataItem) return null;
|
||||||
|
|
||||||
const activeMetadataFields = activeObjectMetadataItem.fields
|
const activeMetadataFields = activeObjectMetadataItem.fields.filter(
|
||||||
.filter(
|
(metadataField) => metadataField.isActive && !metadataField.isSystem,
|
||||||
(metadataField) => metadataField.isActive && !metadataField.isSystem,
|
);
|
||||||
)
|
const disabledMetadataFields = activeObjectMetadataItem.fields.filter(
|
||||||
.sort(sortFieldMetadataItem);
|
(metadataField) => !metadataField.isActive && !metadataField.isSystem,
|
||||||
const disabledMetadataFields = activeObjectMetadataItem.fields
|
);
|
||||||
.filter(
|
|
||||||
(metadataField) => !metadataField.isActive && !metadataField.isSystem,
|
|
||||||
)
|
|
||||||
.sort(sortFieldMetadataItem);
|
|
||||||
|
|
||||||
const handleDisableObject = async () => {
|
const handleDisableObject = async () => {
|
||||||
await disableObjectMetadataItem(activeObjectMetadataItem);
|
await disableObjectMetadataItem(activeObjectMetadataItem);
|
||||||
@ -98,7 +94,11 @@ export const SettingsObjectDetail = () => {
|
|||||||
<Table>
|
<Table>
|
||||||
<StyledObjectFieldTableRow>
|
<StyledObjectFieldTableRow>
|
||||||
<TableHeader>Name</TableHeader>
|
<TableHeader>Name</TableHeader>
|
||||||
<TableHeader>Field type</TableHeader>
|
<TableHeader>
|
||||||
|
{activeObjectMetadataItem.isCustom
|
||||||
|
? 'Identifier'
|
||||||
|
: 'Field type'}
|
||||||
|
</TableHeader>
|
||||||
<TableHeader>Data type</TableHeader>
|
<TableHeader>Data type</TableHeader>
|
||||||
<TableHeader></TableHeader>
|
<TableHeader></TableHeader>
|
||||||
</StyledObjectFieldTableRow>
|
</StyledObjectFieldTableRow>
|
||||||
@ -107,6 +107,15 @@ export const SettingsObjectDetail = () => {
|
|||||||
{activeMetadataFields.map((activeMetadataField) => (
|
{activeMetadataFields.map((activeMetadataField) => (
|
||||||
<SettingsObjectFieldItemTableRow
|
<SettingsObjectFieldItemTableRow
|
||||||
key={activeMetadataField.id}
|
key={activeMetadataField.id}
|
||||||
|
identifierType={getFieldIdentifierType(
|
||||||
|
activeMetadataField,
|
||||||
|
activeObjectMetadataItem,
|
||||||
|
)}
|
||||||
|
variant={
|
||||||
|
activeObjectMetadataItem.isCustom
|
||||||
|
? 'identifier'
|
||||||
|
: 'field-type'
|
||||||
|
}
|
||||||
fieldMetadataItem={activeMetadataField}
|
fieldMetadataItem={activeMetadataField}
|
||||||
ActionIcon={
|
ActionIcon={
|
||||||
<SettingsObjectFieldActiveActionDropdown
|
<SettingsObjectFieldActiveActionDropdown
|
||||||
@ -134,6 +143,11 @@ export const SettingsObjectDetail = () => {
|
|||||||
{disabledMetadataFields.map((disabledMetadataField) => (
|
{disabledMetadataFields.map((disabledMetadataField) => (
|
||||||
<SettingsObjectFieldItemTableRow
|
<SettingsObjectFieldItemTableRow
|
||||||
key={disabledMetadataField.id}
|
key={disabledMetadataField.id}
|
||||||
|
variant={
|
||||||
|
activeObjectMetadataItem.isCustom
|
||||||
|
? 'identifier'
|
||||||
|
: 'field-type'
|
||||||
|
}
|
||||||
fieldMetadataItem={disabledMetadataField}
|
fieldMetadataItem={disabledMetadataField}
|
||||||
ActionIcon={
|
ActionIcon={
|
||||||
<SettingsObjectFieldDisabledActionDropdown
|
<SettingsObjectFieldDisabledActionDropdown
|
||||||
|
Loading…
Reference in New Issue
Block a user