feat: add Select field preview and form (#2655)

Closes #2432
This commit is contained in:
Thaïs 2023-11-28 23:44:21 +01:00 committed by GitHub
parent 0fa55b0634
commit bc787f72ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 317 additions and 87 deletions

View File

@ -10,15 +10,16 @@ import { Field } from '~/generated/graphql';
import { FieldMetadataType } from '~/generated-metadata/graphql';
import { SettingsObjectFieldPreviewValueEffect } from '../components/SettingsObjectFieldPreviewValueEffect';
import { settingsFieldMetadataTypes } from '../constants/settingsFieldMetadataTypes';
import { useFieldPreview } from '../hooks/useFieldPreview';
import { useRelationFieldPreview } from '../hooks/useRelationFieldPreview';
import { SettingsObjectFieldSelectFormValues } from './SettingsObjectFieldSelectForm';
export type SettingsObjectFieldPreviewProps = {
className?: string;
fieldMetadata: Pick<Field, 'icon' | 'label' | 'type'> & { id?: string };
objectMetadataId: string;
relationObjectMetadataId?: string;
selectOptions?: SettingsObjectFieldSelectFormValues;
shrink?: boolean;
};
@ -73,6 +74,7 @@ export const SettingsObjectFieldPreview = ({
fieldMetadata,
objectMetadataId,
relationObjectMetadataId,
selectOptions,
shrink,
}: SettingsObjectFieldPreviewProps) => {
const theme = useTheme();
@ -81,27 +83,17 @@ export const SettingsObjectFieldPreview = ({
entityId,
FieldIcon,
fieldName,
hasValue,
ObjectIcon,
objectMetadataItem,
relationObjectMetadataItem,
value,
} = useFieldPreview({
fieldMetadata,
objectMetadataId,
relationObjectMetadataId,
selectOptions,
});
const { defaultValue: relationDefaultValue, relationObjectMetadataItem } =
useRelationFieldPreview({
relationObjectMetadataId,
skipDefaultValue:
fieldMetadata.type !== FieldMetadataType.Relation || hasValue,
});
const defaultValue =
fieldMetadata.type === FieldMetadataType.Relation
? relationDefaultValue
: settingsFieldMetadataTypes[fieldMetadata.type].defaultValue;
return (
<StyledContainer className={className}>
<StyledObjectSummary>
@ -123,7 +115,7 @@ export const SettingsObjectFieldPreview = ({
<SettingsObjectFieldPreviewValueEffect
entityId={entityId}
fieldName={fieldName}
value={value ?? defaultValue}
value={value}
/>
<StyledFieldPreview shrink={shrink}>
<StyledFieldLabel>

View File

@ -0,0 +1,58 @@
import styled from '@emotion/styled';
import { TextInput } from '@/ui/input/components/TextInput';
import { ThemeColor } from '@/ui/theme/constants/colors';
export type SettingsObjectFieldSelectFormValues = {
color: ThemeColor;
text: string;
}[];
type SettingsObjectFieldSelectFormProps = {
onChange: (values: SettingsObjectFieldSelectFormValues) => void;
values?: SettingsObjectFieldSelectFormValues;
};
const StyledLabel = styled.span`
color: ${({ theme }) => theme.font.color.light};
display: block;
font-size: ${({ theme }) => theme.font.size.xs};
font-weight: ${({ theme }) => theme.font.weight.semiBold};
margin-bottom: ${({ theme }) => theme.spacing(3)};
text-transform: uppercase;
`;
const StyledInputsContainer = styled.div`
display: flex;
flex-direction: column;
gap: 10px;
`;
const StyledOptionInput = styled(TextInput)`
& input {
height: ${({ theme }) => theme.spacing(2)};
}
`;
export const SettingsObjectFieldSelectForm = ({
onChange,
values = [],
}: SettingsObjectFieldSelectFormProps) => {
return (
<div>
<StyledLabel>Options</StyledLabel>
<StyledInputsContainer>
{values.map((value, index) => (
<StyledOptionInput
value={value.text}
onChange={(text) => {
const nextValues = [...values];
nextValues.splice(index, 1, { ...values[index], text });
onChange(nextValues);
}}
/>
))}
</StyledInputsContainer>
</div>
);
};

View File

@ -16,11 +16,16 @@ import {
SettingsObjectFieldRelationForm,
SettingsObjectFieldRelationFormValues,
} from './SettingsObjectFieldRelationForm';
import {
SettingsObjectFieldSelectForm,
SettingsObjectFieldSelectFormValues,
} from './SettingsObjectFieldSelectForm';
import { SettingsObjectFieldTypeCard } from './SettingsObjectFieldTypeCard';
export type SettingsObjectFieldTypeSelectSectionFormValues = Partial<{
type: FieldMetadataType;
relation: SettingsObjectFieldRelationFormValues;
select: SettingsObjectFieldSelectFormValues;
}>;
type SettingsObjectFieldTypeSelectSectionProps = {
@ -54,6 +59,7 @@ export const SettingsObjectFieldTypeSelectSection = ({
values,
}: SettingsObjectFieldTypeSelectSectionProps) => {
const relationFormConfig = values?.relation;
const selectFormConfig = values?.select;
const fieldTypeOptions = Object.entries(settingsFieldMetadataTypes)
.filter(([key]) => !excludedFieldTypes?.includes(key as FieldMetadataType))
@ -80,6 +86,7 @@ export const SettingsObjectFieldTypeSelectSection = ({
FieldMetadataType.Boolean,
FieldMetadataType.Currency,
FieldMetadataType.DateTime,
FieldMetadataType.Enum,
FieldMetadataType.Link,
FieldMetadataType.Number,
FieldMetadataType.Relation,
@ -98,6 +105,7 @@ export const SettingsObjectFieldTypeSelectSection = ({
relationObjectMetadataId={
relationFormConfig?.objectMetadataId
}
selectOptions={selectFormConfig}
/>
{values.type === FieldMetadataType.Relation &&
!!relationFormConfig?.type &&
@ -127,7 +135,7 @@ export const SettingsObjectFieldTypeSelectSection = ({
</>
}
form={
values.type === FieldMetadataType.Relation && (
values.type === FieldMetadataType.Relation ? (
<SettingsObjectFieldRelationForm
disableFieldEdition={
relationFieldMetadata && !relationFieldMetadata.isCustom
@ -140,7 +148,12 @@ export const SettingsObjectFieldTypeSelectSection = ({
})
}
/>
)
) : values.type === FieldMetadataType.Enum ? (
<SettingsObjectFieldSelectForm
values={selectFormConfig}
onChange={(nextValues) => onChange({ select: nextValues })}
/>
) : undefined
}
/>
)}

View File

@ -61,7 +61,6 @@ export const settingsFieldMetadataTypes: Record<
[FieldMetadataType.Enum]: {
label: 'Select',
Icon: IconTag,
defaultValue: { color: 'green', text: 'Option 1' },
},
[FieldMetadataType.Currency]: {
label: 'Currency',

View File

@ -2,6 +2,7 @@ import { useState } from 'react';
import { DeepPartial } from 'react-hook-form';
import { z } from 'zod';
import { mainColors, ThemeColor } from '@/ui/theme/constants/colors';
import {
FieldMetadataType,
RelationMetadataType,
@ -16,6 +17,7 @@ type FormValues = {
label: string;
type: FieldMetadataType;
relation: SettingsObjectFieldTypeSelectSectionFormValues['relation'];
select: SettingsObjectFieldTypeSelectSectionFormValues['select'];
};
const defaultValues: FormValues = {
@ -25,6 +27,7 @@ const defaultValues: FormValues = {
relation: {
type: RelationMetadataType.OneToMany,
},
select: [{ color: 'green', text: 'Option 1' }],
};
const fieldSchema = z.object({
@ -48,7 +51,27 @@ const relationSchema = fieldSchema.merge(
}),
);
const { Relation: _, ...otherFieldTypes } = FieldMetadataType;
const selectSchema = fieldSchema.merge(
z.object({
type: z.literal(FieldMetadataType.Enum),
select: z
.array(
z.object({
color: z.enum(
Object.keys(mainColors) as [ThemeColor, ...ThemeColor[]],
),
text: z.string().min(1),
}),
)
.nonempty(),
}),
);
const {
Enum: _Enum,
Relation: _Relation,
...otherFieldTypes
} = FieldMetadataType;
const otherFieldTypesSchema = fieldSchema.merge(
z.object({
@ -63,9 +86,13 @@ const otherFieldTypesSchema = fieldSchema.merge(
const schema = z.discriminatedUnion('type', [
relationSchema,
selectSchema,
otherFieldTypesSchema,
]);
type PartialFormValues = Partial<FormValues> &
DeepPartial<Pick<FormValues, 'relation'>>;
export const useFieldMetadataForm = () => {
const [isInitialized, setIsInitialized] = useState(false);
const [initialFormValues, setInitialFormValues] =
@ -73,14 +100,15 @@ export const useFieldMetadataForm = () => {
const [formValues, setFormValues] = useState<FormValues>(defaultValues);
const [hasFieldFormChanged, setHasFieldFormChanged] = useState(false);
const [hasRelationFormChanged, setHasRelationFormChanged] = useState(false);
const [hasSelectFormChanged, setHasSelectFormChanged] = useState(false);
const [validationResult, setValidationResult] = useState(
schema.safeParse(formValues),
);
const mergePartialValues = (
previousValues: FormValues,
nextValues: DeepPartial<FormValues>,
) => ({
nextValues: PartialFormValues,
): FormValues => ({
...previousValues,
...nextValues,
relation: {
@ -93,7 +121,7 @@ export const useFieldMetadataForm = () => {
},
});
const initForm = (lazyInitialFormValues: DeepPartial<FormValues>) => {
const initForm = (lazyInitialFormValues: PartialFormValues) => {
if (isInitialized) return;
const mergedFormValues = mergePartialValues(
@ -107,16 +135,22 @@ export const useFieldMetadataForm = () => {
setIsInitialized(true);
};
const handleFormChange = (values: DeepPartial<FormValues>) => {
const handleFormChange = (values: PartialFormValues) => {
const nextFormValues = mergePartialValues(formValues, values);
setFormValues(nextFormValues);
setValidationResult(schema.safeParse(nextFormValues));
const { relation: initialRelationFormValues, ...initialFieldFormValues } =
initialFormValues;
const { relation: nextRelationFormValues, ...nextFieldFormValues } =
nextFormValues;
const {
relation: initialRelationFormValues,
select: initialSelectFormValues,
...initialFieldFormValues
} = initialFormValues;
const {
relation: nextRelationFormValues,
select: nextSelectFormValues,
...nextFieldFormValues
} = nextFormValues;
setHasFieldFormChanged(
!isDeeplyEqual(initialFieldFormValues, nextFieldFormValues),
@ -125,13 +159,18 @@ export const useFieldMetadataForm = () => {
nextFieldFormValues.type === FieldMetadataType.Relation &&
!isDeeplyEqual(initialRelationFormValues, nextRelationFormValues),
);
setHasSelectFormChanged(
nextFieldFormValues.type === FieldMetadataType.Enum &&
!isDeeplyEqual(initialSelectFormValues, nextSelectFormValues),
);
};
return {
formValues,
handleFormChange,
hasFieldFormChanged,
hasFormChanged: hasFieldFormChanged || hasRelationFormChanged,
hasFormChanged:
hasFieldFormChanged || hasRelationFormChanged || hasSelectFormChanged,
hasRelationFormChanged,
initForm,
isInitialized,

View File

@ -1,43 +1,73 @@
import { useObjectMetadataItemForSettings } from '@/object-metadata/hooks/useObjectMetadataItemForSettings';
import { useFindManyObjectRecords } from '@/object-record/hooks/useFindManyObjectRecords';
import { useLazyLoadIcon } from '@/ui/input/hooks/useLazyLoadIcon';
import { Field } from '~/generated-metadata/graphql';
import { assertNotNull } from '~/utils/assert';
import { Field, FieldMetadataType } from '~/generated-metadata/graphql';
import { SettingsObjectFieldSelectFormValues } from '../components/SettingsObjectFieldSelectForm';
import { settingsFieldMetadataTypes } from '../constants/settingsFieldMetadataTypes';
import { useFieldPreviewValue } from './useFieldPreviewValue';
import { useRelationFieldPreviewValue } from './useRelationFieldPreviewValue';
export const useFieldPreview = ({
fieldMetadata,
objectMetadataId,
relationObjectMetadataId,
selectOptions,
}: {
fieldMetadata: Partial<Pick<Field, 'icon' | 'id' | 'type'>>;
fieldMetadata: Pick<Field, 'icon' | 'label' | 'type'> & { id?: string };
objectMetadataId: string;
relationObjectMetadataId?: string;
selectOptions?: SettingsObjectFieldSelectFormValues;
}) => {
const { findObjectMetadataItemById } = useObjectMetadataItemForSettings();
const objectMetadataItem = findObjectMetadataItemById(objectMetadataId);
const { objects } = useFindManyObjectRecords({
objectNamePlural: objectMetadataItem?.namePlural,
skip: !objectMetadataItem || !fieldMetadata.id,
});
const { Icon: ObjectIcon } = useLazyLoadIcon(objectMetadataItem?.icon ?? '');
const { Icon: FieldIcon } = useLazyLoadIcon(fieldMetadata.icon ?? '');
const [firstRecord] = objects;
const fieldName = fieldMetadata.id
? objectMetadataItem?.fields.find(({ id }) => id === fieldMetadata.id)?.name
: undefined;
const value =
fieldMetadata.type !== 'RELATION' && fieldName
? firstRecord?.[fieldName]
: undefined;
const { value: firstRecordFieldValue } = useFieldPreviewValue({
fieldName: fieldName || '',
objectNamePlural: objectMetadataItem?.namePlural || '',
skip:
!fieldName ||
!objectMetadataItem ||
fieldMetadata.type === FieldMetadataType.Relation,
});
const { relationObjectMetadataItem, value: relationValue } =
useRelationFieldPreviewValue({
relationObjectMetadataId,
skip: fieldMetadata.type !== FieldMetadataType.Relation,
});
const defaultValue =
fieldMetadata.type === FieldMetadataType.Enum
? selectOptions?.[0]
: settingsFieldMetadataTypes[fieldMetadata.type].defaultValue;
const isValidSelectValue =
fieldMetadata.type === FieldMetadataType.Enum &&
!!firstRecordFieldValue &&
selectOptions?.some(
(selectOption) => selectOption.text === firstRecordFieldValue,
);
return {
entityId: firstRecord?.id || `${objectMetadataId}-no-records`,
entityId: `${objectMetadataId}-field-form`,
FieldIcon,
fieldName: fieldName || `${fieldMetadata.type}-new-field`,
hasValue: assertNotNull(value),
ObjectIcon,
objectMetadataItem,
value,
relationObjectMetadataItem,
value:
(fieldMetadata.type === FieldMetadataType.Relation
? relationValue
: fieldMetadata.type !== FieldMetadataType.Enum || isValidSelectValue
? firstRecordFieldValue
: undefined) || defaultValue,
};
};

View File

@ -0,0 +1,25 @@
import { useFindManyObjectRecords } from '@/object-record/hooks/useFindManyObjectRecords';
import { assertNotNull } from '~/utils/assert';
export const useFieldPreviewValue = ({
fieldName,
objectNamePlural,
skip,
}: {
fieldName: string;
objectNamePlural: string;
skip?: boolean;
}) => {
const { objects } = useFindManyObjectRecords({
objectNamePlural,
skip,
});
const firstRecordWithValue = objects.find(
(record) => assertNotNull(record[fieldName]) && record[fieldName] !== '',
);
return {
value: firstRecordWithValue?.[fieldName],
};
};

View File

@ -1,13 +1,12 @@
import { useObjectMetadataItemForSettings } from '@/object-metadata/hooks/useObjectMetadataItemForSettings';
import { useFindManyObjectRecords } from '@/object-record/hooks/useFindManyObjectRecords';
import { capitalize } from '~/utils/string/capitalize';
export const useRelationFieldPreview = ({
export const useRelationFieldPreviewValue = ({
relationObjectMetadataId,
skipDefaultValue,
skip,
}: {
relationObjectMetadataId?: string;
skipDefaultValue: boolean;
skip?: boolean;
}) => {
const { findObjectMetadataItemById } = useObjectMetadataItemForSettings();
@ -17,18 +16,16 @@ export const useRelationFieldPreview = ({
const { objects: relationObjects } = useFindManyObjectRecords({
objectNamePlural: relationObjectMetadataItem?.namePlural,
skip: skipDefaultValue || !relationObjectMetadataItem,
skip: skip || !relationObjectMetadataItem,
});
const mockValueName = capitalize(
relationObjectMetadataItem?.nameSingular ?? '',
);
const label = relationObjectMetadataItem?.labelSingular ?? '';
return {
relationObjectMetadataItem,
defaultValue: relationObjects?.[0] ?? {
company: { name: mockValueName }, // Temporary mock for opportunities, this needs to be replaced once labelIdentifiers are implemented
name: mockValueName,
value: relationObjects?.[0] ?? {
company: { name: label }, // Temporary mock for opportunities, this needs to be replaced once labelIdentifiers are implemented
name: label,
},
};
};

View File

@ -1,28 +1,29 @@
import { useContext } from 'react';
import { ChipFieldDisplay } from '@/ui/object/field/meta-types/display/components/ChipFieldDisplay';
import { FullNameFieldDisplay } from '@/ui/object/field/meta-types/display/components/FullNameFieldDisplay';
import { LinkFieldDisplay } from '@/ui/object/field/meta-types/display/components/LinkFieldDisplay';
import { RelationFieldDisplay } from '@/ui/object/field/meta-types/display/components/RelationFieldDisplay';
import { UuidFieldDisplay } from '@/ui/object/field/meta-types/display/components/UuidFieldDisplay';
import { isFieldFullName } from '@/ui/object/field/types/guards/isFieldFullName';
import { isFieldLink } from '@/ui/object/field/types/guards/isFieldLink';
import { isFieldUuid } from '@/ui/object/field/types/guards/isFieldUuid';
import { FieldContext } from '../contexts/FieldContext';
import { ChipFieldDisplay } from '../meta-types/display/components/ChipFieldDisplay';
import { CurrencyFieldDisplay } from '../meta-types/display/components/CurrencyFieldDisplay';
import { DateFieldDisplay } from '../meta-types/display/components/DateFieldDisplay';
import { EmailFieldDisplay } from '../meta-types/display/components/EmailFieldDisplay';
import { FullNameFieldDisplay } from '../meta-types/display/components/FullNameFieldDisplay';
import { LinkFieldDisplay } from '../meta-types/display/components/LinkFieldDisplay';
import { NumberFieldDisplay } from '../meta-types/display/components/NumberFieldDisplay';
import { PhoneFieldDisplay } from '../meta-types/display/components/PhoneFieldDisplay';
import { RelationFieldDisplay } from '../meta-types/display/components/RelationFieldDisplay';
import { SelectFieldDisplay } from '../meta-types/display/components/SelectFieldDisplay';
import { TextFieldDisplay } from '../meta-types/display/components/TextFieldDisplay';
import { UuidFieldDisplay } from '../meta-types/display/components/UuidFieldDisplay';
import { isFieldCurrency } from '../types/guards/isFieldCurrency';
import { isFieldDateTime } from '../types/guards/isFieldDateTime';
import { isFieldEmail } from '../types/guards/isFieldEmail';
import { isFieldFullName } from '../types/guards/isFieldFullName';
import { isFieldLink } from '../types/guards/isFieldLink';
import { isFieldNumber } from '../types/guards/isFieldNumber';
import { isFieldPhone } from '../types/guards/isFieldPhone';
import { isFieldRelation } from '../types/guards/isFieldRelation';
import { isFieldSelect } from '../types/guards/isFieldSelect';
import { isFieldText } from '../types/guards/isFieldText';
import { isFieldUuid } from '../types/guards/isFieldUuid';
export const FieldDisplay = () => {
const { fieldDefinition, isLabelIdentifier } = useContext(FieldContext);
@ -55,6 +56,8 @@ export const FieldDisplay = () => {
<FullNameFieldDisplay />
) : isFieldPhone(fieldDefinition) ? (
<PhoneFieldDisplay />
) : isFieldSelect(fieldDefinition) ? (
<SelectFieldDisplay />
) : (
<></>
)}

View File

@ -0,0 +1,9 @@
import { Tag } from '@/ui/display/tag/components/Tag';
import { useSelectField } from '../../hooks/useSelectField';
export const SelectFieldDisplay = () => {
const { fieldValue } = useSelectField();
return <Tag color={fieldValue.color} text={fieldValue.text} />;
};

View File

@ -0,0 +1,47 @@
import { useContext } from 'react';
import { useRecoilState } from 'recoil';
import { ThemeColor } from '@/ui/theme/constants/colors';
import { FieldContext } from '../../contexts/FieldContext';
import { useFieldInitialValue } from '../../hooks/useFieldInitialValue';
import { entityFieldsFamilySelector } from '../../states/selectors/entityFieldsFamilySelector';
import { FieldSelectValue } from '../../types/FieldMetadata';
import { assertFieldMetadata } from '../../types/guards/assertFieldMetadata';
import { isFieldSelect } from '../../types/guards/isFieldSelect';
import { isFieldSelectValue } from '../../types/guards/isFieldSelectValue';
export const useSelectField = () => {
const { entityId, fieldDefinition, hotkeyScope } = useContext(FieldContext);
assertFieldMetadata('ENUM', isFieldSelect, fieldDefinition);
const { fieldName } = fieldDefinition.metadata;
const [fieldValue, setFieldValue] = useRecoilState<FieldSelectValue>(
entityFieldsFamilySelector({
entityId: entityId,
fieldName: fieldName,
}),
);
const fieldSelectValue = isFieldSelectValue(fieldValue)
? fieldValue
: { color: 'green' as ThemeColor, text: '' };
const fieldInitialValue = useFieldInitialValue();
const initialValue = {
color: 'green' as ThemeColor,
text: fieldInitialValue?.isEmpty
? ''
: fieldInitialValue?.value ?? fieldSelectValue?.text ?? '',
};
return {
fieldDefinition,
fieldValue: fieldSelectValue,
initialValue,
setFieldValue,
hotkeyScope,
};
};

View File

@ -1,4 +1,5 @@
import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect';
import { ThemeColor } from '@/ui/theme/constants/colors';
export type FieldUuidMetadata = {
objectMetadataNameSingular?: string;
@ -80,19 +81,24 @@ export type FieldRelationMetadata = {
relationObjectMetadataNamePlural: string;
};
export type FieldSelectMetadata = {
fieldName: string;
};
export type FieldMetadata =
| FieldBooleanMetadata
| FieldNumberMetadata
| FieldDateTimeMetadata
| FieldTextMetadata
| FieldUuidMetadata
| FieldCurrencyMetadata
| FieldLinkMetadata
| FieldPhoneMetadata
| FieldDateTimeMetadata
| FieldEmailMetadata
| FieldFullNameMetadata
| FieldLinkMetadata
| FieldNumberMetadata
| FieldPhoneMetadata
| FieldProbabilityMetadata
| FieldRelationMetadata
| FieldFullNameMetadata;
| FieldSelectMetadata
| FieldTextMetadata
| FieldUuidMetadata;
export type FieldTextValue = string;
export type FieldUUidValue = string;
@ -109,5 +115,6 @@ export type FieldCurrencyValue = {
};
export type FieldFullNameValue = { firstName: string; lastName: string };
export type FieldProbabilityValue = number;
export type FieldSelectValue = { color: ThemeColor; text: string };
export type FieldRelationValue = EntityForSelect | null;

View File

@ -11,6 +11,7 @@ import {
FieldPhoneMetadata,
FieldProbabilityMetadata,
FieldRelationMetadata,
FieldSelectMetadata,
FieldTextMetadata,
FieldUuidMetadata,
} from '../FieldMetadata';
@ -28,6 +29,8 @@ type AssertFieldMetadataFunction = <
? FieldDateTimeMetadata
: E extends 'EMAIL'
? FieldEmailMetadata
: E extends 'ENUM'
? FieldSelectMetadata
: E extends 'LINK'
? FieldLinkMetadata
: E extends 'NUMBER'

View File

@ -1,14 +0,0 @@
import { z } from 'zod';
import { mainColors, ThemeColor } from '@/ui/theme/constants/colors';
const enumColors = Object.keys(mainColors) as [ThemeColor, ...ThemeColor[]];
const enumValueSchema = z.object({
color: z.enum(enumColors),
text: z.string(),
});
export const isFieldEnumValue = (
fieldValue: unknown,
): fieldValue is z.infer<typeof enumValueSchema> =>
enumValueSchema.safeParse(fieldValue).success;

View File

@ -0,0 +1,6 @@
import { FieldDefinition } from '../FieldDefinition';
import { FieldMetadata, FieldSelectMetadata } from '../FieldMetadata';
export const isFieldSelect = (
field: FieldDefinition<FieldMetadata>,
): field is FieldDefinition<FieldSelectMetadata> => field.type === 'ENUM';

View File

@ -0,0 +1,14 @@
import { z } from 'zod';
import { mainColors, ThemeColor } from '@/ui/theme/constants/colors';
const selectColors = Object.keys(mainColors) as [ThemeColor, ...ThemeColor[]];
const selectValueSchema = z.object({
color: z.enum(selectColors),
text: z.string(),
});
export const isFieldSelectValue = (
fieldValue: unknown,
): fieldValue is z.infer<typeof selectValueSchema> =>
selectValueSchema.safeParse(fieldValue).success;

View File

@ -172,6 +172,7 @@ export const SettingsObjectFieldEdit = () => {
values={{
type: formValues.type,
relation: formValues.relation,
select: formValues.select,
}}
/>
<Section>

View File

@ -223,6 +223,7 @@ export const SettingsObjectNewFieldStep2 = () => {
values={{
type: formValues.type,
relation: formValues.relation,
select: formValues.select,
}}
/>
</SettingsPageContainer>