[Issue-5772] Add sort feature on settings tables (#5787)
## Proposed Changes - Introduce a new custom hook - useTableSort to sort table content - Add test cases for the new custom hook - Integrate useTableSort hook on to the table in settings object and settings object field pages ## Related Issue https://github.com/twentyhq/twenty/issues/5772 ## Evidence https://github.com/twentyhq/twenty/assets/87609792/8be456ce-2fa5-44ec-8bbd-70fb6c8fdb30 ## Evidence after addressing review comments https://github.com/twentyhq/twenty/assets/87609792/c267e3da-72f9-4c0e-8c94-a38122d6395e ## Further comments Apologies for the large PR. Looking forward for the review --------- Co-authored-by: Félix Malfait <felix.malfait@gmail.com> Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
committed by
GitHub
parent
0f75e14ab2
commit
59e14fabb4
@ -1,237 +0,0 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { useEffect } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { H2Title, IconPlus, IconSettings } from 'twenty-ui';
|
||||
|
||||
import { LABEL_IDENTIFIER_FIELD_METADATA_TYPES } from '@/object-metadata/constants/LabelIdentifierFieldMetadataTypes';
|
||||
import { useFieldMetadataItem } from '@/object-metadata/hooks/useFieldMetadataItem';
|
||||
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
|
||||
import { useUpdateOneObjectMetadataItem } from '@/object-metadata/hooks/useUpdateOneObjectMetadataItem';
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { getActiveFieldMetadataItems } from '@/object-metadata/utils/getActiveFieldMetadataItems';
|
||||
import { getDisabledFieldMetadataItems } from '@/object-metadata/utils/getDisabledFieldMetadataItems';
|
||||
import { getFieldSlug } from '@/object-metadata/utils/getFieldSlug';
|
||||
import { isLabelIdentifierField } from '@/object-metadata/utils/isLabelIdentifierField';
|
||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||
import { SettingsObjectFieldActiveActionDropdown } from '@/settings/data-model/object-details/components/SettingsObjectFieldActiveActionDropdown';
|
||||
import { SettingsObjectFieldInactiveActionDropdown } from '@/settings/data-model/object-details/components/SettingsObjectFieldDisabledActionDropdown';
|
||||
import {
|
||||
SettingsObjectFieldItemTableRow,
|
||||
StyledObjectFieldTableRow,
|
||||
} from '@/settings/data-model/object-details/components/SettingsObjectFieldItemTableRow';
|
||||
import { SettingsObjectSummaryCard } from '@/settings/data-model/object-details/components/SettingsObjectSummaryCard';
|
||||
import { getFieldIdentifierType } from '@/settings/data-model/utils/getFieldIdentifierType';
|
||||
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
import { Button } from '@/ui/input/button/components/Button';
|
||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
|
||||
import { Section } from '@/ui/layout/section/components/Section';
|
||||
import { Table } from '@/ui/layout/table/components/Table';
|
||||
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
|
||||
import { TableSection } from '@/ui/layout/table/components/TableSection';
|
||||
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
|
||||
import { UndecoratedLink } from '@/ui/navigation/link/components/UndecoratedLink';
|
||||
|
||||
const StyledDiv = styled.div`
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding-top: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
export const SettingsObjectDetail = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { objectSlug = '' } = useParams();
|
||||
const { findActiveObjectMetadataItemBySlug } =
|
||||
useFilteredObjectMetadataItems();
|
||||
const { updateOneObjectMetadataItem } = useUpdateOneObjectMetadataItem();
|
||||
|
||||
const activeObjectMetadataItem =
|
||||
findActiveObjectMetadataItemBySlug(objectSlug);
|
||||
|
||||
useEffect(() => {
|
||||
if (!activeObjectMetadataItem) navigate(AppPath.NotFound);
|
||||
}, [activeObjectMetadataItem, navigate]);
|
||||
|
||||
const {
|
||||
activateMetadataField,
|
||||
deactivateMetadataField,
|
||||
deleteMetadataField,
|
||||
} = useFieldMetadataItem();
|
||||
|
||||
if (!activeObjectMetadataItem) return null;
|
||||
|
||||
const activeMetadataFields = getActiveFieldMetadataItems(
|
||||
activeObjectMetadataItem,
|
||||
);
|
||||
const deactivatedMetadataFields = getDisabledFieldMetadataItems(
|
||||
activeObjectMetadataItem,
|
||||
);
|
||||
|
||||
const handleDisableObject = async () => {
|
||||
await updateOneObjectMetadataItem({
|
||||
idToUpdate: activeObjectMetadataItem.id,
|
||||
updatePayload: { isActive: false },
|
||||
});
|
||||
navigate(getSettingsPagePath(SettingsPath.Objects));
|
||||
};
|
||||
|
||||
const handleDisableField = (activeFieldMetadatItem: FieldMetadataItem) => {
|
||||
deactivateMetadataField(activeFieldMetadatItem);
|
||||
};
|
||||
|
||||
const handleSetLabelIdentifierField = (
|
||||
activeFieldMetadatItem: FieldMetadataItem,
|
||||
) =>
|
||||
updateOneObjectMetadataItem({
|
||||
idToUpdate: activeObjectMetadataItem.id,
|
||||
updatePayload: {
|
||||
labelIdentifierFieldMetadataId: activeFieldMetadatItem.id,
|
||||
},
|
||||
});
|
||||
|
||||
const shouldDisplayAddFieldButton = !activeObjectMetadataItem.isRemote;
|
||||
|
||||
return (
|
||||
<SubMenuTopBarContainer Icon={IconSettings} title="Settings">
|
||||
<SettingsPageContainer>
|
||||
<Breadcrumb
|
||||
links={[
|
||||
{ children: 'Objects', href: '/settings/objects' },
|
||||
{ children: activeObjectMetadataItem.labelPlural },
|
||||
]}
|
||||
/>
|
||||
<Section>
|
||||
<H2Title title="About" description="Manage your object" />
|
||||
<SettingsObjectSummaryCard
|
||||
iconKey={activeObjectMetadataItem.icon ?? undefined}
|
||||
name={activeObjectMetadataItem.labelPlural || ''}
|
||||
objectMetadataItem={activeObjectMetadataItem}
|
||||
onDeactivate={handleDisableObject}
|
||||
onEdit={() => navigate('./edit')}
|
||||
/>
|
||||
</Section>
|
||||
<Section>
|
||||
<H2Title
|
||||
title="Fields"
|
||||
description={`Customise the fields available in the ${activeObjectMetadataItem.labelSingular} views and their display order in the ${activeObjectMetadataItem.labelSingular} detail view and menus.`}
|
||||
/>
|
||||
<Table>
|
||||
<StyledObjectFieldTableRow>
|
||||
<TableHeader>Name</TableHeader>
|
||||
<TableHeader>
|
||||
{activeObjectMetadataItem.isCustom
|
||||
? 'Identifier'
|
||||
: 'Field type'}
|
||||
</TableHeader>
|
||||
<TableHeader>Data type</TableHeader>
|
||||
<TableHeader></TableHeader>
|
||||
</StyledObjectFieldTableRow>
|
||||
{!!activeMetadataFields.length && (
|
||||
<TableSection title="Active">
|
||||
{activeMetadataFields.map((activeMetadataField) => {
|
||||
const isLabelIdentifier = isLabelIdentifierField({
|
||||
fieldMetadataItem: activeMetadataField,
|
||||
objectMetadataItem: activeObjectMetadataItem,
|
||||
});
|
||||
const canBeSetAsLabelIdentifier =
|
||||
activeObjectMetadataItem.isCustom &&
|
||||
!isLabelIdentifier &&
|
||||
LABEL_IDENTIFIER_FIELD_METADATA_TYPES.includes(
|
||||
activeMetadataField.type,
|
||||
);
|
||||
|
||||
return (
|
||||
<SettingsObjectFieldItemTableRow
|
||||
key={activeMetadataField.id}
|
||||
identifierType={getFieldIdentifierType(
|
||||
activeMetadataField,
|
||||
activeObjectMetadataItem,
|
||||
)}
|
||||
variant={
|
||||
activeObjectMetadataItem.isCustom
|
||||
? 'identifier'
|
||||
: 'field-type'
|
||||
}
|
||||
fieldMetadataItem={activeMetadataField}
|
||||
isRemoteObjectField={activeObjectMetadataItem.isRemote}
|
||||
// to={`./${getFieldSlug(activeMetadataField)}`}
|
||||
ActionIcon={
|
||||
<SettingsObjectFieldActiveActionDropdown
|
||||
isCustomField={!!activeMetadataField.isCustom}
|
||||
scopeKey={activeMetadataField.id}
|
||||
onEdit={() =>
|
||||
navigate(`./${getFieldSlug(activeMetadataField)}`)
|
||||
}
|
||||
onSetAsLabelIdentifier={
|
||||
canBeSetAsLabelIdentifier
|
||||
? () =>
|
||||
handleSetLabelIdentifierField(
|
||||
activeMetadataField,
|
||||
)
|
||||
: undefined
|
||||
}
|
||||
onDeactivate={
|
||||
isLabelIdentifier
|
||||
? undefined
|
||||
: () => handleDisableField(activeMetadataField)
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</TableSection>
|
||||
)}
|
||||
{!!deactivatedMetadataFields.length && (
|
||||
<TableSection isInitiallyExpanded={false} title="Inactive">
|
||||
{deactivatedMetadataFields.map((deactivatedMetadataField) => (
|
||||
<SettingsObjectFieldItemTableRow
|
||||
key={deactivatedMetadataField.id}
|
||||
variant={
|
||||
activeObjectMetadataItem.isCustom
|
||||
? 'identifier'
|
||||
: 'field-type'
|
||||
}
|
||||
fieldMetadataItem={deactivatedMetadataField}
|
||||
ActionIcon={
|
||||
<SettingsObjectFieldInactiveActionDropdown
|
||||
isCustomField={!!deactivatedMetadataField.isCustom}
|
||||
scopeKey={deactivatedMetadataField.id}
|
||||
onActivate={() =>
|
||||
activateMetadataField(deactivatedMetadataField)
|
||||
}
|
||||
onDelete={() =>
|
||||
deleteMetadataField(deactivatedMetadataField)
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</TableSection>
|
||||
)}
|
||||
</Table>
|
||||
{shouldDisplayAddFieldButton && (
|
||||
<StyledDiv>
|
||||
<UndecoratedLink
|
||||
to={
|
||||
deactivatedMetadataFields.length
|
||||
? './new-field/step-1'
|
||||
: './new-field/step-2'
|
||||
}
|
||||
>
|
||||
<Button
|
||||
Icon={IconPlus}
|
||||
title="Add Field"
|
||||
size="small"
|
||||
variant="secondary"
|
||||
/>
|
||||
</UndecoratedLink>
|
||||
</StyledDiv>
|
||||
)}
|
||||
</Section>
|
||||
</SettingsPageContainer>
|
||||
</SubMenuTopBarContainer>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,30 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
|
||||
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import { isDefined } from 'twenty-ui';
|
||||
import { SettingsObjectDetailPageContent } from '~/pages/settings/data-model/SettingsObjectDetailPageContent';
|
||||
|
||||
export const SettingsObjectDetailPage = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { objectSlug = '' } = useParams();
|
||||
const { findActiveObjectMetadataItemBySlug } =
|
||||
useFilteredObjectMetadataItems();
|
||||
|
||||
const activeObjectMetadataItem =
|
||||
findActiveObjectMetadataItemBySlug(objectSlug);
|
||||
|
||||
useEffect(() => {
|
||||
if (!activeObjectMetadataItem) navigate(AppPath.NotFound);
|
||||
}, [activeObjectMetadataItem, navigate]);
|
||||
|
||||
if (!isDefined(activeObjectMetadataItem)) return <></>;
|
||||
|
||||
return (
|
||||
<SettingsObjectDetailPageContent
|
||||
objectMetadataItem={activeObjectMetadataItem}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,101 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { H2Title, IconPlus, IconSettings } from 'twenty-ui';
|
||||
|
||||
import { useUpdateOneObjectMetadataItem } from '@/object-metadata/hooks/useUpdateOneObjectMetadataItem';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||
|
||||
import { getDisabledFieldMetadataItems } from '@/object-metadata/utils/getDisabledFieldMetadataItems';
|
||||
import { SettingsObjectSummaryCard } from '@/settings/data-model/object-details/components/SettingsObjectSummaryCard';
|
||||
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
import { Button } from '@/ui/input/button/components/Button';
|
||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
|
||||
import { Section } from '@/ui/layout/section/components/Section';
|
||||
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
|
||||
import { UndecoratedLink } from '@/ui/navigation/link/components/UndecoratedLink';
|
||||
import { isNonEmptyArray } from '@sniptt/guards';
|
||||
import { SettingsObjectFieldTable } from '~/pages/settings/data-model/SettingsObjectFieldTable';
|
||||
|
||||
const StyledDiv = styled.div`
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding-top: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
export type SettingsObjectDetailPageContentProps = {
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
};
|
||||
|
||||
export const SettingsObjectDetailPageContent = ({
|
||||
objectMetadataItem,
|
||||
}: SettingsObjectDetailPageContentProps) => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { updateOneObjectMetadataItem } = useUpdateOneObjectMetadataItem();
|
||||
|
||||
const handleDisableObject = async () => {
|
||||
await updateOneObjectMetadataItem({
|
||||
idToUpdate: objectMetadataItem.id,
|
||||
updatePayload: { isActive: false },
|
||||
});
|
||||
navigate(getSettingsPagePath(SettingsPath.Objects));
|
||||
};
|
||||
|
||||
const disabledFieldMetadataItems =
|
||||
getDisabledFieldMetadataItems(objectMetadataItem);
|
||||
|
||||
const shouldDisplayAddFieldButton = !objectMetadataItem.isRemote;
|
||||
|
||||
return (
|
||||
<SubMenuTopBarContainer Icon={IconSettings} title="Settings">
|
||||
<SettingsPageContainer>
|
||||
<Breadcrumb
|
||||
links={[
|
||||
{ children: 'Objects', href: '/settings/objects' },
|
||||
{ children: objectMetadataItem.labelPlural },
|
||||
]}
|
||||
/>
|
||||
<Section>
|
||||
<H2Title title="About" description="Manage your object" />
|
||||
<SettingsObjectSummaryCard
|
||||
iconKey={objectMetadataItem.icon ?? undefined}
|
||||
name={objectMetadataItem.labelPlural || ''}
|
||||
objectMetadataItem={objectMetadataItem}
|
||||
onDeactivate={handleDisableObject}
|
||||
onEdit={() => navigate('./edit')}
|
||||
/>
|
||||
</Section>
|
||||
<Section>
|
||||
<H2Title
|
||||
title="Fields"
|
||||
description={`Customise the fields available in the ${objectMetadataItem.labelSingular} views and their display order in the ${objectMetadataItem.labelSingular} detail view and menus.`}
|
||||
/>
|
||||
<SettingsObjectFieldTable
|
||||
objectMetadataItem={objectMetadataItem}
|
||||
mode="view"
|
||||
/>
|
||||
{shouldDisplayAddFieldButton && (
|
||||
<StyledDiv>
|
||||
<UndecoratedLink
|
||||
to={
|
||||
isNonEmptyArray(disabledFieldMetadataItems)
|
||||
? './new-field/step-1'
|
||||
: './new-field/step-2'
|
||||
}
|
||||
>
|
||||
<Button
|
||||
Icon={IconPlus}
|
||||
title="Add Field"
|
||||
size="small"
|
||||
variant="secondary"
|
||||
/>
|
||||
</UndecoratedLink>
|
||||
</StyledDiv>
|
||||
)}
|
||||
</Section>
|
||||
</SettingsPageContainer>
|
||||
</SubMenuTopBarContainer>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,194 @@
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import {
|
||||
SettingsObjectFieldItemTableRow,
|
||||
StyledObjectFieldTableRow,
|
||||
} from '@/settings/data-model/object-details/components/SettingsObjectFieldItemTableRow';
|
||||
import { settingsObjectFieldsFamilyState } from '@/settings/data-model/object-details/states/settingsObjectFieldsFamilyState';
|
||||
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 { TableSection } from '@/ui/layout/table/components/TableSection';
|
||||
import { useSortedArray } from '@/ui/layout/table/hooks/useSortedArray';
|
||||
import { TableMetadata } from '@/ui/layout/table/types/TableMetadata';
|
||||
import { isNonEmptyArray } from '@sniptt/guards';
|
||||
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { useMapFieldMetadataItemToSettingsObjectDetailTableItem } from '~/pages/settings/data-model/hooks/useMapFieldMetadataItemToSettingsObjectDetailTableItem';
|
||||
import { SettingsObjectDetailTableItem } from '~/pages/settings/data-model/types/SettingsObjectDetailTableItem';
|
||||
|
||||
const SETTINGS_OBJECT_DETAIL_TABLE_METADATA_STANDARD: TableMetadata<SettingsObjectDetailTableItem> =
|
||||
{
|
||||
tableId: 'settingsObjectDetail',
|
||||
fields: [
|
||||
{
|
||||
fieldLabel: 'Name',
|
||||
fieldName: 'label',
|
||||
fieldType: 'string',
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
fieldLabel: 'Field type',
|
||||
fieldName: 'fieldType',
|
||||
fieldType: 'string',
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
fieldLabel: 'Data type',
|
||||
fieldName: 'dataType',
|
||||
fieldType: 'string',
|
||||
align: 'left',
|
||||
},
|
||||
],
|
||||
initialSort: {
|
||||
fieldName: 'label',
|
||||
orderBy: 'AscNullsLast',
|
||||
},
|
||||
};
|
||||
|
||||
const SETTINGS_OBJECT_DETAIL_TABLE_METADATA_CUSTOM: TableMetadata<SettingsObjectDetailTableItem> =
|
||||
{
|
||||
tableId: 'settingsObjectDetail',
|
||||
fields: [
|
||||
{
|
||||
fieldLabel: 'Name',
|
||||
fieldName: 'label',
|
||||
fieldType: 'string',
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
fieldLabel: 'Identifier',
|
||||
fieldName: 'identifierType',
|
||||
fieldType: 'string',
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
fieldLabel: 'Data type',
|
||||
fieldName: 'dataType',
|
||||
fieldType: 'string',
|
||||
align: 'left',
|
||||
},
|
||||
],
|
||||
initialSort: {
|
||||
fieldName: 'label',
|
||||
orderBy: 'AscNullsLast',
|
||||
},
|
||||
};
|
||||
|
||||
export type SettingsObjectFieldTableProps = {
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
mode: 'view' | 'new-field';
|
||||
};
|
||||
|
||||
// TODO: find another way than using mode which feels like it could be replaced by another pattern
|
||||
export const SettingsObjectFieldTable = ({
|
||||
objectMetadataItem,
|
||||
mode,
|
||||
}: SettingsObjectFieldTableProps) => {
|
||||
const tableMetadata = objectMetadataItem.isCustom
|
||||
? SETTINGS_OBJECT_DETAIL_TABLE_METADATA_CUSTOM
|
||||
: SETTINGS_OBJECT_DETAIL_TABLE_METADATA_STANDARD;
|
||||
|
||||
const { mapFieldMetadataItemToSettingsObjectDetailTableItem } =
|
||||
useMapFieldMetadataItemToSettingsObjectDetailTableItem(objectMetadataItem);
|
||||
|
||||
const [settingsObjectFields, setSettingsObjectFields] = useRecoilState(
|
||||
settingsObjectFieldsFamilyState({
|
||||
objectMetadataItemId: objectMetadataItem.id,
|
||||
}),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setSettingsObjectFields(objectMetadataItem.fields);
|
||||
}, [objectMetadataItem, setSettingsObjectFields]);
|
||||
|
||||
const activeObjectSettingsDetailItems = useMemo(() => {
|
||||
const activeMetadataFields = settingsObjectFields?.filter(
|
||||
(fieldMetadataItem) =>
|
||||
fieldMetadataItem.isActive && !fieldMetadataItem.isSystem,
|
||||
);
|
||||
|
||||
return (
|
||||
activeMetadataFields?.map(
|
||||
mapFieldMetadataItemToSettingsObjectDetailTableItem,
|
||||
) ?? []
|
||||
);
|
||||
}, [
|
||||
settingsObjectFields,
|
||||
mapFieldMetadataItemToSettingsObjectDetailTableItem,
|
||||
]);
|
||||
|
||||
const disabledObjectSettingsDetailItems = useMemo(() => {
|
||||
const disabledFieldMetadataItems = settingsObjectFields?.filter(
|
||||
(fieldMetadataItem) =>
|
||||
!fieldMetadataItem.isActive && !fieldMetadataItem.isSystem,
|
||||
);
|
||||
|
||||
return (
|
||||
disabledFieldMetadataItems?.map(
|
||||
mapFieldMetadataItemToSettingsObjectDetailTableItem,
|
||||
) ?? []
|
||||
);
|
||||
}, [
|
||||
settingsObjectFields,
|
||||
mapFieldMetadataItemToSettingsObjectDetailTableItem,
|
||||
]);
|
||||
|
||||
const sortedActiveObjectSettingsDetailItems = useSortedArray(
|
||||
activeObjectSettingsDetailItems,
|
||||
tableMetadata,
|
||||
);
|
||||
|
||||
const sortedDisabledObjectSettingsDetailItems = useSortedArray(
|
||||
disabledObjectSettingsDetailItems,
|
||||
tableMetadata,
|
||||
);
|
||||
|
||||
return (
|
||||
<Table>
|
||||
<StyledObjectFieldTableRow>
|
||||
{tableMetadata.fields.map((item) => (
|
||||
<SortableTableHeader
|
||||
key={item.fieldName}
|
||||
fieldName={item.fieldName}
|
||||
label={item.fieldLabel}
|
||||
tableId={tableMetadata.tableId}
|
||||
initialSort={tableMetadata.initialSort}
|
||||
/>
|
||||
))}
|
||||
<TableHeader></TableHeader>
|
||||
</StyledObjectFieldTableRow>
|
||||
{isNonEmptyArray(sortedActiveObjectSettingsDetailItems) && (
|
||||
<TableSection title="Active">
|
||||
{sortedActiveObjectSettingsDetailItems.map(
|
||||
(objectSettingsDetailItem) => (
|
||||
<SettingsObjectFieldItemTableRow
|
||||
key={objectSettingsDetailItem.fieldMetadataItem.id}
|
||||
settingsObjectDetailTableItem={objectSettingsDetailItem}
|
||||
status="active"
|
||||
mode={mode}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
</TableSection>
|
||||
)}
|
||||
{isNonEmptyArray(sortedDisabledObjectSettingsDetailItems) && (
|
||||
<TableSection
|
||||
isInitiallyExpanded={mode === 'new-field' ? true : false}
|
||||
title="Inactive"
|
||||
>
|
||||
{sortedDisabledObjectSettingsDetailItems.map(
|
||||
(objectSettingsDetailItem) => (
|
||||
<SettingsObjectFieldItemTableRow
|
||||
key={objectSettingsDetailItem.fieldMetadataItem.id}
|
||||
settingsObjectDetailTableItem={objectSettingsDetailItem}
|
||||
status="disabled"
|
||||
mode={mode}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
</TableSection>
|
||||
)}
|
||||
</Table>
|
||||
);
|
||||
};
|
||||
@ -1,27 +1,22 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import styled from '@emotion/styled';
|
||||
import { H2Title, IconMinus, IconPlus, IconSettings } from 'twenty-ui';
|
||||
import { useEffect } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { H2Title, IconPlus, IconSettings } from 'twenty-ui';
|
||||
|
||||
import { useFieldMetadataItem } from '@/object-metadata/hooks/useFieldMetadataItem';
|
||||
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
|
||||
import { isLabelIdentifierField } from '@/object-metadata/utils/isLabelIdentifierField';
|
||||
import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons';
|
||||
import { SettingsHeaderContainer } from '@/settings/components/SettingsHeaderContainer';
|
||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||
import {
|
||||
SettingsObjectFieldItemTableRow,
|
||||
StyledObjectFieldTableRow,
|
||||
} from '@/settings/data-model/object-details/components/SettingsObjectFieldItemTableRow';
|
||||
|
||||
import { useFieldMetadataItem } from '@/object-metadata/hooks/useFieldMetadataItem';
|
||||
import { settingsObjectFieldsFamilyState } from '@/settings/data-model/object-details/states/settingsObjectFieldsFamilyState';
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import { Button } from '@/ui/input/button/components/Button';
|
||||
import { LightIconButton } from '@/ui/input/button/components/LightIconButton';
|
||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
|
||||
import { Section } from '@/ui/layout/section/components/Section';
|
||||
import { Table } from '@/ui/layout/table/components/Table';
|
||||
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
|
||||
import { TableSection } from '@/ui/layout/table/components/TableSection';
|
||||
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { SettingsObjectFieldTable } from '~/pages/settings/data-model/SettingsObjectFieldTable';
|
||||
|
||||
const StyledSection = styled(Section)`
|
||||
display: flex;
|
||||
@ -43,62 +38,52 @@ export const SettingsObjectNewFieldStep1 = () => {
|
||||
const activeObjectMetadataItem =
|
||||
findActiveObjectMetadataItemBySlug(objectSlug);
|
||||
|
||||
const [settingsObjectFields] = useRecoilState(
|
||||
settingsObjectFieldsFamilyState({
|
||||
objectMetadataItemId: activeObjectMetadataItem?.id,
|
||||
}),
|
||||
);
|
||||
|
||||
const { activateMetadataField, deactivateMetadataField } =
|
||||
useFieldMetadataItem();
|
||||
const [metadataFields, setMetadataFields] = useState(
|
||||
activeObjectMetadataItem?.fields ?? [],
|
||||
);
|
||||
|
||||
const activeMetadataFields = metadataFields.filter((field) => field.isActive);
|
||||
const deactivatedMetadataFields = metadataFields.filter(
|
||||
(field) => !field.isActive,
|
||||
);
|
||||
|
||||
const canSave = metadataFields.some(
|
||||
const canSave = settingsObjectFields?.some(
|
||||
(field, index) =>
|
||||
field.isActive !== activeObjectMetadataItem?.fields[index].isActive,
|
||||
);
|
||||
|
||||
const handleSave = async () => {
|
||||
if (!activeObjectMetadataItem || !settingsObjectFields) {
|
||||
return;
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
settingsObjectFields.map((fieldMetadataItem, index) => {
|
||||
if (
|
||||
fieldMetadataItem.isActive ===
|
||||
activeObjectMetadataItem.fields[index].isActive
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return fieldMetadataItem.isActive
|
||||
? activateMetadataField(fieldMetadataItem)
|
||||
: deactivateMetadataField(fieldMetadataItem);
|
||||
}),
|
||||
);
|
||||
|
||||
navigate(`/settings/objects/${objectSlug}`);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!activeObjectMetadataItem) {
|
||||
navigate(AppPath.NotFound);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!metadataFields.length)
|
||||
setMetadataFields(activeObjectMetadataItem.fields);
|
||||
}, [activeObjectMetadataItem, metadataFields.length, navigate]);
|
||||
}, [activeObjectMetadataItem, navigate]);
|
||||
|
||||
if (!activeObjectMetadataItem) return null;
|
||||
|
||||
const handleToggleField = (fieldMetadataId: string) =>
|
||||
setMetadataFields((previousFields) =>
|
||||
previousFields.map((field) =>
|
||||
field.id === fieldMetadataId
|
||||
? { ...field, isActive: !field.isActive }
|
||||
: field,
|
||||
),
|
||||
);
|
||||
|
||||
const handleSave = async () => {
|
||||
await Promise.all(
|
||||
metadataFields.map((metadataField, index) => {
|
||||
if (
|
||||
metadataField.isActive ===
|
||||
activeObjectMetadataItem.fields[index].isActive
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return metadataField.isActive
|
||||
? activateMetadataField(metadataField)
|
||||
: deactivateMetadataField(metadataField);
|
||||
}),
|
||||
);
|
||||
|
||||
navigate(`/settings/objects/${objectSlug}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<SubMenuTopBarContainer Icon={IconSettings} title="Settings">
|
||||
<SettingsPageContainer>
|
||||
@ -126,58 +111,10 @@ export const SettingsObjectNewFieldStep1 = () => {
|
||||
title="Check deactivated fields"
|
||||
description="Before creating a custom field, check if it already exists in the deactivated section."
|
||||
/>
|
||||
<Table>
|
||||
<StyledObjectFieldTableRow>
|
||||
<TableHeader>Name</TableHeader>
|
||||
<TableHeader>Field type</TableHeader>
|
||||
<TableHeader>Data type</TableHeader>
|
||||
<TableHeader></TableHeader>
|
||||
</StyledObjectFieldTableRow>
|
||||
{!!activeMetadataFields.length && (
|
||||
<TableSection isInitiallyExpanded={false} title="Active">
|
||||
{activeMetadataFields.map((activeMetadataField) => (
|
||||
<SettingsObjectFieldItemTableRow
|
||||
key={activeMetadataField.id}
|
||||
fieldMetadataItem={activeMetadataField}
|
||||
isRemoteObjectField={activeObjectMetadataItem.isRemote}
|
||||
ActionIcon={
|
||||
isLabelIdentifierField({
|
||||
fieldMetadataItem: activeMetadataField,
|
||||
objectMetadataItem: activeObjectMetadataItem,
|
||||
}) ? undefined : (
|
||||
<LightIconButton
|
||||
Icon={IconMinus}
|
||||
accent="tertiary"
|
||||
onClick={() =>
|
||||
handleToggleField(activeMetadataField.id)
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</TableSection>
|
||||
)}
|
||||
{!!deactivatedMetadataFields.length && (
|
||||
<TableSection title="Disabled">
|
||||
{deactivatedMetadataFields.map((deactivatedMetadataField) => (
|
||||
<SettingsObjectFieldItemTableRow
|
||||
key={deactivatedMetadataField.name}
|
||||
fieldMetadataItem={deactivatedMetadataField}
|
||||
ActionIcon={
|
||||
<LightIconButton
|
||||
Icon={IconPlus}
|
||||
accent="tertiary"
|
||||
onClick={() =>
|
||||
handleToggleField(deactivatedMetadataField.id)
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</TableSection>
|
||||
)}
|
||||
</Table>
|
||||
<SettingsObjectFieldTable
|
||||
objectMetadataItem={activeObjectMetadataItem}
|
||||
mode="new-field"
|
||||
/>
|
||||
<StyledAddCustomFieldButton
|
||||
Icon={IconPlus}
|
||||
title="Add Custom Field"
|
||||
|
||||
@ -12,23 +12,31 @@ import { useDeleteOneObjectMetadataItem } from '@/object-metadata/hooks/useDelet
|
||||
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
|
||||
import { useUpdateOneObjectMetadataItem } from '@/object-metadata/hooks/useUpdateOneObjectMetadataItem';
|
||||
import { getObjectSlug } from '@/object-metadata/utils/getObjectSlug';
|
||||
import { useCombinedGetTotalCount } from '@/object-record/multiple-objects/hooks/useCombinedGetTotalCount';
|
||||
import { SettingsHeaderContainer } from '@/settings/components/SettingsHeaderContainer';
|
||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||
import {
|
||||
SettingsObjectItemTableRow,
|
||||
SettingsObjectMetadataItemTableRow,
|
||||
StyledObjectTableRow,
|
||||
} from '@/settings/data-model/object-details/components/SettingsObjectItemTableRow';
|
||||
import { SettingsObjectCoverImage } from '@/settings/data-model/objects/SettingsObjectCoverImage';
|
||||
import { SettingsObjectInactiveMenuDropDown } from '@/settings/data-model/objects/SettingsObjectInactiveMenuDropDown';
|
||||
import { getObjectTypeLabel } from '@/settings/data-model/utils/getObjectTypeLabel';
|
||||
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
import { Button } from '@/ui/input/button/components/Button';
|
||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
|
||||
import { Section } from '@/ui/layout/section/components/Section';
|
||||
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 { TableSection } from '@/ui/layout/table/components/TableSection';
|
||||
import { useSortedArray } from '@/ui/layout/table/hooks/useSortedArray';
|
||||
import { UndecoratedLink } from '@/ui/navigation/link/components/UndecoratedLink';
|
||||
import { isNonEmptyArray } from '@sniptt/guards';
|
||||
import { useMemo } from 'react';
|
||||
import { SETTINGS_OBJECT_TABLE_METADATA } from '~/pages/settings/data-model/constants/SettingsObjectTableMetadata';
|
||||
import { SettingsObjectTableItem } from '~/pages/settings/data-model/types/SettingsObjectTableItem';
|
||||
|
||||
const StyledIconChevronRight = styled(IconChevronRight)`
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
@ -41,11 +49,71 @@ const StyledH1Title = styled(H1Title)`
|
||||
export const SettingsObjects = () => {
|
||||
const theme = useTheme();
|
||||
|
||||
const { activeObjectMetadataItems, inactiveObjectMetadataItems } =
|
||||
useFilteredObjectMetadataItems();
|
||||
const { deleteOneObjectMetadataItem } = useDeleteOneObjectMetadataItem();
|
||||
const { updateOneObjectMetadataItem } = useUpdateOneObjectMetadataItem();
|
||||
|
||||
const { activeObjectMetadataItems, inactiveObjectMetadataItems } =
|
||||
useFilteredObjectMetadataItems();
|
||||
|
||||
const { totalCountByObjectMetadataItemNamePlural } = useCombinedGetTotalCount(
|
||||
{
|
||||
objectMetadataItems: [
|
||||
...activeObjectMetadataItems,
|
||||
...inactiveObjectMetadataItems,
|
||||
],
|
||||
},
|
||||
);
|
||||
|
||||
const activeObjectSettingsArray = useMemo(
|
||||
() =>
|
||||
activeObjectMetadataItems.map(
|
||||
(objectMetadataItem) =>
|
||||
({
|
||||
objectMetadataItem,
|
||||
labelPlural: objectMetadataItem.labelPlural,
|
||||
objectTypeLabel: getObjectTypeLabel(objectMetadataItem).labelText,
|
||||
fieldsCount: objectMetadataItem.fields.filter(
|
||||
(field) => !field.isSystem,
|
||||
).length,
|
||||
totalObjectCount:
|
||||
totalCountByObjectMetadataItemNamePlural[
|
||||
objectMetadataItem.namePlural
|
||||
] ?? 0,
|
||||
}) satisfies SettingsObjectTableItem,
|
||||
),
|
||||
[activeObjectMetadataItems, totalCountByObjectMetadataItemNamePlural],
|
||||
);
|
||||
|
||||
const inactiveObjectSettingsArray = useMemo(
|
||||
() =>
|
||||
inactiveObjectMetadataItems.map(
|
||||
(objectMetadataItem) =>
|
||||
({
|
||||
objectMetadataItem,
|
||||
labelPlural: objectMetadataItem.labelPlural,
|
||||
objectTypeLabel: getObjectTypeLabel(objectMetadataItem).labelText,
|
||||
fieldsCount: objectMetadataItem.fields.filter(
|
||||
(field) => !field.isSystem,
|
||||
).length,
|
||||
totalObjectCount:
|
||||
totalCountByObjectMetadataItemNamePlural[
|
||||
objectMetadataItem.namePlural
|
||||
] ?? 0,
|
||||
}) satisfies SettingsObjectTableItem,
|
||||
),
|
||||
[inactiveObjectMetadataItems, totalCountByObjectMetadataItemNamePlural],
|
||||
);
|
||||
|
||||
const sortedActiveObjectSettingsItems = useSortedArray(
|
||||
activeObjectSettingsArray,
|
||||
SETTINGS_OBJECT_TABLE_METADATA,
|
||||
);
|
||||
|
||||
const sortedInactiveObjectSettingsItems = useSortedArray(
|
||||
inactiveObjectSettingsArray,
|
||||
SETTINGS_OBJECT_TABLE_METADATA,
|
||||
);
|
||||
|
||||
return (
|
||||
<SubMenuTopBarContainer Icon={IconSettings} title="Settings">
|
||||
<SettingsPageContainer>
|
||||
@ -66,51 +134,67 @@ export const SettingsObjects = () => {
|
||||
<H2Title title="Existing objects" />
|
||||
<Table>
|
||||
<StyledObjectTableRow>
|
||||
<TableHeader>Name</TableHeader>
|
||||
<TableHeader>Type</TableHeader>
|
||||
<TableHeader align="right">Fields</TableHeader>
|
||||
<TableHeader align="right">Instances</TableHeader>
|
||||
{SETTINGS_OBJECT_TABLE_METADATA.fields.map(
|
||||
(settingsObjectsTableMetadataField) => (
|
||||
<SortableTableHeader
|
||||
fieldName={settingsObjectsTableMetadataField.fieldName}
|
||||
label={settingsObjectsTableMetadataField.fieldLabel}
|
||||
tableId={SETTINGS_OBJECT_TABLE_METADATA.tableId}
|
||||
align={settingsObjectsTableMetadataField.align}
|
||||
initialSort={SETTINGS_OBJECT_TABLE_METADATA.initialSort}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
<TableHeader></TableHeader>
|
||||
</StyledObjectTableRow>
|
||||
{!!activeObjectMetadataItems.length && (
|
||||
{isNonEmptyArray(sortedActiveObjectSettingsItems) && (
|
||||
<TableSection title="Active">
|
||||
{activeObjectMetadataItems.map((activeObjectMetadataItem) => (
|
||||
<SettingsObjectItemTableRow
|
||||
key={activeObjectMetadataItem.namePlural}
|
||||
objectItem={activeObjectMetadataItem}
|
||||
{sortedActiveObjectSettingsItems.map((objectSettingsItem) => (
|
||||
<SettingsObjectMetadataItemTableRow
|
||||
key={objectSettingsItem.objectMetadataItem.namePlural}
|
||||
objectMetadataItem={objectSettingsItem.objectMetadataItem}
|
||||
totalObjectCount={objectSettingsItem.totalObjectCount}
|
||||
action={
|
||||
<StyledIconChevronRight
|
||||
size={theme.icon.size.md}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
/>
|
||||
}
|
||||
to={`/settings/objects/${getObjectSlug(
|
||||
activeObjectMetadataItem,
|
||||
link={`/settings/objects/${getObjectSlug(
|
||||
objectSettingsItem.objectMetadataItem,
|
||||
)}`}
|
||||
/>
|
||||
))}
|
||||
</TableSection>
|
||||
)}
|
||||
{!!inactiveObjectMetadataItems.length && (
|
||||
{isNonEmptyArray(inactiveObjectMetadataItems) && (
|
||||
<TableSection title="Inactive">
|
||||
{inactiveObjectMetadataItems.map(
|
||||
(inactiveObjectMetadataItem) => (
|
||||
<SettingsObjectItemTableRow
|
||||
key={inactiveObjectMetadataItem.namePlural}
|
||||
objectItem={inactiveObjectMetadataItem}
|
||||
{sortedInactiveObjectSettingsItems.map(
|
||||
(objectSettingsItem) => (
|
||||
<SettingsObjectMetadataItemTableRow
|
||||
key={objectSettingsItem.objectMetadataItem.namePlural}
|
||||
objectMetadataItem={
|
||||
objectSettingsItem.objectMetadataItem
|
||||
}
|
||||
totalObjectCount={objectSettingsItem.totalObjectCount}
|
||||
action={
|
||||
<SettingsObjectInactiveMenuDropDown
|
||||
isCustomObject={inactiveObjectMetadataItem.isCustom}
|
||||
scopeKey={inactiveObjectMetadataItem.namePlural}
|
||||
isCustomObject={
|
||||
objectSettingsItem.objectMetadataItem.isCustom
|
||||
}
|
||||
scopeKey={
|
||||
objectSettingsItem.objectMetadataItem.namePlural
|
||||
}
|
||||
onActivate={() =>
|
||||
updateOneObjectMetadataItem({
|
||||
idToUpdate: inactiveObjectMetadataItem.id,
|
||||
idToUpdate:
|
||||
objectSettingsItem.objectMetadataItem.id,
|
||||
updatePayload: { isActive: true },
|
||||
})
|
||||
}
|
||||
onDelete={() =>
|
||||
deleteOneObjectMetadataItem(
|
||||
inactiveObjectMetadataItem.id,
|
||||
objectSettingsItem.objectMetadataItem.id,
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
@ -8,11 +8,11 @@ import {
|
||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
import { sleep } from '~/utils/sleep';
|
||||
|
||||
import { SettingsObjectDetail } from '../SettingsObjectDetail';
|
||||
import { SettingsObjectDetailPage } from '../SettingsObjectDetailPage';
|
||||
|
||||
const meta: Meta<PageDecoratorArgs> = {
|
||||
title: 'Pages/Settings/DataModel/SettingsObjectDetail',
|
||||
component: SettingsObjectDetail,
|
||||
component: SettingsObjectDetailPage,
|
||||
decorators: [PageDecorator],
|
||||
args: {
|
||||
routePath: '/settings/objects/:objectSlug',
|
||||
@ -25,7 +25,7 @@ const meta: Meta<PageDecoratorArgs> = {
|
||||
|
||||
export default meta;
|
||||
|
||||
export type Story = StoryObj<typeof SettingsObjectDetail>;
|
||||
export type Story = StoryObj<typeof SettingsObjectDetailPage>;
|
||||
|
||||
export const StandardObject: Story = {
|
||||
play: async () => {
|
||||
|
||||
@ -0,0 +1,37 @@
|
||||
import { TableMetadata } from '@/ui/layout/table/types/TableMetadata';
|
||||
import { SettingsObjectTableItem } from '~/pages/settings/data-model/types/SettingsObjectTableItem';
|
||||
|
||||
export const SETTINGS_OBJECT_TABLE_METADATA: TableMetadata<SettingsObjectTableItem> =
|
||||
{
|
||||
tableId: 'settingsObject',
|
||||
fields: [
|
||||
{
|
||||
fieldLabel: 'Name',
|
||||
fieldName: 'labelPlural',
|
||||
fieldType: 'string',
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
fieldLabel: 'Type',
|
||||
fieldName: 'objectTypeLabel',
|
||||
fieldType: 'string',
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
fieldLabel: 'Fields',
|
||||
fieldName: 'fieldsCount',
|
||||
fieldType: 'number',
|
||||
align: 'right',
|
||||
},
|
||||
{
|
||||
fieldLabel: 'Instances',
|
||||
fieldName: 'totalObjectCount',
|
||||
fieldType: 'number',
|
||||
align: 'right',
|
||||
},
|
||||
],
|
||||
initialSort: {
|
||||
fieldName: 'labelPlural',
|
||||
orderBy: 'AscNullsLast',
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,46 @@
|
||||
import { useGetRelationMetadata } from '@/object-metadata/hooks/useGetRelationMetadata';
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { getFieldIdentifierType } from '@/settings/data-model/utils/getFieldIdentifierType';
|
||||
import { getSettingsFieldTypeConfig } from '@/settings/data-model/utils/getSettingsFieldTypeConfig';
|
||||
import { SettingsObjectDetailTableItem } from '~/pages/settings/data-model/types/SettingsObjectDetailTableItem';
|
||||
import { getSettingsObjectFieldType } from '~/pages/settings/data-model/utils/getSettingsObjectFieldType';
|
||||
|
||||
export const useMapFieldMetadataItemToSettingsObjectDetailTableItem = (
|
||||
objectMetadataItem: ObjectMetadataItem,
|
||||
) => {
|
||||
const getRelationMetadata = useGetRelationMetadata();
|
||||
|
||||
const mapFieldMetadataItemToSettingsObjectDetailTableItem = (
|
||||
fieldMetadataItem: FieldMetadataItem,
|
||||
): SettingsObjectDetailTableItem => {
|
||||
const fieldType = getSettingsObjectFieldType(
|
||||
objectMetadataItem,
|
||||
fieldMetadataItem,
|
||||
);
|
||||
|
||||
const { relationObjectMetadataItem } =
|
||||
getRelationMetadata({
|
||||
fieldMetadataItem,
|
||||
}) ?? {};
|
||||
|
||||
const identifierType = getFieldIdentifierType(
|
||||
fieldMetadataItem,
|
||||
objectMetadataItem,
|
||||
);
|
||||
|
||||
return {
|
||||
fieldMetadataItem,
|
||||
fieldType: fieldType ?? '',
|
||||
dataType:
|
||||
relationObjectMetadataItem?.labelPlural ??
|
||||
getSettingsFieldTypeConfig(fieldMetadataItem.type)?.label ??
|
||||
'',
|
||||
label: fieldMetadataItem.label,
|
||||
identifierType: identifierType,
|
||||
objectMetadataItem,
|
||||
} satisfies SettingsObjectDetailTableItem;
|
||||
};
|
||||
|
||||
return { mapFieldMetadataItemToSettingsObjectDetailTableItem };
|
||||
};
|
||||
@ -0,0 +1,12 @@
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { FieldIdentifierType } from '@/settings/data-model/types/FieldIdentifierType';
|
||||
|
||||
export type SettingsObjectDetailTableItem = {
|
||||
fieldMetadataItem: FieldMetadataItem;
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
fieldType: string | boolean;
|
||||
label: string;
|
||||
dataType: string;
|
||||
identifierType?: FieldIdentifierType;
|
||||
};
|
||||
@ -0,0 +1,9 @@
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
|
||||
export type SettingsObjectTableItem = {
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
totalObjectCount: number;
|
||||
fieldsCount: number;
|
||||
objectTypeLabel: string;
|
||||
labelPlural: string;
|
||||
};
|
||||
@ -0,0 +1,30 @@
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { getFieldIdentifierType } from '@/settings/data-model/utils/getFieldIdentifierType';
|
||||
import { isDefined } from 'twenty-ui';
|
||||
|
||||
export const getSettingsObjectFieldType = (
|
||||
objectMetadataItem: ObjectMetadataItem,
|
||||
fieldMetadataItem: FieldMetadataItem,
|
||||
) => {
|
||||
const variant = objectMetadataItem.isCustom ? 'identifier' : 'field-type';
|
||||
|
||||
const identifierType = getFieldIdentifierType(
|
||||
fieldMetadataItem,
|
||||
objectMetadataItem,
|
||||
);
|
||||
|
||||
if (variant === 'field-type') {
|
||||
return objectMetadataItem.isRemote
|
||||
? 'Remote'
|
||||
: fieldMetadataItem.isCustom
|
||||
? 'Custom'
|
||||
: 'Standard';
|
||||
} else {
|
||||
return isDefined(identifierType)
|
||||
? identifierType === 'label'
|
||||
? 'Record text'
|
||||
: 'Record image'
|
||||
: null;
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user