Redesign Object/Field tables (#16844)
Redesign the data model pages for more clarity / better distinction between fields and relations
This commit is contained in:
+19
-8
@@ -12,13 +12,12 @@ describe('isObjectMetadataSettingsReadOnly', () => {
|
||||
isUIReadOnly: false,
|
||||
isRemote: false,
|
||||
},
|
||||
workspaceCustomApplicationId: 'workspaceApplicationId',
|
||||
});
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true if object is managed by application', () => {
|
||||
it('should return true if object is remote', () => {
|
||||
const result = isObjectMetadataSettingsReadOnly({
|
||||
objectPermissions: {
|
||||
canUpdateObjectRecords: true,
|
||||
@@ -26,24 +25,36 @@ describe('isObjectMetadataSettingsReadOnly', () => {
|
||||
restrictedFields: {},
|
||||
},
|
||||
objectMetadataItem: {
|
||||
applicationId: 'applicationId',
|
||||
isUIReadOnly: false,
|
||||
isRemote: false,
|
||||
isRemote: true,
|
||||
},
|
||||
workspaceCustomApplicationId: null,
|
||||
});
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if object is owned by workspace custom application', () => {
|
||||
it('should return true if object is UI read only', () => {
|
||||
const result = isObjectMetadataSettingsReadOnly({
|
||||
objectMetadataItem: {
|
||||
isUIReadOnly: true,
|
||||
isRemote: false,
|
||||
},
|
||||
});
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for standard/third-party objects (they are editable via standardOverrides)', () => {
|
||||
const result = isObjectMetadataSettingsReadOnly({
|
||||
objectPermissions: {
|
||||
canUpdateObjectRecords: true,
|
||||
objectMetadataId: '123',
|
||||
restrictedFields: {},
|
||||
},
|
||||
objectMetadataItem: {
|
||||
isUIReadOnly: false,
|
||||
isRemote: false,
|
||||
applicationId: 'workspaceApplicationId',
|
||||
},
|
||||
workspaceCustomApplicationId: 'workspaceApplicationId',
|
||||
});
|
||||
|
||||
expect(result).toBe(false);
|
||||
|
||||
+6
-17
@@ -1,28 +1,17 @@
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { isObjectMetadataReadOnly } from '@/object-record/read-only/utils/isObjectMetadataReadOnly';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { type ObjectPermission } from '~/generated/graphql';
|
||||
|
||||
type IsObjectMetadataReadOnlyParams = {
|
||||
type IsObjectMetadataSettingsReadOnlyParams = {
|
||||
objectPermissions?: ObjectPermission;
|
||||
objectMetadataItem?: Pick<
|
||||
ObjectMetadataItem,
|
||||
'isUIReadOnly' | 'isRemote' | 'applicationId'
|
||||
>;
|
||||
workspaceCustomApplicationId: string | null;
|
||||
objectMetadataItem?: Pick<ObjectMetadataItem, 'isUIReadOnly' | 'isRemote'>;
|
||||
};
|
||||
|
||||
// Returns true only for remote or UI read-only objects
|
||||
// Standard and third-party app objects are editable (label/icon/description via standardOverrides)
|
||||
export const isObjectMetadataSettingsReadOnly = ({
|
||||
objectPermissions,
|
||||
objectMetadataItem,
|
||||
workspaceCustomApplicationId,
|
||||
}: IsObjectMetadataReadOnlyParams) => {
|
||||
return (
|
||||
isObjectMetadataReadOnly({ objectPermissions, objectMetadataItem }) ||
|
||||
(isDefined(objectMetadataItem?.applicationId)
|
||||
? isDefined(workspaceCustomApplicationId)
|
||||
? objectMetadataItem.applicationId !== workspaceCustomApplicationId
|
||||
: true
|
||||
: false)
|
||||
);
|
||||
}: IsObjectMetadataSettingsReadOnlyParams) => {
|
||||
return isObjectMetadataReadOnly({ objectPermissions, objectMetadataItem });
|
||||
};
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
import { getItemTagInfo } from '@/settings/data-model/utils/getItemTagInfo';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { Tag } from 'twenty-ui/components';
|
||||
import { Avatar } from 'twenty-ui/display';
|
||||
|
||||
type SettingsItemTypeTagProps = {
|
||||
item: {
|
||||
@@ -12,10 +14,18 @@ type SettingsItemTypeTagProps = {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
font-size: ${({ theme }) => theme.font.size.sm};
|
||||
gap: ${({ theme }) => theme.spacing(1)};
|
||||
`;
|
||||
|
||||
export const SettingsItemTypeTag = ({
|
||||
className,
|
||||
item: { isCustom, isRemote, applicationId },
|
||||
}: SettingsItemTypeTagProps) => {
|
||||
const theme = useTheme();
|
||||
const currentWorkspace = useRecoilValue(currentWorkspaceState);
|
||||
const itemTagInfo = getItemTagInfo({
|
||||
objectMetadataItem: { isCustom, isRemote, applicationId },
|
||||
@@ -24,11 +34,16 @@ export const SettingsItemTypeTag = ({
|
||||
});
|
||||
|
||||
return (
|
||||
<Tag
|
||||
className={className}
|
||||
color={itemTagInfo.labelColor}
|
||||
text={itemTagInfo.labelText}
|
||||
weight="medium"
|
||||
/>
|
||||
<StyledContainer className={className}>
|
||||
<Avatar
|
||||
placeholder={itemTagInfo.labelText}
|
||||
placeholderColorSeed={itemTagInfo.labelText}
|
||||
type="squared"
|
||||
size="xs"
|
||||
color={theme.tag.text[itemTagInfo.labelColor]}
|
||||
backgroundColor={theme.tag.background[itemTagInfo.labelColor]}
|
||||
/>
|
||||
{itemTagInfo.labelText}
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
|
||||
+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',
|
||||
|
||||
@@ -14,16 +14,13 @@ import styled from '@emotion/styled';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { AppPath, SettingsPath } from 'twenty-shared/types';
|
||||
|
||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
import { isObjectMetadataSettingsReadOnly } from '@/object-record/read-only/utils/isObjectMetadataSettingsReadOnly';
|
||||
import { SettingsItemTypeTag } from '@/settings/components/SettingsItemTypeTag';
|
||||
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { getSettingsPath, isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
H3Title,
|
||||
IconCodeCircle,
|
||||
IconListDetails,
|
||||
IconPlus,
|
||||
@@ -43,16 +40,6 @@ const StyledContentContainer = styled.div`
|
||||
padding-left: 0;
|
||||
`;
|
||||
|
||||
const StyledObjectTypeTag = styled(SettingsItemTypeTag)`
|
||||
box-sizing: border-box;
|
||||
height: ${({ theme }) => theme.spacing(5)};
|
||||
margin-left: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledTitleContainer = styled.div`
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
export const SettingsObjectDetailPage = () => {
|
||||
const navigateApp = useNavigateApp();
|
||||
const { t } = useLingui();
|
||||
@@ -70,11 +57,8 @@ export const SettingsObjectDetailPage = () => {
|
||||
findObjectMetadataItemByNamePlural(objectNamePlural) ??
|
||||
findObjectMetadataItemByNamePlural(updatedObjectNamePlural);
|
||||
|
||||
const currentWorkspace = useRecoilValue(currentWorkspaceState);
|
||||
const readonly = isObjectMetadataSettingsReadOnly({
|
||||
objectMetadataItem,
|
||||
workspaceCustomApplicationId:
|
||||
currentWorkspace?.workspaceCustomApplication?.id,
|
||||
});
|
||||
|
||||
const activeTabId = useRecoilComponentValue(
|
||||
@@ -157,12 +141,7 @@ export const SettingsObjectDetailPage = () => {
|
||||
return (
|
||||
<>
|
||||
<SubMenuTopBarContainer
|
||||
title={
|
||||
<StyledTitleContainer>
|
||||
<H3Title title={objectMetadataItem.labelPlural} />
|
||||
<StyledObjectTypeTag item={objectMetadataItem} />
|
||||
</StyledTitleContainer>
|
||||
}
|
||||
title={objectMetadataItem.labelPlural}
|
||||
links={[
|
||||
{
|
||||
children: t`Workspace`,
|
||||
|
||||
@@ -5,7 +5,6 @@ import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { type z } from 'zod';
|
||||
|
||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
import { useFieldMetadataItem } from '@/object-metadata/hooks/useFieldMetadataItem';
|
||||
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
|
||||
import { useGetRelationMetadata } from '@/object-metadata/hooks/useGetRelationMetadata';
|
||||
@@ -30,7 +29,7 @@ import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMe
|
||||
import { shouldNavigateBackToMemorizedUrlOnSaveState } from '@/ui/navigation/states/shouldNavigateBackToMemorizedUrlOnSaveState';
|
||||
import styled from '@emotion/styled';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { AppPath, SettingsPath } from 'twenty-shared/types';
|
||||
import { getSettingsPath, isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
@@ -84,11 +83,8 @@ export const SettingsObjectFieldEdit = () => {
|
||||
const objectMetadataItem =
|
||||
findObjectMetadataItemByNamePlural(objectNamePlural);
|
||||
|
||||
const currentWorkspace = useRecoilValue(currentWorkspaceState);
|
||||
const readonly = isObjectMetadataSettingsReadOnly({
|
||||
objectMetadataItem,
|
||||
workspaceCustomApplicationId:
|
||||
currentWorkspace?.workspaceCustomApplication?.id,
|
||||
});
|
||||
|
||||
const {
|
||||
|
||||
@@ -18,6 +18,7 @@ import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useRecoilState } from 'recoil';
|
||||
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';
|
||||
@@ -25,7 +26,18 @@ import { useMapFieldMetadataItemToSettingsObjectDetailTableItem } from '~/pages/
|
||||
import { type SettingsObjectDetailTableItem } from '~/pages/settings/data-model/types/SettingsObjectDetailTableItem';
|
||||
import { normalizeSearchText } from '~/utils/normalizeSearchText';
|
||||
|
||||
const GET_SETTINGS_OBJECT_DETAIL_TABLE_METADATA_STANDARD: TableMetadata<SettingsObjectDetailTableItem> =
|
||||
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_FIELD_TABLE_METADATA: TableMetadata<SettingsObjectDetailTableItem> =
|
||||
{
|
||||
tableId: 'settingsObjectDetail',
|
||||
fields: [
|
||||
@@ -36,7 +48,7 @@ const GET_SETTINGS_OBJECT_DETAIL_TABLE_METADATA_STANDARD: TableMetadata<Settings
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
fieldLabel: msg`Field type`,
|
||||
fieldLabel: msg`App`,
|
||||
fieldName: 'fieldType',
|
||||
fieldType: 'string',
|
||||
align: 'left',
|
||||
@@ -54,63 +66,23 @@ const GET_SETTINGS_OBJECT_DETAIL_TABLE_METADATA_STANDARD: TableMetadata<Settings
|
||||
},
|
||||
};
|
||||
|
||||
const GET_SETTINGS_OBJECT_DETAIL_TABLE_METADATA_CUSTOM: TableMetadata<SettingsObjectDetailTableItem> =
|
||||
{
|
||||
tableId: 'settingsObjectDetail',
|
||||
fields: [
|
||||
{
|
||||
fieldLabel: msg`Name`,
|
||||
fieldName: 'label',
|
||||
fieldType: 'string',
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
fieldLabel: msg`Identifier`,
|
||||
fieldName: 'identifierType',
|
||||
fieldType: 'string',
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
fieldLabel: msg`Data type`,
|
||||
fieldName: 'dataType',
|
||||
fieldType: 'string',
|
||||
align: 'left',
|
||||
},
|
||||
],
|
||||
initialSort: {
|
||||
fieldName: 'label',
|
||||
orderBy: 'AscNullsLast',
|
||||
},
|
||||
};
|
||||
|
||||
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;
|
||||
`;
|
||||
|
||||
export type SettingsObjectFieldTableProps = {
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
mode: 'view' | 'new-field';
|
||||
excludeRelations?: boolean;
|
||||
};
|
||||
|
||||
// TODO: find another way than using mode which feels like it could be replaced by another pattern
|
||||
export const SettingsObjectFieldTable = ({
|
||||
objectMetadataItem,
|
||||
mode,
|
||||
excludeRelations = false,
|
||||
}: SettingsObjectFieldTableProps) => {
|
||||
const { t } = useLingui();
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [showInactive, setShowInactive] = useState(true);
|
||||
|
||||
const tableMetadata = objectMetadataItem.isCustom
|
||||
? GET_SETTINGS_OBJECT_DETAIL_TABLE_METADATA_CUSTOM
|
||||
: GET_SETTINGS_OBJECT_DETAIL_TABLE_METADATA_STANDARD;
|
||||
const tableMetadata = SETTINGS_OBJECT_FIELD_TABLE_METADATA;
|
||||
|
||||
const { mapFieldMetadataItemToSettingsObjectDetailTableItem } =
|
||||
useMapFieldMetadataItemToSettingsObjectDetailTableItem(objectMetadataItem);
|
||||
@@ -130,14 +102,23 @@ export const SettingsObjectFieldTable = ({
|
||||
(fieldMetadataItem) => !fieldMetadataItem.isSystem,
|
||||
);
|
||||
|
||||
const fieldsToDisplay = excludeRelations
|
||||
? nonSystemFields?.filter(
|
||||
(fieldMetadataItem) =>
|
||||
fieldMetadataItem.type !== FieldMetadataType.RELATION &&
|
||||
fieldMetadataItem.type !== FieldMetadataType.MORPH_RELATION,
|
||||
)
|
||||
: nonSystemFields;
|
||||
|
||||
return (
|
||||
nonSystemFields?.map(
|
||||
fieldsToDisplay?.map(
|
||||
mapFieldMetadataItemToSettingsObjectDetailTableItem,
|
||||
) ?? []
|
||||
);
|
||||
}, [
|
||||
settingsObjectFields,
|
||||
mapFieldMetadataItemToSettingsObjectDetailTableItem,
|
||||
excludeRelations,
|
||||
]);
|
||||
|
||||
const sortedAllObjectSettingsDetailItems = useSortedArray(
|
||||
|
||||
-5
@@ -35,11 +35,6 @@ export const Default: Story = {
|
||||
const employeeInput = await canvas.findByPlaceholderText('Employees');
|
||||
await userEvent.type(employeeInput, 'Test');
|
||||
|
||||
const descriptionInput = await canvas.findByPlaceholderText(
|
||||
'Write a description',
|
||||
);
|
||||
await userEvent.type(descriptionInput, 'Test description');
|
||||
|
||||
const saveButton = await canvas.findByText('Save');
|
||||
await new Promise((resolve) => setTimeout(resolve, 5000));
|
||||
await userEvent.click(saveButton);
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ export const GET_SETTINGS_OBJECT_TABLE_METADATA: TableMetadata<SettingsObjectTab
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
fieldLabel: msg`Type`,
|
||||
fieldLabel: msg`App`,
|
||||
fieldName: 'objectTypeLabel',
|
||||
fieldType: 'string',
|
||||
align: 'left',
|
||||
|
||||
-9
@@ -4,7 +4,6 @@ import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons
|
||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||
import { SettingsDataModelNewFieldBreadcrumbDropDown } from '@/settings/data-model/components/SettingsDataModelNewFieldBreadcrumbDropDown';
|
||||
import { FIELD_NAME_MAXIMUM_LENGTH } from '@/settings/data-model/constants/FieldNameMaximumLength';
|
||||
import { SettingsDataModelFieldDescriptionForm } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldDescriptionForm';
|
||||
import { SettingsDataModelFieldIconLabelForm } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldIconLabelForm';
|
||||
import { SettingsDataModelFieldSettingsFormCard } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldSettingsFormCard';
|
||||
import { settingsFieldFormSchema } from '@/settings/data-model/fields/forms/validation-schemas/settingsFieldFormSchema';
|
||||
@@ -67,7 +66,6 @@ export const SettingsObjectNewFieldConfigure = () => {
|
||||
icon:
|
||||
DEFAULT_ICONS_BY_FIELD_TYPE[fieldType] ?? DEFAULT_ICON_FOR_NEW_FIELD,
|
||||
label: '',
|
||||
description: '',
|
||||
name: '',
|
||||
},
|
||||
});
|
||||
@@ -237,13 +235,6 @@ export const SettingsObjectNewFieldConfigure = () => {
|
||||
objectNameSingular={activeObjectMetadataItem.nameSingular}
|
||||
/>
|
||||
</Section>
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`Description`}
|
||||
description={t`The description of this field`}
|
||||
/>
|
||||
<SettingsDataModelFieldDescriptionForm />
|
||||
</Section>
|
||||
</SettingsPageContainer>
|
||||
</SubMenuTopBarContainer>
|
||||
</FormProvider>
|
||||
|
||||
Reference in New Issue
Block a user