@ -20,7 +20,7 @@ export const CREATE_ONE_OBJECT_METADATA_ITEM = gql`
|
||||
`;
|
||||
|
||||
export const CREATE_ONE_FIELD_METADATA_ITEM = gql`
|
||||
mutation CreateOneFieldMetadataItem($input: CreateOneFieldInput!) {
|
||||
mutation CreateOneFieldMetadataItem($input: CreateOneFieldMetadataInput!) {
|
||||
createOneField(input: $input) {
|
||||
id
|
||||
type
|
||||
@ -28,12 +28,13 @@ export const CREATE_ONE_FIELD_METADATA_ITEM = gql`
|
||||
label
|
||||
description
|
||||
icon
|
||||
placeholder
|
||||
isCustom
|
||||
isActive
|
||||
isNullable
|
||||
createdAt
|
||||
updatedAt
|
||||
defaultValue
|
||||
options
|
||||
}
|
||||
}
|
||||
`;
|
||||
@ -65,7 +66,6 @@ export const UPDATE_ONE_FIELD_METADATA_ITEM = gql`
|
||||
label
|
||||
description
|
||||
icon
|
||||
placeholder
|
||||
isCustom
|
||||
isActive
|
||||
isNullable
|
||||
@ -125,7 +125,6 @@ export const DELETE_ONE_FIELD_METADATA_ITEM = gql`
|
||||
label
|
||||
description
|
||||
icon
|
||||
placeholder
|
||||
isCustom
|
||||
isActive
|
||||
isNullable
|
||||
|
||||
@ -58,6 +58,8 @@ export const FIND_MANY_OBJECT_METADATA_ITEMS = gql`
|
||||
}
|
||||
fromFieldMetadataId
|
||||
}
|
||||
defaultValue
|
||||
options
|
||||
}
|
||||
}
|
||||
pageInfo {
|
||||
|
||||
@ -1,8 +1,11 @@
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { FieldType } from '@/object-record/field/types/FieldType';
|
||||
import { Field } from '~/generated/graphql';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
|
||||
import { FieldMetadataItem } from '../types/FieldMetadataItem';
|
||||
import { FieldMetadataOption } from '../types/FieldMetadataOption';
|
||||
import { formatFieldMetadataItemInput } from '../utils/formatFieldMetadataItemInput';
|
||||
|
||||
import { useCreateOneFieldMetadataItem } from './useCreateOneFieldMetadataItem';
|
||||
@ -17,6 +20,7 @@ export const useFieldMetadataItem = () => {
|
||||
const createMetadataField = (
|
||||
input: Pick<Field, 'label' | 'icon' | 'description'> & {
|
||||
objectMetadataId: string;
|
||||
options?: Omit<FieldMetadataOption, 'id'>[];
|
||||
type: FieldMetadataType;
|
||||
},
|
||||
) =>
|
||||
@ -27,11 +31,20 @@ export const useFieldMetadataItem = () => {
|
||||
});
|
||||
|
||||
const editMetadataField = (
|
||||
input: Pick<Field, 'id' | 'label' | 'icon' | 'description'>,
|
||||
input: Pick<Field, 'id' | 'label' | 'icon' | 'description'> & {
|
||||
options?: FieldMetadataOption[];
|
||||
},
|
||||
) =>
|
||||
updateOneFieldMetadataItem({
|
||||
fieldMetadataIdToUpdate: input.id,
|
||||
updatePayload: formatFieldMetadataItemInput(input),
|
||||
updatePayload: formatFieldMetadataItemInput({
|
||||
...input,
|
||||
// In Edit mode, all options need an id,
|
||||
// so we generate an id for newly created options.
|
||||
options: input.options?.map((option) =>
|
||||
option.id ? option : { ...option, id: v4() },
|
||||
),
|
||||
}),
|
||||
});
|
||||
|
||||
const activateMetadataField = (metadataField: FieldMetadataItem) =>
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import { ThemeColor } from '@/ui/theme/constants/colors';
|
||||
import { Field, Relation } from '~/generated-metadata/graphql';
|
||||
|
||||
export type FieldMetadataItem = Omit<
|
||||
Field,
|
||||
'fromRelationMetadata' | 'toRelationMetadata'
|
||||
'fromRelationMetadata' | 'toRelationMetadata' | 'defaultValue' | 'options'
|
||||
> & {
|
||||
fromRelationMetadata?:
|
||||
| (Pick<Relation, 'id' | 'toFieldMetadataId' | 'relationType'> & {
|
||||
@ -20,4 +21,12 @@ export type FieldMetadataItem = Omit<
|
||||
>;
|
||||
})
|
||||
| null;
|
||||
defaultValue?: string;
|
||||
options?: {
|
||||
color: ThemeColor;
|
||||
id: string;
|
||||
label: string;
|
||||
position: number;
|
||||
value: string;
|
||||
}[];
|
||||
};
|
||||
|
||||
@ -0,0 +1,8 @@
|
||||
import { ThemeColor } from '@/ui/theme/constants/colors';
|
||||
|
||||
export type FieldMetadataOption = {
|
||||
color?: ThemeColor;
|
||||
id?: string;
|
||||
isDefault?: boolean;
|
||||
label: string;
|
||||
};
|
||||
@ -1,12 +1,34 @@
|
||||
import toCamelCase from 'lodash.camelcase';
|
||||
import toSnakeCase from 'lodash.snakecase';
|
||||
|
||||
import { Field } from '~/generated-metadata/graphql';
|
||||
|
||||
import { FieldMetadataOption } from '../types/FieldMetadataOption';
|
||||
|
||||
const getOptionValueFromLabel = (label: string) =>
|
||||
toSnakeCase(label.trim()).toUpperCase();
|
||||
|
||||
export const formatFieldMetadataItemInput = (
|
||||
input: Pick<Field, 'label' | 'icon' | 'description'>,
|
||||
) => ({
|
||||
description: input.description?.trim() ?? null,
|
||||
icon: input.icon,
|
||||
label: input.label.trim(),
|
||||
name: toCamelCase(input.label.trim()),
|
||||
});
|
||||
input: Pick<Field, 'label' | 'icon' | 'description' | 'defaultValue'> & {
|
||||
options?: FieldMetadataOption[];
|
||||
},
|
||||
) => {
|
||||
const defaultOption = input.options?.find((option) => option.isDefault);
|
||||
|
||||
return {
|
||||
defaultValue: defaultOption
|
||||
? getOptionValueFromLabel(defaultOption.label)
|
||||
: undefined,
|
||||
description: input.description?.trim() ?? null,
|
||||
icon: input.icon,
|
||||
label: input.label.trim(),
|
||||
name: toCamelCase(input.label.trim()),
|
||||
options: input.options?.map((option, index) => ({
|
||||
color: option.color,
|
||||
id: option.id,
|
||||
label: option.label.trim(),
|
||||
position: index,
|
||||
value: getOptionValueFromLabel(option.label),
|
||||
})),
|
||||
};
|
||||
};
|
||||
|
||||
@ -12,11 +12,7 @@ import { isFieldRating } from '../../types/guards/isFieldRating';
|
||||
export const useRatingField = () => {
|
||||
const { entityId, fieldDefinition, hotkeyScope } = useContext(FieldContext);
|
||||
|
||||
assertFieldMetadata(
|
||||
FieldMetadataType.Probability,
|
||||
isFieldRating,
|
||||
fieldDefinition,
|
||||
);
|
||||
assertFieldMetadata(FieldMetadataType.Rating, isFieldRating, fieldDefinition);
|
||||
|
||||
const fieldName = fieldDefinition.metadata.fieldName;
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@ import { useContext } from 'react';
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { ThemeColor } from '@/ui/theme/constants/colors';
|
||||
import { FieldMetadataType } from '~/generated/graphql';
|
||||
|
||||
import { FieldContext } from '../../contexts/FieldContext';
|
||||
import { useFieldInitialValue } from '../../hooks/useFieldInitialValue';
|
||||
@ -14,7 +15,7 @@ import { isFieldSelectValue } from '../../types/guards/isFieldSelectValue';
|
||||
export const useSelectField = () => {
|
||||
const { entityId, fieldDefinition, hotkeyScope } = useContext(FieldContext);
|
||||
|
||||
assertFieldMetadata('ENUM', isFieldSelect, fieldDefinition);
|
||||
assertFieldMetadata(FieldMetadataType.Select, isFieldSelect, fieldDefinition);
|
||||
|
||||
const { fieldName } = fieldDefinition.metadata;
|
||||
|
||||
|
||||
@ -46,7 +46,7 @@ const RatingFieldInputWithContext = ({
|
||||
fieldDefinition={{
|
||||
fieldMetadataId: 'rating',
|
||||
label: 'Rating',
|
||||
type: FieldMetadataType.Probability,
|
||||
type: FieldMetadataType.Rating,
|
||||
iconName: 'Icon123',
|
||||
metadata: {
|
||||
fieldName: 'Rating',
|
||||
|
||||
@ -2,6 +2,7 @@ import { selectorFamily } from 'recoil';
|
||||
|
||||
import { isFieldFullName } from '@/object-record/field/types/guards/isFieldFullName';
|
||||
import { isFieldFullNameValue } from '@/object-record/field/types/guards/isFieldFullNameValue';
|
||||
import { isFieldSelect } from '@/object-record/field/types/guards/isFieldSelect';
|
||||
import { isFieldUuid } from '@/object-record/field/types/guards/isFieldUuid';
|
||||
import { assertNotNull } from '~/utils/assert';
|
||||
|
||||
@ -42,7 +43,8 @@ export const isEntityFieldEmptyFamilySelector = selectorFamily({
|
||||
isFieldNumber(fieldDefinition) ||
|
||||
isFieldRating(fieldDefinition) ||
|
||||
isFieldEmail(fieldDefinition) ||
|
||||
isFieldBoolean(fieldDefinition)
|
||||
isFieldBoolean(fieldDefinition) ||
|
||||
isFieldSelect(fieldDefinition)
|
||||
//|| isFieldPhone(fieldDefinition)
|
||||
) {
|
||||
const fieldValue = get(entityFieldsFamilyState(entityId))?.[
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
export type FieldType =
|
||||
| 'BOOLEAN'
|
||||
| 'UUID'
|
||||
| 'TEXT'
|
||||
| 'RELATION'
|
||||
| 'CHIP'
|
||||
| 'CURRENCY'
|
||||
| 'DATE_TIME'
|
||||
| 'DOUBLE_TEXT_CHIP'
|
||||
| 'DOUBLE_TEXT'
|
||||
| 'EMAIL'
|
||||
| 'ENUM'
|
||||
| 'FULL_NAME'
|
||||
| 'LINK'
|
||||
| 'NUMBER'
|
||||
| 'PHONE'
|
||||
| 'PROBABILITY'
|
||||
| 'RATING'
|
||||
| 'RELATION'
|
||||
| 'SELECT'
|
||||
| 'TEXT'
|
||||
| 'URL'
|
||||
| 'LINK'
|
||||
| 'CURRENCY'
|
||||
| 'FULL_NAME';
|
||||
| 'UUID';
|
||||
|
||||
@ -29,7 +29,7 @@ type AssertFieldMetadataFunction = <
|
||||
? FieldDateTimeMetadata
|
||||
: E extends 'EMAIL'
|
||||
? FieldEmailMetadata
|
||||
: E extends 'ENUM'
|
||||
: E extends 'SELECT'
|
||||
? FieldSelectMetadata
|
||||
: E extends 'LINK'
|
||||
? FieldLinkMetadata
|
||||
@ -37,7 +37,7 @@ type AssertFieldMetadataFunction = <
|
||||
? FieldNumberMetadata
|
||||
: E extends 'PHONE'
|
||||
? FieldPhoneMetadata
|
||||
: E extends 'PROBABILITY'
|
||||
: E extends 'RATING'
|
||||
? FieldRatingMetadata
|
||||
: E extends 'RELATION'
|
||||
? FieldRelationMetadata
|
||||
|
||||
@ -6,4 +6,4 @@ import { FieldMetadata, FieldRatingMetadata } from '../FieldMetadata';
|
||||
export const isFieldRating = (
|
||||
field: Pick<FieldDefinition<FieldMetadata>, 'type'>,
|
||||
): field is FieldDefinition<FieldRatingMetadata> =>
|
||||
field.type === FieldMetadataType.Probability;
|
||||
field.type === FieldMetadataType.Rating;
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
import { FieldMetadataType } from '~/generated/graphql';
|
||||
|
||||
import { FieldDefinition } from '../FieldDefinition';
|
||||
import { FieldMetadata, FieldSelectMetadata } from '../FieldMetadata';
|
||||
|
||||
export const isFieldSelect = (
|
||||
field: FieldDefinition<FieldMetadata>,
|
||||
): field is FieldDefinition<FieldSelectMetadata> => field.type === 'ENUM';
|
||||
field: Pick<FieldDefinition<FieldMetadata>, 'type'>,
|
||||
): field is FieldDefinition<FieldSelectMetadata> =>
|
||||
field.type === FieldMetadataType.Select;
|
||||
|
||||
@ -148,7 +148,7 @@ export const SettingsObjectFieldPreview = ({
|
||||
>
|
||||
{fieldMetadata.type === FieldMetadataType.Boolean ? (
|
||||
<BooleanFieldInput readonly />
|
||||
) : fieldMetadata.type === FieldMetadataType.Probability ? (
|
||||
) : fieldMetadata.type === FieldMetadataType.Rating ? (
|
||||
<RatingFieldInput readonly />
|
||||
) : (
|
||||
<FieldDisplay />
|
||||
|
||||
@ -88,10 +88,10 @@ export const SettingsObjectFieldTypeSelectSection = ({
|
||||
FieldMetadataType.Boolean,
|
||||
FieldMetadataType.Currency,
|
||||
FieldMetadataType.DateTime,
|
||||
FieldMetadataType.Enum,
|
||||
FieldMetadataType.Select,
|
||||
FieldMetadataType.Link,
|
||||
FieldMetadataType.Number,
|
||||
FieldMetadataType.Probability,
|
||||
FieldMetadataType.Rating,
|
||||
FieldMetadataType.Relation,
|
||||
FieldMetadataType.Text,
|
||||
].includes(values.type) && (
|
||||
@ -151,7 +151,7 @@ export const SettingsObjectFieldTypeSelectSection = ({
|
||||
})
|
||||
}
|
||||
/>
|
||||
) : values.type === FieldMetadataType.Enum ? (
|
||||
) : values.type === FieldMetadataType.Select ? (
|
||||
<SettingsObjectFieldSelectForm
|
||||
values={selectFormConfig}
|
||||
onChange={(nextValues) => onChange({ select: nextValues })}
|
||||
|
||||
@ -95,7 +95,7 @@ export const Rating: Story = {
|
||||
fieldMetadata: {
|
||||
icon: 'IconHandClick',
|
||||
label: 'Engagement',
|
||||
type: FieldMetadataType.Probability,
|
||||
type: FieldMetadataType.Rating,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@ -108,7 +108,7 @@ export const WithSelectForm: Story = {
|
||||
fieldMetadata: { label: 'Industry', icon: 'IconBuildingFactory2' },
|
||||
values: {
|
||||
...fieldMetadataFormDefaultValues,
|
||||
type: FieldMetadataType.Enum,
|
||||
type: FieldMetadataType.Select,
|
||||
select: [
|
||||
{
|
||||
color: 'pink',
|
||||
|
||||
@ -59,10 +59,14 @@ export const settingsFieldMetadataTypes: Record<
|
||||
Icon: IconCalendarEvent,
|
||||
defaultValue: defaultDateValue.toISOString(),
|
||||
},
|
||||
[FieldMetadataType.Enum]: {
|
||||
[FieldMetadataType.Select]: {
|
||||
label: 'Select',
|
||||
Icon: IconTag,
|
||||
},
|
||||
[FieldMetadataType.MultiSelect]: {
|
||||
label: 'Multi-Select',
|
||||
Icon: IconTag,
|
||||
},
|
||||
[FieldMetadataType.Currency]: {
|
||||
label: 'Currency',
|
||||
Icon: IconCoins,
|
||||
@ -79,5 +83,10 @@ export const settingsFieldMetadataTypes: Record<
|
||||
Icon: IconTwentyStar,
|
||||
defaultValue: '3',
|
||||
},
|
||||
[FieldMetadataType.Rating]: {
|
||||
label: 'Rating',
|
||||
Icon: IconTwentyStar,
|
||||
defaultValue: '3',
|
||||
},
|
||||
[FieldMetadataType.FullName]: { label: 'Full Name', Icon: IconUser },
|
||||
};
|
||||
|
||||
@ -56,11 +56,12 @@ const relationSchema = fieldSchema.merge(
|
||||
|
||||
const selectSchema = fieldSchema.merge(
|
||||
z.object({
|
||||
type: z.literal(FieldMetadataType.Enum),
|
||||
type: z.literal(FieldMetadataType.Select),
|
||||
select: z
|
||||
.array(
|
||||
z.object({
|
||||
color: themeColorSchema,
|
||||
id: z.string().optional(),
|
||||
isDefault: z.boolean().optional(),
|
||||
label: z.string().min(1),
|
||||
}),
|
||||
@ -70,18 +71,20 @@ const selectSchema = fieldSchema.merge(
|
||||
);
|
||||
|
||||
const {
|
||||
Enum: _Enum,
|
||||
Select: _Select,
|
||||
Relation: _Relation,
|
||||
...otherFieldTypes
|
||||
} = FieldMetadataType;
|
||||
|
||||
type OtherFieldType = Exclude<
|
||||
FieldMetadataType,
|
||||
FieldMetadataType.Relation | FieldMetadataType.Select
|
||||
>;
|
||||
|
||||
const otherFieldTypesSchema = fieldSchema.merge(
|
||||
z.object({
|
||||
type: z.enum(
|
||||
Object.values(otherFieldTypes) as [
|
||||
Exclude<FieldMetadataType, FieldMetadataType.Relation>,
|
||||
...Exclude<FieldMetadataType, FieldMetadataType.Relation>[],
|
||||
],
|
||||
Object.values(otherFieldTypes) as [OtherFieldType, ...OtherFieldType[]],
|
||||
),
|
||||
}),
|
||||
);
|
||||
@ -165,7 +168,7 @@ export const useFieldMetadataForm = () => {
|
||||
!isDeeplyEqual(initialRelationFormValues, nextRelationFormValues),
|
||||
);
|
||||
setHasSelectFormChanged(
|
||||
nextFieldFormValues.type === FieldMetadataType.Enum &&
|
||||
nextFieldFormValues.type === FieldMetadataType.Select &&
|
||||
!isDeeplyEqual(initialSelectFormValues, nextSelectFormValues),
|
||||
);
|
||||
};
|
||||
@ -177,6 +180,7 @@ export const useFieldMetadataForm = () => {
|
||||
hasFormChanged:
|
||||
hasFieldFormChanged || hasRelationFormChanged || hasSelectFormChanged,
|
||||
hasRelationFormChanged,
|
||||
hasSelectFormChanged,
|
||||
initForm,
|
||||
isInitialized,
|
||||
isValid: validationResult.success,
|
||||
|
||||
@ -48,7 +48,7 @@ export const useFieldPreview = ({
|
||||
|
||||
const defaultSelectValue = selectOptions?.[0];
|
||||
const selectValue =
|
||||
fieldMetadata.type === FieldMetadataType.Enum &&
|
||||
fieldMetadata.type === FieldMetadataType.Select &&
|
||||
typeof firstRecordFieldValue === 'string'
|
||||
? selectOptions?.find(
|
||||
(selectOption) => selectOption.value === firstRecordFieldValue,
|
||||
@ -65,7 +65,7 @@ export const useFieldPreview = ({
|
||||
value:
|
||||
fieldMetadata.type === FieldMetadataType.Relation
|
||||
? relationValue
|
||||
: fieldMetadata.type === FieldMetadataType.Enum
|
||||
: fieldMetadata.type === FieldMetadataType.Select
|
||||
? selectValue || defaultSelectValue
|
||||
: firstRecordFieldValue || defaultValue,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user