4778 multi select field front implement multi select type (#4887)

This commit is contained in:
martmull
2024-04-11 12:57:08 +02:00
committed by GitHub
parent aecf8783a0
commit a7fcc5d47e
42 changed files with 698 additions and 254 deletions

View File

@ -24,6 +24,7 @@ export type SettingsObjectFieldSelectFormValues =
type SettingsObjectFieldSelectFormProps = {
onChange: (values: SettingsObjectFieldSelectFormValues) => void;
values: SettingsObjectFieldSelectFormValues;
isMultiSelect?: boolean;
};
const StyledContainer = styled(CardContent)`
@ -60,6 +61,7 @@ const getNextColor = (currentColor: ThemeColor) => {
export const SettingsObjectFieldSelectForm = ({
onChange,
values,
isMultiSelect = false,
}: SettingsObjectFieldSelectFormProps) => {
const handleDragEnd = (result: DropResult) => {
if (!result.destination) return;
@ -72,6 +74,38 @@ export const SettingsObjectFieldSelectForm = ({
onChange(nextOptions);
};
const handleDefaultValueChange = (
index: number,
option: SettingsObjectFieldSelectFormOption,
nextOption: SettingsObjectFieldSelectFormOption,
forceUniqueDefaultValue: boolean,
) => {
const computeUniqueDefaultValue =
forceUniqueDefaultValue && option.isDefault !== nextOption.isDefault;
const nextOptions = computeUniqueDefaultValue
? values.map((value) => ({
...value,
isDefault: false,
}))
: [...values];
nextOptions.splice(index, 1, nextOption);
onChange(nextOptions);
};
const findNewLabel = () => {
let optionIndex = values.length + 1;
while (optionIndex < 100) {
const newLabel = `Option ${optionIndex}`;
if (!values.map((value) => value.label).includes(newLabel)) {
return newLabel;
}
optionIndex += 1;
}
return `Option 100`;
};
return (
<>
<StyledContainer>
@ -91,18 +125,12 @@ export const SettingsObjectFieldSelectForm = ({
key={option.value}
isDefault={option.isDefault}
onChange={(nextOption) => {
const hasDefaultOptionChanged =
!option.isDefault && nextOption.isDefault;
const nextOptions = hasDefaultOptionChanged
? values.map((value) => ({
...value,
isDefault: false,
}))
: [...values];
nextOptions.splice(index, 1, nextOption);
onChange(nextOptions);
handleDefaultValueChange(
index,
option,
nextOption,
!isMultiSelect,
);
}}
onRemove={
values.length > 1
@ -131,7 +159,7 @@ export const SettingsObjectFieldSelectForm = ({
...values,
{
color: getNextColor(values[values.length - 1].color),
label: `Option ${values.length + 1}`,
label: findNewLabel(),
value: v4(),
},
])

View File

@ -11,6 +11,7 @@ import {
IconPhone,
IconRelationManyToMany,
IconTag,
IconTags,
IconTextSize,
IconUser,
} from 'twenty-ui';
@ -75,8 +76,8 @@ export const SETTINGS_FIELD_TYPE_CONFIGS: Record<
Icon: IconTag,
},
[FieldMetadataType.MultiSelect]: {
label: 'MultiSelect',
Icon: IconTag,
label: 'Multi-select',
Icon: IconTags,
},
[FieldMetadataType.Currency]: {
label: 'Currency',

View File

@ -27,6 +27,7 @@ export type SettingsDataModelFieldSettingsFormValues = {
currency: SettingsObjectFieldCurrencyFormValues;
relation: SettingsObjectFieldRelationFormValues;
select: SettingsObjectFieldSelectFormValues;
multiSelect: SettingsObjectFieldSelectFormValues;
defaultValue: any;
};
@ -63,6 +64,7 @@ const previewableTypes = [
FieldMetadataType.Currency,
FieldMetadataType.DateTime,
FieldMetadataType.Select,
FieldMetadataType.MultiSelect,
FieldMetadataType.Link,
FieldMetadataType.Number,
FieldMetadataType.Rating,
@ -98,7 +100,11 @@ export const SettingsDataModelFieldSettingsFormCard = ({
shrink={fieldMetadataItem.type === FieldMetadataType.Relation}
objectMetadataItem={objectMetadataItem}
relationObjectMetadataItem={relationObjectMetadataItem}
selectOptions={values.select}
selectOptions={
fieldMetadataItem.type === FieldMetadataType.MultiSelect
? values.multiSelect
: values.select
}
/>
{fieldMetadataItem.type === FieldMetadataType.Relation &&
!!relationObjectMetadataItem && (
@ -155,6 +161,14 @@ export const SettingsDataModelFieldSettingsFormCard = ({
onChange({ select: nextSelectValues })
}
/>
) : fieldMetadataItem.type === FieldMetadataType.MultiSelect ? (
<SettingsObjectFieldSelectForm
values={values.multiSelect}
onChange={(nextMultiSelectValues) =>
onChange({ multiSelect: nextMultiSelectValues })
}
isMultiSelect={true}
/>
) : fieldMetadataItem.type === FieldMetadataType.Boolean ? (
<SettingsDataModelDefaultValueForm
value={values.defaultValue}

View File

@ -26,6 +26,7 @@ const defaultValues = {
currency: fieldMetadataFormDefaultValues.currency,
relation: fieldMetadataFormDefaultValues.relation,
select: fieldMetadataFormDefaultValues.select,
multiSelect: fieldMetadataFormDefaultValues.multiSelect,
defaultValue: fieldMetadataFormDefaultValues.defaultValue,
};

View File

@ -27,6 +27,9 @@ describe('useFieldMetadataForm', () => {
select: [
{ color: 'green', label: 'Option 1', value: expect.any(String) },
],
multiSelect: [
{ color: 'green', label: 'Option 1', value: expect.any(String) },
],
});
});

View File

@ -34,13 +34,19 @@ export const fieldMetadataFormDefaultValues: FormValues = {
},
defaultValue: null,
select: [{ color: 'green', label: 'Option 1', value: v4() }],
multiSelect: [{ color: 'green', label: 'Option 1', value: v4() }],
};
const fieldSchema = z.object({
description: z.string().optional(),
icon: z.string().startsWith('Icon'),
label: z.string().min(1),
defaultValue: z.any(),
type: z.enum(
Object.values(FieldMetadataType) as [
FieldMetadataType,
...FieldMetadataType[],
],
),
});
const currencySchema = fieldSchema.merge(
@ -83,10 +89,27 @@ const selectSchema = fieldSchema.merge(
}),
);
const multiSelectSchema = fieldSchema.merge(
z.object({
type: z.literal(FieldMetadataType.MultiSelect),
multiSelect: z
.array(
z.object({
color: themeColorSchema,
id: z.string().optional(),
isDefault: z.boolean().optional(),
label: z.string().min(1),
}),
)
.nonempty(),
}),
);
const {
Currency: _Currency,
Relation: _Relation,
Select: _Select,
MultiSelect: _MultiSelect,
...otherFieldTypes
} = FieldMetadataType;
@ -95,6 +118,7 @@ type OtherFieldType = Exclude<
| FieldMetadataType.Currency
| FieldMetadataType.Relation
| FieldMetadataType.Select
| FieldMetadataType.MultiSelect
>;
const otherFieldTypesSchema = fieldSchema.merge(
@ -109,6 +133,7 @@ const schema = z.discriminatedUnion('type', [
currencySchema,
relationSchema,
selectSchema,
multiSelectSchema,
otherFieldTypesSchema,
]);
@ -127,6 +152,8 @@ export const useFieldMetadataForm = () => {
const [hasCurrencyFormChanged, setHasCurrencyFormChanged] = useState(false);
const [hasRelationFormChanged, setHasRelationFormChanged] = useState(false);
const [hasSelectFormChanged, setHasSelectFormChanged] = useState(false);
const [hasMultiSelectFormChanged, setHasMultiSelectFormChanged] =
useState(false);
const [hasDefaultValueChanged, setHasDefaultValueFormChanged] =
useState(false);
const [validationResult, setValidationResult] = useState(
@ -174,13 +201,15 @@ export const useFieldMetadataForm = () => {
currency: initialCurrencyFormValues,
relation: initialRelationFormValues,
select: initialSelectFormValues,
defaultValue: initalDefaultValue,
multiSelect: initialMultiSelectFormValues,
defaultValue: initialDefaultValue,
...initialFieldFormValues
} = initialFormValues;
const {
currency: nextCurrencyFormValues,
relation: nextRelationFormValues,
select: nextSelectFormValues,
multiSelect: nextMultiSelectFormValues,
defaultValue: nextDefaultValue,
...nextFieldFormValues
} = nextFormValues;
@ -200,9 +229,13 @@ export const useFieldMetadataForm = () => {
nextFieldFormValues.type === FieldMetadataType.Select &&
!isDeeplyEqual(initialSelectFormValues, nextSelectFormValues),
);
setHasMultiSelectFormChanged(
nextFieldFormValues.type === FieldMetadataType.MultiSelect &&
!isDeeplyEqual(initialMultiSelectFormValues, nextMultiSelectFormValues),
);
setHasDefaultValueFormChanged(
nextFieldFormValues.type === FieldMetadataType.Boolean &&
!isDeeplyEqual(initalDefaultValue, nextDefaultValue),
!isDeeplyEqual(initialDefaultValue, nextDefaultValue),
);
};
@ -215,9 +248,11 @@ export const useFieldMetadataForm = () => {
hasCurrencyFormChanged ||
hasRelationFormChanged ||
hasSelectFormChanged ||
hasMultiSelectFormChanged ||
hasDefaultValueChanged,
hasRelationFormChanged,
hasSelectFormChanged,
hasMultiSelectFormChanged,
hasDefaultValueChanged,
initForm,
isInitialized,

View File

@ -21,7 +21,6 @@ export const getFieldDefaultPreviewValue = ({
relationObjectMetadataItem?: ObjectMetadataItem;
selectOptions?: SettingsObjectFieldSelectFormValues;
}) => {
// Select field
if (
fieldMetadataItem.type === FieldMetadataType.Select &&
isDefined(selectOptions)
@ -31,7 +30,13 @@ export const getFieldDefaultPreviewValue = ({
return defaultSelectOption.value;
}
// Relation field
if (
fieldMetadataItem.type === FieldMetadataType.MultiSelect &&
isDefined(selectOptions)
) {
return selectOptions.map((selectOption) => selectOption.value);
}
if (
fieldMetadataItem.type === FieldMetadataType.Relation &&
isDefined(relationObjectMetadataItem)
@ -60,7 +65,6 @@ export const getFieldDefaultPreviewValue = ({
return defaultRelationRecord;
}
// Other fields
const isLabelIdentifier =
!!fieldMetadataItem.id &&
!!fieldMetadataItem.name &&