Redesign Object/Field tables (#16844)
Redesign the data model pages for more clarity / better distinction between fields and relations
This commit is contained in:
+14
-29
@@ -1,8 +1,10 @@
|
||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
import { useDeleteOneFieldMetadataItem } from '@/object-metadata/hooks/useDeleteOneFieldMetadataItem';
|
||||
import { useFieldMetadataItem } from '@/object-metadata/hooks/useFieldMetadataItem';
|
||||
import { useGetRelationMetadata } from '@/object-metadata/hooks/useGetRelationMetadata';
|
||||
import { isLabelIdentifierField } from '@/object-metadata/utils/isLabelIdentifierField';
|
||||
import { isObjectMetadataSettingsReadOnly } from '@/object-record/read-only/utils/isObjectMetadataSettingsReadOnly';
|
||||
import { SettingsItemTypeTag } from '@/settings/components/SettingsItemTypeTag';
|
||||
import { RELATION_TYPES } from '@/settings/data-model/constants/RelationTypes';
|
||||
import { SettingsObjectFieldInactiveActionDropdown } from '@/settings/data-model/object-details/components/SettingsObjectFieldDisabledActionDropdown';
|
||||
import { settingsObjectFieldsFamilyState } from '@/settings/data-model/object-details/states/settingsObjectFieldsFamilyState';
|
||||
import { isFieldTypeSupportedInSettings } from '@/settings/data-model/utils/isFieldTypeSupportedInSettings';
|
||||
@@ -12,7 +14,7 @@ import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { useMemo } from 'react';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { FieldMetadataType, SettingsPath } from 'twenty-shared/types';
|
||||
import { getSettingsPath, isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
@@ -26,9 +28,6 @@ import { UndecoratedLink } from 'twenty-ui/navigation';
|
||||
import { RelationType } from '~/generated-metadata/graphql';
|
||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||
import { type SettingsObjectDetailTableItem } from '~/pages/settings/data-model/types/SettingsObjectDetailTableItem';
|
||||
|
||||
import { isObjectMetadataSettingsReadOnly } from '@/object-record/read-only/utils/isObjectMetadataSettingsReadOnly';
|
||||
import { RELATION_TYPES } from '@/settings/data-model/constants/RelationTypes';
|
||||
import { SettingsObjectFieldDataType } from './SettingsObjectFieldDataType';
|
||||
|
||||
type SettingsObjectFieldItemTableRowProps = {
|
||||
@@ -90,20 +89,13 @@ export const SettingsObjectFieldItemTableRow = ({
|
||||
status,
|
||||
}: SettingsObjectFieldItemTableRowProps) => {
|
||||
const { t } = useLingui();
|
||||
const { fieldMetadataItem, identifierType, objectMetadataItem } =
|
||||
const { fieldMetadataItem, objectMetadataItem } =
|
||||
settingsObjectDetailTableItem;
|
||||
|
||||
const currentWorkspace = useRecoilValue(currentWorkspaceState);
|
||||
const readonly = isObjectMetadataSettingsReadOnly({
|
||||
objectMetadataItem,
|
||||
workspaceCustomApplicationId:
|
||||
currentWorkspace?.workspaceCustomApplication?.id,
|
||||
});
|
||||
|
||||
const isRemoteObjectField = objectMetadataItem.isRemote;
|
||||
|
||||
const variant = objectMetadataItem.isCustom ? 'identifier' : 'field-type';
|
||||
|
||||
const navigate = useNavigateSettings();
|
||||
|
||||
const theme = useTheme();
|
||||
@@ -167,21 +159,6 @@ export const SettingsObjectFieldItemTableRow = ({
|
||||
});
|
||||
};
|
||||
|
||||
const typeLabel =
|
||||
variant === 'field-type'
|
||||
? isRemoteObjectField
|
||||
? t`Remote`
|
||||
: fieldMetadataItem.isCustom
|
||||
? t`Custom`
|
||||
: t`Standard`
|
||||
: variant === 'identifier'
|
||||
? isDefined(identifierType)
|
||||
? identifierType === 'label'
|
||||
? t`Record text`
|
||||
: t`Record image`
|
||||
: ''
|
||||
: '';
|
||||
|
||||
if (!isFieldTypeSupported) return null;
|
||||
|
||||
const isRelatedObjectLinkable =
|
||||
@@ -224,7 +201,15 @@ export const SettingsObjectFieldItemTableRow = ({
|
||||
</StyledNameTableCell>
|
||||
</UndecoratedLink>
|
||||
|
||||
<TableCell>{typeLabel}</TableCell>
|
||||
<TableCell>
|
||||
<SettingsItemTypeTag
|
||||
item={{
|
||||
isCustom: fieldMetadataItem.isCustom ?? undefined,
|
||||
isRemote: objectMetadataItem.isRemote,
|
||||
applicationId: objectMetadataItem.applicationId,
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<SettingsObjectFieldDataType
|
||||
Icon={RelationIcon}
|
||||
|
||||
+242
@@ -0,0 +1,242 @@
|
||||
import { useDeleteOneFieldMetadataItem } from '@/object-metadata/hooks/useDeleteOneFieldMetadataItem';
|
||||
import { useFieldMetadataItem } from '@/object-metadata/hooks/useFieldMetadataItem';
|
||||
import { useGetRelationMetadata } from '@/object-metadata/hooks/useGetRelationMetadata';
|
||||
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { isObjectMetadataSettingsReadOnly } from '@/object-record/read-only/utils/isObjectMetadataSettingsReadOnly';
|
||||
import { SettingsItemTypeTag } from '@/settings/components/SettingsItemTypeTag';
|
||||
import { RELATION_TYPES } from '@/settings/data-model/constants/RelationTypes';
|
||||
import { SettingsObjectFieldInactiveActionDropdown } from '@/settings/data-model/object-details/components/SettingsObjectFieldDisabledActionDropdown';
|
||||
import { TableCell } from '@/ui/layout/table/components/TableCell';
|
||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { useMemo } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { FieldMetadataType, SettingsPath } from 'twenty-shared/types';
|
||||
import { getSettingsPath, isDefined } from 'twenty-shared/utils';
|
||||
import { IconChevronRight, useIcons } from 'twenty-ui/display';
|
||||
import { UndecoratedLink } from 'twenty-ui/navigation';
|
||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||
|
||||
type SettingsObjectRelationItemTableRowProps = {
|
||||
fieldMetadataItem: FieldMetadataItem;
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
};
|
||||
|
||||
export const StyledObjectRelationTableRow = styled(TableRow)`
|
||||
grid-template-columns: 180px 100px 148px 36px;
|
||||
`;
|
||||
|
||||
const StyledNameTableCell = styled(TableCell)`
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
gap: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledNameContainer = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
gap: ${({ theme }) => theme.spacing(1)};
|
||||
`;
|
||||
|
||||
const StyledNameLabel = styled.div`
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
const StyledInactiveLabel = styled.span`
|
||||
color: ${({ theme }) => theme.font.color.extraLight};
|
||||
font-size: ${({ theme }) => theme.font.size.sm};
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
flex: 0 999 auto;
|
||||
min-width: 48px;
|
||||
|
||||
&::before {
|
||||
content: '·';
|
||||
margin-right: ${({ theme }) => theme.spacing(1)};
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledIconTableCell = styled(TableCell)`
|
||||
justify-content: center;
|
||||
padding-right: ${({ theme }) => theme.spacing(1)};
|
||||
`;
|
||||
|
||||
const StyledIconChevronRight = styled(IconChevronRight)`
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
`;
|
||||
|
||||
const StyledRelationType = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
font-size: ${({ theme }) => theme.font.size.sm};
|
||||
gap: ${({ theme }) => theme.spacing(1)};
|
||||
`;
|
||||
|
||||
const StyledLink = styled(Link)`
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
text-decoration: underline;
|
||||
text-decoration-color: ${({ theme }) => theme.border.color.strong};
|
||||
text-underline-offset: 2px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
&:hover {
|
||||
color: ${({ theme }) => theme.color.blue};
|
||||
text-decoration-color: ${({ theme }) => theme.color.blue};
|
||||
}
|
||||
`;
|
||||
|
||||
export const SettingsObjectRelationItemTableRow = ({
|
||||
fieldMetadataItem,
|
||||
objectMetadataItem,
|
||||
}: SettingsObjectRelationItemTableRowProps) => {
|
||||
const { t } = useLingui();
|
||||
const navigate = useNavigateSettings();
|
||||
const theme = useTheme();
|
||||
const { getIcon } = useIcons();
|
||||
|
||||
const Icon = getIcon(fieldMetadataItem.icon);
|
||||
|
||||
const getRelationMetadata = useGetRelationMetadata();
|
||||
const { relationObjectMetadataItem, relationType } =
|
||||
useMemo(
|
||||
() => getRelationMetadata({ fieldMetadataItem }),
|
||||
[fieldMetadataItem, getRelationMetadata],
|
||||
) ?? {};
|
||||
|
||||
const readonly = isObjectMetadataSettingsReadOnly({
|
||||
objectMetadataItem,
|
||||
});
|
||||
|
||||
const { activateMetadataField } = useFieldMetadataItem();
|
||||
const { deleteOneFieldMetadataItem } = useDeleteOneFieldMetadataItem();
|
||||
|
||||
const linkToNavigate = getSettingsPath(SettingsPath.ObjectFieldEdit, {
|
||||
objectNamePlural: objectMetadataItem.namePlural,
|
||||
fieldName: fieldMetadataItem.name,
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @nx/workspace-no-navigate-prefer-link
|
||||
const navigateToFieldEdit = () =>
|
||||
navigate(SettingsPath.ObjectFieldEdit, {
|
||||
objectNamePlural: objectMetadataItem.namePlural,
|
||||
fieldName: fieldMetadataItem.name,
|
||||
});
|
||||
|
||||
const isRelatedObjectLinkable =
|
||||
isDefined(relationObjectMetadataItem?.namePlural) &&
|
||||
!relationObjectMetadataItem.isSystem;
|
||||
|
||||
const morphRelationCount = fieldMetadataItem.morphRelations?.length;
|
||||
|
||||
const relationTypeLabel = (() => {
|
||||
if (fieldMetadataItem.type === FieldMetadataType.MORPH_RELATION) {
|
||||
return t`${morphRelationCount} Objects`;
|
||||
}
|
||||
if (isDefined(relationType) === true) {
|
||||
return RELATION_TYPES[relationType].label;
|
||||
}
|
||||
return '';
|
||||
})();
|
||||
|
||||
const RelationIcon = relationType
|
||||
? RELATION_TYPES[relationType].Icon
|
||||
: undefined;
|
||||
|
||||
const targetObjectLabel =
|
||||
isRelatedObjectLinkable && relationObjectMetadataItem
|
||||
? relationObjectMetadataItem.labelPlural
|
||||
: fieldMetadataItem.label;
|
||||
|
||||
return (
|
||||
// eslint-disable-next-line @nx/workspace-no-navigate-prefer-link
|
||||
<StyledObjectRelationTableRow onClick={navigateToFieldEdit}>
|
||||
<StyledNameTableCell>
|
||||
{!!Icon && (
|
||||
<Icon
|
||||
style={{ minWidth: theme.icon.size.md }}
|
||||
size={theme.icon.size.md}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
/>
|
||||
)}
|
||||
<StyledNameContainer>
|
||||
{isRelatedObjectLinkable ? (
|
||||
<StyledLink
|
||||
to={getSettingsPath(SettingsPath.ObjectDetail, {
|
||||
objectNamePlural: relationObjectMetadataItem.namePlural,
|
||||
})}
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
title={targetObjectLabel}
|
||||
>
|
||||
{targetObjectLabel}
|
||||
</StyledLink>
|
||||
) : (
|
||||
<StyledNameLabel title={targetObjectLabel}>
|
||||
{targetObjectLabel}
|
||||
</StyledNameLabel>
|
||||
)}
|
||||
{!fieldMetadataItem.isActive && (
|
||||
<StyledInactiveLabel>{t`Deactivated`}</StyledInactiveLabel>
|
||||
)}
|
||||
</StyledNameContainer>
|
||||
</StyledNameTableCell>
|
||||
|
||||
<TableCell>
|
||||
<SettingsItemTypeTag
|
||||
item={{
|
||||
isCustom: fieldMetadataItem.isCustom ?? undefined,
|
||||
isRemote: objectMetadataItem.isRemote,
|
||||
applicationId: objectMetadataItem.applicationId,
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
|
||||
<TableCell>
|
||||
<StyledRelationType>
|
||||
{RelationIcon && (
|
||||
<RelationIcon
|
||||
size={theme.icon.size.sm}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
/>
|
||||
)}
|
||||
{relationTypeLabel}
|
||||
</StyledRelationType>
|
||||
</TableCell>
|
||||
|
||||
<StyledIconTableCell>
|
||||
{fieldMetadataItem.isActive ? (
|
||||
<UndecoratedLink to={linkToNavigate}>
|
||||
<StyledIconChevronRight
|
||||
size={theme.icon.size.md}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
/>
|
||||
</UndecoratedLink>
|
||||
) : (
|
||||
<SettingsObjectFieldInactiveActionDropdown
|
||||
isCustomField={fieldMetadataItem.isCustom === true}
|
||||
readonly={readonly}
|
||||
fieldMetadataItemId={fieldMetadataItem.id}
|
||||
onEdit={navigateToFieldEdit}
|
||||
onActivate={() =>
|
||||
activateMetadataField(fieldMetadataItem.id, objectMetadataItem.id)
|
||||
}
|
||||
onDelete={() =>
|
||||
deleteOneFieldMetadataItem({
|
||||
idToDelete: fieldMetadataItem.id,
|
||||
objectMetadataId: objectMetadataItem.id,
|
||||
})
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</StyledIconTableCell>
|
||||
</StyledObjectRelationTableRow>
|
||||
);
|
||||
};
|
||||
+167
@@ -0,0 +1,167 @@
|
||||
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { SortableTableHeader } from '@/ui/layout/table/components/SortableTableHeader';
|
||||
import { Table } from '@/ui/layout/table/components/Table';
|
||||
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
|
||||
import { useSortedArray } from '@/ui/layout/table/hooks/useSortedArray';
|
||||
import { type TableMetadata } from '@/ui/layout/table/types/TableMetadata';
|
||||
import styled from '@emotion/styled';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { IconArchive, IconFilter, IconSearch } from 'twenty-ui/display';
|
||||
import { Button } from 'twenty-ui/input';
|
||||
import { MenuItemToggle } from 'twenty-ui/navigation';
|
||||
import { normalizeSearchText } from '~/utils/normalizeSearchText';
|
||||
import {
|
||||
SettingsObjectRelationItemTableRow,
|
||||
StyledObjectRelationTableRow,
|
||||
} from './SettingsObjectRelationItemTableRow';
|
||||
|
||||
const StyledSearchAndFilterContainer = styled.div`
|
||||
display: flex;
|
||||
gap: ${({ theme }) => theme.spacing(2)};
|
||||
padding-bottom: ${({ theme }) => theme.spacing(2)};
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledSearchInput = styled(SettingsTextInput)`
|
||||
flex: 1;
|
||||
`;
|
||||
|
||||
const SETTINGS_OBJECT_RELATION_TABLE_METADATA: TableMetadata<FieldMetadataItem> =
|
||||
{
|
||||
tableId: 'settingsObjectRelations',
|
||||
fields: [
|
||||
{
|
||||
fieldLabel: msg`Name`,
|
||||
fieldName: 'label',
|
||||
fieldType: 'string',
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
fieldLabel: msg`App`,
|
||||
fieldName: 'isCustom',
|
||||
fieldType: 'string',
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
fieldLabel: msg`Type`,
|
||||
fieldName: 'type',
|
||||
fieldType: 'string',
|
||||
align: 'left',
|
||||
},
|
||||
],
|
||||
initialSort: {
|
||||
fieldName: 'label',
|
||||
orderBy: 'AscNullsLast',
|
||||
},
|
||||
};
|
||||
|
||||
type SettingsObjectRelationsTableProps = {
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
};
|
||||
|
||||
export const SettingsObjectRelationsTable = ({
|
||||
objectMetadataItem,
|
||||
}: SettingsObjectRelationsTableProps) => {
|
||||
const { t } = useLingui();
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [showInactive, setShowInactive] = useState(true);
|
||||
|
||||
const tableMetadata = SETTINGS_OBJECT_RELATION_TABLE_METADATA;
|
||||
|
||||
const relationFields = useMemo(() => {
|
||||
return objectMetadataItem.fields.filter(
|
||||
(field) =>
|
||||
!field.isSystem &&
|
||||
(field.type === FieldMetadataType.RELATION ||
|
||||
field.type === FieldMetadataType.MORPH_RELATION),
|
||||
);
|
||||
}, [objectMetadataItem.fields]);
|
||||
|
||||
const sortedRelationFields = useSortedArray(relationFields, tableMetadata);
|
||||
|
||||
const filteredRelationFields = useMemo(() => {
|
||||
const searchNormalized = normalizeSearchText(searchTerm);
|
||||
|
||||
return sortedRelationFields.filter((field) => {
|
||||
const matchesActiveFilter = showInactive || field.isActive;
|
||||
const matchesSearch = normalizeSearchText(field.label).includes(
|
||||
searchNormalized,
|
||||
);
|
||||
return matchesActiveFilter && matchesSearch;
|
||||
});
|
||||
}, [sortedRelationFields, searchTerm, showInactive]);
|
||||
|
||||
if (relationFields.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledSearchAndFilterContainer>
|
||||
<StyledSearchInput
|
||||
instanceId="object-relation-table-search"
|
||||
LeftIcon={IconSearch}
|
||||
placeholder={t`Search a field...`}
|
||||
value={searchTerm}
|
||||
onChange={setSearchTerm}
|
||||
/>
|
||||
<Dropdown
|
||||
dropdownId="settings-relations-filter-dropdown"
|
||||
dropdownPlacement="bottom-end"
|
||||
dropdownOffset={{ x: 0, y: 8 }}
|
||||
clickableComponent={
|
||||
<Button
|
||||
Icon={IconFilter}
|
||||
size="medium"
|
||||
variant="secondary"
|
||||
accent="default"
|
||||
ariaLabel={t`Filter`}
|
||||
/>
|
||||
}
|
||||
dropdownComponents={
|
||||
<DropdownContent>
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItemToggle
|
||||
LeftIcon={IconArchive}
|
||||
onToggleChange={() => setShowInactive(!showInactive)}
|
||||
toggled={showInactive}
|
||||
text={t`Inactive`}
|
||||
toggleSize="small"
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownContent>
|
||||
}
|
||||
/>
|
||||
</StyledSearchAndFilterContainer>
|
||||
<Table>
|
||||
<StyledObjectRelationTableRow>
|
||||
{tableMetadata.fields.map((item) => (
|
||||
<SortableTableHeader
|
||||
key={item.fieldName}
|
||||
fieldName={item.fieldName}
|
||||
label={t(item.fieldLabel)}
|
||||
tableId={tableMetadata.tableId}
|
||||
initialSort={tableMetadata.initialSort}
|
||||
/>
|
||||
))}
|
||||
<TableHeader></TableHeader>
|
||||
</StyledObjectRelationTableRow>
|
||||
{filteredRelationFields.map((fieldMetadataItem) => (
|
||||
<SettingsObjectRelationItemTableRow
|
||||
key={fieldMetadataItem.id}
|
||||
fieldMetadataItem={fieldMetadataItem}
|
||||
objectMetadataItem={objectMetadataItem}
|
||||
/>
|
||||
))}
|
||||
</Table>
|
||||
</>
|
||||
);
|
||||
};
|
||||
+1
-5
@@ -1,4 +1,3 @@
|
||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
import { useUpdateOneObjectMetadataItem } from '@/object-metadata/hooks/useUpdateOneObjectMetadataItem';
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { SettingsDataModelObjectAboutForm } from '@/settings/data-model/objects/forms/components/SettingsDataModelObjectAboutForm';
|
||||
@@ -8,7 +7,7 @@ import {
|
||||
} from '@/settings/data-model/validation-schemas/settingsDataModelObjectAboutFormSchema';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
import { SettingsPath } from 'twenty-shared/types';
|
||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||
import { updatedObjectNamePluralState } from '~/pages/settings/data-model/states/updatedObjectNamePluralState';
|
||||
@@ -21,11 +20,8 @@ type SettingsUpdateDataModelObjectAboutFormProps = {
|
||||
export const SettingsUpdateDataModelObjectAboutForm = ({
|
||||
objectMetadataItem,
|
||||
}: SettingsUpdateDataModelObjectAboutFormProps) => {
|
||||
const currentWorkspace = useRecoilValue(currentWorkspaceState);
|
||||
const readonly = isObjectMetadataSettingsReadOnly({
|
||||
objectMetadataItem,
|
||||
workspaceCustomApplicationId:
|
||||
currentWorkspace?.workspaceCustomApplication?.id,
|
||||
});
|
||||
const navigate = useNavigateSettings();
|
||||
const setUpdatedObjectNamePlural = useSetRecoilState(
|
||||
|
||||
+70
-36
@@ -1,19 +1,17 @@
|
||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { SettingsPath } from 'twenty-shared/types';
|
||||
import { getSettingsPath } from 'twenty-shared/utils';
|
||||
import { SettingsObjectFieldTable } from '~/pages/settings/data-model/SettingsObjectFieldTable';
|
||||
|
||||
import { isObjectMetadataSettingsReadOnly } from '@/object-record/read-only/utils/isObjectMetadataSettingsReadOnly';
|
||||
import { SettingsObjectRelationsTable } from '@/settings/data-model/object-details/components/SettingsObjectRelationsTable';
|
||||
import styled from '@emotion/styled';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { FieldMetadataType, SettingsPath } from 'twenty-shared/types';
|
||||
import { getSettingsPath } from 'twenty-shared/utils';
|
||||
import { H2Title, IconPlus } from 'twenty-ui/display';
|
||||
import { Button } from 'twenty-ui/input';
|
||||
import { Section } from 'twenty-ui/layout';
|
||||
import { UndecoratedLink } from 'twenty-ui/navigation';
|
||||
import { isObjectMetadataSettingsReadOnly } from '@/object-record/read-only/utils/isObjectMetadataSettingsReadOnly';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { SettingsObjectFieldTable } from '~/pages/settings/data-model/SettingsObjectFieldTable';
|
||||
|
||||
const StyledDiv = styled.div`
|
||||
const StyledButtonContainer = styled.div`
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding-top: ${({ theme }) => theme.spacing(2)};
|
||||
@@ -24,42 +22,78 @@ type ObjectFieldsProps = {
|
||||
};
|
||||
|
||||
export const ObjectFields = ({ objectMetadataItem }: ObjectFieldsProps) => {
|
||||
const currentWorkspace = useRecoilValue(currentWorkspaceState);
|
||||
const readonly = isObjectMetadataSettingsReadOnly({
|
||||
objectMetadataItem,
|
||||
workspaceCustomApplicationId:
|
||||
currentWorkspace?.workspaceCustomApplication?.id,
|
||||
});
|
||||
|
||||
const { t } = useLingui();
|
||||
const objectLabelSingular = objectMetadataItem.labelSingular;
|
||||
|
||||
const hasRelations = objectMetadataItem.fields.some(
|
||||
(field) =>
|
||||
!field.isSystem &&
|
||||
(field.type === FieldMetadataType.RELATION ||
|
||||
field.type === FieldMetadataType.MORPH_RELATION),
|
||||
);
|
||||
|
||||
return (
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`Fields`}
|
||||
description={t`Customise the fields available in the ${objectLabelSingular} views.`}
|
||||
/>
|
||||
<SettingsObjectFieldTable
|
||||
objectMetadataItem={objectMetadataItem}
|
||||
mode="view"
|
||||
/>
|
||||
{!readonly && (
|
||||
<StyledDiv>
|
||||
<UndecoratedLink
|
||||
to={getSettingsPath(SettingsPath.ObjectNewFieldSelect, {
|
||||
objectNamePlural: objectMetadataItem.namePlural,
|
||||
})}
|
||||
>
|
||||
<Button
|
||||
Icon={IconPlus}
|
||||
title={t`Add Field`}
|
||||
size="small"
|
||||
variant="secondary"
|
||||
/>
|
||||
</UndecoratedLink>
|
||||
</StyledDiv>
|
||||
<>
|
||||
{hasRelations && (
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`Relations`}
|
||||
description={t`Relation between this object and other objects`}
|
||||
/>
|
||||
<SettingsObjectRelationsTable
|
||||
objectMetadataItem={objectMetadataItem}
|
||||
/>
|
||||
<StyledButtonContainer>
|
||||
{!readonly && (
|
||||
<UndecoratedLink
|
||||
to={getSettingsPath(
|
||||
SettingsPath.ObjectNewFieldConfigure,
|
||||
{ objectNamePlural: objectMetadataItem.namePlural },
|
||||
{ fieldType: FieldMetadataType.MORPH_RELATION },
|
||||
)}
|
||||
>
|
||||
<Button
|
||||
Icon={IconPlus}
|
||||
title={t`Add relation`}
|
||||
size="small"
|
||||
variant="secondary"
|
||||
/>
|
||||
</UndecoratedLink>
|
||||
)}
|
||||
</StyledButtonContainer>
|
||||
</Section>
|
||||
)}
|
||||
</Section>
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`Fields`}
|
||||
description={t`Customise the fields available in the ${objectLabelSingular} views and their display order in the ${objectLabelSingular} detail view and menus.`}
|
||||
/>
|
||||
<SettingsObjectFieldTable
|
||||
objectMetadataItem={objectMetadataItem}
|
||||
mode="view"
|
||||
excludeRelations
|
||||
/>
|
||||
<StyledButtonContainer>
|
||||
{!readonly && (
|
||||
<UndecoratedLink
|
||||
to={getSettingsPath(SettingsPath.ObjectNewFieldSelect, {
|
||||
objectNamePlural: objectMetadataItem.namePlural,
|
||||
})}
|
||||
>
|
||||
<Button
|
||||
Icon={IconPlus}
|
||||
title={t`Add Field`}
|
||||
size="small"
|
||||
variant="secondary"
|
||||
/>
|
||||
</UndecoratedLink>
|
||||
)}
|
||||
</StyledButtonContainer>
|
||||
</Section>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
-5
@@ -1,6 +1,5 @@
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
|
||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
import { useDeleteOneObjectMetadataItem } from '@/object-metadata/hooks/useDeleteOneObjectMetadataItem';
|
||||
import { useUpdateOneObjectMetadataItem } from '@/object-metadata/hooks/useUpdateOneObjectMetadataItem';
|
||||
import { isObjectMetadataSettingsReadOnly } from '@/object-record/read-only/utils/isObjectMetadataSettingsReadOnly';
|
||||
@@ -11,7 +10,6 @@ import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModa
|
||||
import { useModal } from '@/ui/layout/modal/hooks/useModal';
|
||||
import styled from '@emotion/styled';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { SettingsPath } from 'twenty-shared/types';
|
||||
import { H2Title, IconArchive, IconTrash } from 'twenty-ui/display';
|
||||
import { Button } from 'twenty-ui/input';
|
||||
@@ -47,11 +45,8 @@ export const ObjectSettings = ({
|
||||
setIsDeleting,
|
||||
}: ObjectSettingsProps) => {
|
||||
const { t } = useLingui();
|
||||
const currentWorkspace = useRecoilValue(currentWorkspaceState);
|
||||
const readonly = isObjectMetadataSettingsReadOnly({
|
||||
objectMetadataItem,
|
||||
workspaceCustomApplicationId:
|
||||
currentWorkspace?.workspaceCustomApplication?.id,
|
||||
});
|
||||
const navigate = useNavigateSettings();
|
||||
const { updateOneObjectMetadataItem } = useUpdateOneObjectMetadataItem();
|
||||
|
||||
-5
@@ -1,4 +1,3 @@
|
||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
import { useUpdateOneObjectMetadataItem } from '@/object-metadata/hooks/useUpdateOneObjectMetadataItem';
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { getActiveFieldMetadataItems } from '@/object-metadata/utils/getActiveFieldMetadataItems';
|
||||
@@ -11,7 +10,6 @@ import { t } from '@lingui/core/macro';
|
||||
import { useMemo } from 'react';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { isLabelIdentifierFieldMetadataTypes } from 'twenty-shared/utils';
|
||||
import { IconCircleOff, IconPlus, useIcons } from 'twenty-ui/display';
|
||||
import { type SelectOption } from 'twenty-ui/input';
|
||||
@@ -44,11 +42,8 @@ const StyledContainer = styled.div`
|
||||
export const SettingsDataModelObjectIdentifiersForm = ({
|
||||
objectMetadataItem,
|
||||
}: SettingsDataModelObjectIdentifiersFormProps) => {
|
||||
const currentWorkspace = useRecoilValue(currentWorkspaceState);
|
||||
const readonly = isObjectMetadataSettingsReadOnly({
|
||||
objectMetadataItem,
|
||||
workspaceCustomApplicationId:
|
||||
currentWorkspace?.workspaceCustomApplication?.id,
|
||||
});
|
||||
const formConfig = useForm<SettingsDataModelObjectIdentifiersFormValues>({
|
||||
mode: 'onTouched',
|
||||
|
||||
Reference in New Issue
Block a user