Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added new view to select types for objects #6700

Merged
merged 18 commits into from
Sep 6, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { SettingsFieldTypeCategoryType } from '@/settings/data-model/types/SettingsFieldTypeCategoryType';

export const SETTINGS_FIELD_TYPE_CATEGORIES: SettingsFieldTypeCategoryType[] = [
'Basic',
'Relation',
'Advanced',
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { SettingsFieldTypeCategoryType } from '@/settings/data-model/types/SettingsFieldTypeCategoryType';

export const SETTINGS_FIELD_TYPE_CATEGORY_DESCRIPTIONS: Record<
SettingsFieldTypeCategoryType,
string
> = {
Comment on lines +3 to +6
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Consider using const instead of export const for better tree-shaking

Basic: 'All the basic field types you need to start',
Advanced: 'More advanced fields for advanced projects',
Relation: 'Custom relationships and predefined relationships',
};
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {

import { CurrencyCode } from '@/object-record/record-field/types/CurrencyCode';
import { DEFAULT_DATE_VALUE } from '@/settings/data-model/constants/DefaultDateValue';
import { SettingsFieldTypeCategoryType } from '@/settings/data-model/types/SettingsFieldTypeCategoryType';
import { SettingsSupportedFieldType } from '@/settings/data-model/types/SettingsSupportedFieldType';
import { FieldMetadataType } from '~/generated-metadata/graphql';

Expand All @@ -32,87 +33,108 @@ export type SettingsFieldTypeConfig = {
label: string;
Icon: IconComponent;
exampleValue?: unknown;
category: SettingsFieldTypeCategoryType;
};

export const SETTINGS_FIELD_TYPE_CONFIGS = {
[FieldMetadataType.Uuid]: {
label: 'Unique ID',
Icon: IconKey,
exampleValue: '00000000-0000-0000-0000-000000000000',
category: 'Advanced',
},
[FieldMetadataType.Text]: {
label: 'Text',
Icon: IconTextSize,
exampleValue:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum magna enim, dapibus non enim in, lacinia faucibus nunc. Sed interdum ante sed felis facilisis, eget ultricies neque molestie. Mauris auctor, justo eu volutpat cursus, libero erat tempus nulla, non sodales lorem lacus a est.',
category: 'Basic',
},
[FieldMetadataType.Numeric]: {
label: 'Numeric',
Icon: IconNumbers,
exampleValue: 2000,
category: 'Basic',
},
[FieldMetadataType.Number]: {
label: 'Number',
Icon: IconNumbers,
exampleValue: 2000,
category: 'Basic',
},
[FieldMetadataType.Link]: {
label: 'Link',
Icon: IconLink,
exampleValue: { url: 'www.twenty.com', label: '' },
category: 'Basic',
},
[FieldMetadataType.Links]: {
label: 'Links',
Icon: IconLink,
exampleValue: { primaryLinkUrl: 'twenty.com', primaryLinkLabel: '' },
category: 'Basic',
},
[FieldMetadataType.Boolean]: {
label: 'True/False',
Icon: IconCheck,
exampleValue: true,
category: 'Basic',
},
[FieldMetadataType.DateTime]: {
label: 'Date and Time',
Icon: IconCalendarTime,
exampleValue: DEFAULT_DATE_VALUE.toISOString(),
category: 'Basic',
},
[FieldMetadataType.Date]: {
label: 'Date',
Icon: IconCalendarEvent,
exampleValue: DEFAULT_DATE_VALUE.toISOString(),
category: 'Basic',
},
[FieldMetadataType.Select]: {
label: 'Select',
Icon: IconTag,
category: 'Basic',
},
[FieldMetadataType.MultiSelect]: {
label: 'Multi-select',
Icon: IconTags,
category: 'Basic',
},
[FieldMetadataType.Currency]: {
label: 'Currency',
Icon: IconCoins,
exampleValue: { amountMicros: 2000000000, currencyCode: CurrencyCode.USD },
category: 'Basic',
},
[FieldMetadataType.Relation]: {
label: 'Relation',
Icon: IconRelationManyToMany,
category: 'Relation',
},
[FieldMetadataType.Email]: {
label: 'Email',
Icon: IconMail,
category: 'Basic',
},
[FieldMetadataType.Email]: { label: 'Email', Icon: IconMail },
[FieldMetadataType.Phone]: {
label: 'Phone',
Icon: IconPhone,
exampleValue: '+1234-567-890',
category: 'Basic',
},
[FieldMetadataType.Rating]: {
label: 'Rating',
Icon: IconTwentyStar,
exampleValue: '3',
category: 'Basic',
},
[FieldMetadataType.FullName]: {
label: 'Full Name',
Icon: IconUser,
exampleValue: { firstName: 'John', lastName: 'Doe' },
category: 'Advanced',
},
[FieldMetadataType.Address]: {
label: 'Address',
Expand All @@ -127,20 +149,25 @@ export const SETTINGS_FIELD_TYPE_CONFIGS = {
addressLat: 34.0522,
addressLng: -118.2437,
},
category: 'Basic',
},
[FieldMetadataType.RawJson]: {
label: 'JSON',
Icon: IconJson,
exampleValue: { key: 'value' },

category: 'Basic',
Comment on lines +164 to +165
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Remove unnecessary newline between properties

},
[FieldMetadataType.RichText]: {
label: 'Rich Text',
Icon: IconFilePencil,
exampleValue: { key: 'value' },
category: 'Basic',
},
[FieldMetadataType.Actor]: {
label: 'Actor',
Icon: IconCreativeCommonsSa,
category: 'Basic',
},
} as const satisfies Record<
SettingsSupportedFieldType,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import omit from 'lodash.omit';
import styled from '@emotion/styled';
import { Controller, useFormContext } from 'react-hook-form';
import { z } from 'zod';

import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { SETTINGS_FIELD_TYPE_CATEGORIES } from '@/settings/data-model/constants/SettingsFieldTypeCategories';
import { SETTINGS_FIELD_TYPE_CATEGORY_DESCRIPTIONS } from '@/settings/data-model/constants/SettingsFieldTypeCategoryDescriptions';
import {
SETTINGS_FIELD_TYPE_CONFIGS,
SettingsFieldTypeConfig,
Expand All @@ -11,7 +13,10 @@ import { useBooleanSettingsFormInitialValues } from '@/settings/data-model/field
import { useCurrencySettingsFormInitialValues } from '@/settings/data-model/fields/forms/currency/hooks/useCurrencySettingsFormInitialValues';
import { useSelectSettingsFormInitialValues } from '@/settings/data-model/fields/forms/select/hooks/useSelectSettingsFormInitialValues';
import { SettingsSupportedFieldType } from '@/settings/data-model/types/SettingsSupportedFieldType';
import { Select, SelectOption } from '@/ui/input/components/Select';
import { Button } from '@/ui/input/button/components/Button';
import { Section } from '@react-email/components';
import { useState } from 'react';
import { H2Title } from 'twenty-ui';
import { FieldMetadataType } from '~/generated-metadata/graphql';

export const settingsDataModelFieldTypeFormSchema = z.object({
Expand All @@ -23,7 +28,7 @@ export const settingsDataModelFieldTypeFormSchema = z.object({
),
});

type SettingsDataModelFieldTypeFormValues = z.infer<
export type SettingsDataModelFieldTypeFormValues = z.infer<
typeof settingsDataModelFieldTypeFormSchema
>;

Expand All @@ -35,27 +40,55 @@ type SettingsDataModelFieldTypeSelectProps = {
FieldMetadataItem,
'defaultValue' | 'options' | 'type'
>;
selectedFieldType?: SettingsSupportedFieldType;
setSelectedFieldType: any;
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Consider using a more specific type than 'any' for setSelectedFieldType


const StyledButton = styled(Button)`
height: 32px;
width: 248px;
`;
const StyledButtonContainer = styled.div`
display: flex;
gap: 16px;
justify-content: flex-start;
flex-wrap: wrap;
`;

const StyledInput = styled.input`
border-radius: 0;
background-color: transparent;
color: ${({ theme }) => theme.font.color.primary};
font-size: ${({ theme }) => theme.font.size.md};
margin: 0;
outline: none;
height: 24px;
padding: 0;
width: ${({ theme }) => `calc(100% - ${theme.spacing(8)})`};

&::placeholder {
color: ${({ theme }) => theme.font.color.light};
font-weight: ${({ theme }) => theme.font.weight.medium};
}
`;

export const SettingsDataModelFieldTypeSelect = ({
className,
disabled,
excludedFieldTypes = [],
fieldMetadataItem,
selectedFieldType,
setSelectedFieldType,
}: SettingsDataModelFieldTypeSelectProps) => {
const { control } = useFormContext<SettingsDataModelFieldTypeFormValues>();

const fieldTypeConfigs: Partial<
Record<SettingsSupportedFieldType, SettingsFieldTypeConfig>
> = omit(SETTINGS_FIELD_TYPE_CONFIGS, excludedFieldTypes);

const fieldTypeOptions = Object.entries<SettingsFieldTypeConfig>(
fieldTypeConfigs,
).map<SelectOption<SettingsSupportedFieldType>>(([key, dataTypeConfig]) => ({
Icon: dataTypeConfig.Icon,
label: dataTypeConfig.label,
value: key as SettingsSupportedFieldType,
}));
const [searchQuery, setSearchQuery] = useState('');
const fieldTypeConfigs = Object.entries<SettingsFieldTypeConfig>(
SETTINGS_FIELD_TYPE_CONFIGS,
).filter(
([key, config]) =>
!excludedFieldTypes.includes(key as SettingsSupportedFieldType) &&
config.label.toLowerCase().includes(searchQuery.toLowerCase()),
);

const { resetDefaultValueField: resetBooleanDefaultValueField } =
useBooleanSettingsFormInitialValues({ fieldMetadataItem });
Expand All @@ -66,8 +99,6 @@ export const SettingsDataModelFieldTypeSelect = ({
const { resetDefaultValueField: resetSelectDefaultValueField } =
useSelectSettingsFormInitialValues({ fieldMetadataItem });

// Reset defaultValue on type change with a valid value for the selected type
// so the form does not become invalid.
const resetDefaultValueField = (nextValue: SettingsSupportedFieldType) => {
switch (nextValue) {
case FieldMetadataType.Boolean:
Expand All @@ -90,23 +121,54 @@ export const SettingsDataModelFieldTypeSelect = ({
name="type"
control={control}
defaultValue={
fieldMetadataItem && fieldMetadataItem.type in fieldTypeConfigs
selectedFieldType ||
(fieldMetadataItem &&
fieldMetadataItem.type in SETTINGS_FIELD_TYPE_CONFIGS
? (fieldMetadataItem.type as SettingsSupportedFieldType)
: FieldMetadataType.Text
: FieldMetadataType.Text)
}
render={({ field: { onChange, value } }) => (
<Select
className={className}
fullWidth
disabled={disabled}
dropdownId="object-field-type-select"
value={value}
onChange={(nextValue) => {
onChange(nextValue);
resetDefaultValueField(nextValue);
}}
options={fieldTypeOptions}
/>
<>
<Section>
<StyledInput
type="text"
placeholder="Search a field"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
/>
</Section>
{SETTINGS_FIELD_TYPE_CATEGORIES.map((category) => (
<Section key={category}>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: This nested mapping and filtering could potentially be optimized for better performance

<H2Title
title={category}
description={
SETTINGS_FIELD_TYPE_CATEGORY_DESCRIPTIONS[category]
}
/>
<StyledButtonContainer>
{fieldTypeConfigs
.filter(([, config]) => config.category === category)
.map(([key, config]) => (
<StyledButton
key={key}
onClick={() => {
onChange(key as SettingsSupportedFieldType);
resetDefaultValueField(
key as SettingsSupportedFieldType,
);
setSelectedFieldType(key as SettingsSupportedFieldType);
}}
disabled={disabled}
variant={value === key ? 'primary' : 'secondary'}
title={config.label}
Icon={config.Icon}
size="small"
/>
))}
</StyledButtonContainer>
</Section>
))}
</>
)}
/>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type SettingsFieldTypeCategoryType = 'Basic' | 'Advanced' | 'Relation';
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const StyledWrapper = styled.nav`
// font-weight: ${({ theme }) => theme.font.weight.semiBold};
gap: ${({ theme }) => theme.spacing(2)};
line-height: ${({ theme }) => theme.text.lineHeight.lg};
white-space: nowrap;
`;

const StyledLink = styled(Link)`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -213,11 +213,7 @@ export const SettingsObjectFieldEdit = () => {
title="Type and values"
description="The field's type and values."
/>
<StyledSettingsObjectFieldTypeSelect
disabled
fieldMetadataItem={activeMetadataField}
excludedFieldTypes={[FieldMetadataType.Link]}
/>

<SettingsDataModelFieldSettingsFormCard
disableCurrencyForm
fieldMetadataItem={activeMetadataField}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import styled from '@emotion/styled';
import { useEffect } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { H2Title, IconPlus, IconSettings } from 'twenty-ui';
import { H2Title, IconHierarchy2, IconPlus } from 'twenty-ui';

import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons';
Expand Down Expand Up @@ -85,7 +85,7 @@ export const SettingsObjectNewFieldStep1 = () => {

return (
<SubMenuTopBarContainer
Icon={IconSettings}
Icon={IconHierarchy2}
title={
<Breadcrumb
links={[
Expand Down
Loading
Loading