From 34a3197fee731092dbed9683cd0dbdb94c672330 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tha=C3=AFs?= Date: Wed, 29 Nov 2023 12:19:56 +0100 Subject: [PATCH] feat: set Select field option as default option (#2725) Closes #2591 --- .../SettingsObjectFieldRelationForm.tsx | 24 +++++------ .../SettingsObjectFieldSelectForm.tsx | 38 +++++++++------- ...SettingsObjectFieldSelectFormOptionRow.tsx | 43 ++++++++++++++----- .../SettingsObjectFieldTypeSelectSection.tsx | 14 +++--- .../data-model/hooks/useFieldMetadataForm.ts | 9 ++-- .../data-model/hooks/useFieldPreview.ts | 29 ++++++------- .../SettingsObjectFieldSelectFormOption.ts | 8 ++++ .../modules/ui/input/components/TextInput.tsx | 10 ++++- .../display/components/SelectFieldDisplay.tsx | 2 +- .../field/meta-types/hooks/useSelectField.ts | 6 +-- .../ui/object/field/types/FieldMetadata.ts | 2 +- .../field/types/guards/isFieldSelectValue.ts | 2 +- .../data-model/SettingsObjectFieldEdit.tsx | 11 +++-- .../SettingsObjectNewFieldStep2.tsx | 3 +- 14 files changed, 127 insertions(+), 74 deletions(-) create mode 100644 front/src/modules/settings/data-model/types/SettingsObjectFieldSelectFormOption.ts diff --git a/front/src/modules/settings/data-model/components/SettingsObjectFieldRelationForm.tsx b/front/src/modules/settings/data-model/components/SettingsObjectFieldRelationForm.tsx index 3401c6d1d..9f9e66f5b 100644 --- a/front/src/modules/settings/data-model/components/SettingsObjectFieldRelationForm.tsx +++ b/front/src/modules/settings/data-model/components/SettingsObjectFieldRelationForm.tsx @@ -11,17 +11,17 @@ import { Field } from '~/generated-metadata/graphql'; import { relationTypes } from '../constants/relationTypes'; import { RelationType } from '../types/RelationType'; -export type SettingsObjectFieldRelationFormValues = Partial<{ - field: Partial>; +export type SettingsObjectFieldRelationFormValues = { + field: Pick; objectMetadataId: string; type: RelationType; -}>; +}; type SettingsObjectFieldRelationFormProps = { disableFieldEdition?: boolean; disableRelationEdition?: boolean; - onChange: (values: SettingsObjectFieldRelationFormValues) => void; - values?: SettingsObjectFieldRelationFormValues; + onChange: (values: Partial) => void; + values: SettingsObjectFieldRelationFormValues; }; const StyledContainer = styled.div` @@ -61,7 +61,7 @@ export const SettingsObjectFieldRelationForm = ({ useObjectMetadataItemForSettings(); const selectedObjectMetadataItem = - (values?.objectMetadataId + (values.objectMetadataId ? findObjectMetadataItemById(values.objectMetadataId) : undefined) || objectMetadataItems[0]; @@ -72,7 +72,7 @@ export const SettingsObjectFieldRelationForm = ({ label="Relation type" dropdownScopeId="relation-type-select" disabled={disableRelationEdition} - value={values?.type} + value={values.type} options={Object.entries(relationTypes).map( ([value, { label, Icon }]) => ({ label, @@ -86,7 +86,7 @@ export const SettingsObjectFieldRelationForm = ({ label="Object destination" dropdownScopeId="object-destination-select" disabled={disableRelationEdition} - value={values?.objectMetadataId} + value={values.objectMetadataId} options={objectMetadataItems.map((objectMetadataItem) => ({ label: objectMetadataItem.labelPlural, value: objectMetadataItem.id, @@ -104,10 +104,10 @@ export const SettingsObjectFieldRelationForm = ({ onChange({ - field: { ...values?.field, icon: value.iconKey }, + field: { ...values.field, icon: value.iconKey }, }) } variant="primary" @@ -115,11 +115,11 @@ export const SettingsObjectFieldRelationForm = ({ { if (!value || validateMetadataLabel(value)) { onChange({ - field: { ...values?.field, label: value }, + field: { ...values.field, label: value }, }); } }} diff --git a/front/src/modules/settings/data-model/components/SettingsObjectFieldSelectForm.tsx b/front/src/modules/settings/data-model/components/SettingsObjectFieldSelectForm.tsx index bb8fd477a..d5288845f 100644 --- a/front/src/modules/settings/data-model/components/SettingsObjectFieldSelectForm.tsx +++ b/front/src/modules/settings/data-model/components/SettingsObjectFieldSelectForm.tsx @@ -4,17 +4,16 @@ import { IconPlus } from '@/ui/display/icon'; import { Button } from '@/ui/input/button/components/Button'; import { mainColors, ThemeColor } from '@/ui/theme/constants/colors'; -import { - SettingsObjectFieldSelectFormOption, - SettingsObjectFieldSelectFormOptionRow, -} from './SettingsObjectFieldSelectFormOptionRow'; +import { SettingsObjectFieldSelectFormOption } from '../types/SettingsObjectFieldSelectFormOption'; + +import { SettingsObjectFieldSelectFormOptionRow } from './SettingsObjectFieldSelectFormOptionRow'; export type SettingsObjectFieldSelectFormValues = SettingsObjectFieldSelectFormOption[]; type SettingsObjectFieldSelectFormProps = { onChange: (values: SettingsObjectFieldSelectFormValues) => void; - values?: SettingsObjectFieldSelectFormValues; + values: SettingsObjectFieldSelectFormValues; }; const StyledContainer = styled.div` @@ -54,31 +53,38 @@ const getNextColor = (currentColor: ThemeColor) => { export const SettingsObjectFieldSelectForm = ({ onChange, - values = [], + values, }: SettingsObjectFieldSelectFormProps) => { return ( <> Options - {values.map((value, index) => ( + {values.map((option, index) => ( { - const nextValues = [...values]; - nextValues.splice(index, 1, optionValue); - onChange(nextValues); + 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); }} onRemove={ values.length > 1 ? () => { - const nextValues = [...values]; - nextValues.splice(index, 1); - onChange(nextValues); + const nextOptions = [...values]; + nextOptions.splice(index, 1); + onChange(nextOptions); } : undefined } - value={value} + option={option} /> ))} @@ -92,7 +98,7 @@ export const SettingsObjectFieldSelectForm = ({ ...values, { color: getNextColor(values[values.length - 1].color), - text: `Option ${values.length + 1}`, + label: `Option ${values.length + 1}`, }, ]) } diff --git a/front/src/modules/settings/data-model/components/SettingsObjectFieldSelectFormOptionRow.tsx b/front/src/modules/settings/data-model/components/SettingsObjectFieldSelectFormOptionRow.tsx index c3207ed15..d6cff6640 100644 --- a/front/src/modules/settings/data-model/components/SettingsObjectFieldSelectFormOptionRow.tsx +++ b/front/src/modules/settings/data-model/components/SettingsObjectFieldSelectFormOptionRow.tsx @@ -2,7 +2,12 @@ import { useMemo } from 'react'; import styled from '@emotion/styled'; import { v4 } from 'uuid'; -import { IconDotsVertical, IconTrash } from '@/ui/display/icon'; +import { + IconCheck, + IconDotsVertical, + IconTrash, + IconX, +} from '@/ui/display/icon'; import { LightIconButton } from '@/ui/input/button/components/LightIconButton'; import { TextInput } from '@/ui/input/components/TextInput'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; @@ -11,17 +16,14 @@ import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/Drop import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope'; import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem'; -import { ThemeColor } from '@/ui/theme/constants/colors'; -export type SettingsObjectFieldSelectFormOption = { - color: ThemeColor; - text: string; -}; +import { SettingsObjectFieldSelectFormOption } from '../types/SettingsObjectFieldSelectFormOption'; type SettingsObjectFieldSelectFormOptionRowProps = { + isDefault?: boolean; onChange: (value: SettingsObjectFieldSelectFormOption) => void; onRemove?: () => void; - value: SettingsObjectFieldSelectFormOption; + option: SettingsObjectFieldSelectFormOption; }; const StyledRow = styled.div` @@ -41,9 +43,10 @@ const StyledOptionInput = styled(TextInput)` `; export const SettingsObjectFieldSelectFormOptionRow = ({ + isDefault, onChange, onRemove, - value, + option, }: SettingsObjectFieldSelectFormOptionRowProps) => { const dropdownScopeId = useMemo(() => `select-field-option-row-${v4()}`, []); @@ -52,8 +55,9 @@ export const SettingsObjectFieldSelectFormOptionRow = ({ return ( onChange({ ...value, text })} + value={option.label} + onChange={(label) => onChange({ ...option, label })} + RightIcon={isDefault ? IconCheck : undefined} /> + {isDefault ? ( + { + onChange({ ...option, isDefault: false }); + closeDropdown(); + }} + /> + ) : ( + { + onChange({ ...option, isDefault: true }); + closeDropdown(); + }} + /> + )} {!!onRemove && ( ; +}; type SettingsObjectFieldTypeSelectSectionProps = { excludedFieldTypes?: FieldMetadataType[]; fieldMetadata: Pick & { id?: string }; - onChange: (values: SettingsObjectFieldTypeSelectSectionFormValues) => void; + onChange: ( + values: Partial, + ) => void; relationFieldMetadata?: Pick; - values?: SettingsObjectFieldTypeSelectSectionFormValues; + values: SettingsObjectFieldTypeSelectSectionFormValues; } & Pick; const StyledSettingsObjectFieldTypeCard = styled(SettingsObjectFieldTypeCard)` @@ -58,8 +60,8 @@ export const SettingsObjectFieldTypeSelectSection = ({ relationFieldMetadata, values, }: SettingsObjectFieldTypeSelectSectionProps) => { - const relationFormConfig = values?.relation; - const selectFormConfig = values?.select; + const relationFormConfig = values.relation; + const selectFormConfig = values.select; const fieldTypeOptions = Object.entries(settingsFieldMetadataTypes) .filter(([key]) => !excludedFieldTypes?.includes(key as FieldMetadataType)) diff --git a/front/src/modules/settings/data-model/hooks/useFieldMetadataForm.ts b/front/src/modules/settings/data-model/hooks/useFieldMetadataForm.ts index c64e61a89..85b630cec 100644 --- a/front/src/modules/settings/data-model/hooks/useFieldMetadataForm.ts +++ b/front/src/modules/settings/data-model/hooks/useFieldMetadataForm.ts @@ -26,8 +26,10 @@ const defaultValues: FormValues = { type: FieldMetadataType.Text, relation: { type: RelationMetadataType.OneToMany, + objectMetadataId: '', + field: { label: '' }, }, - select: [{ color: 'green', text: 'Option 1' }], + select: [{ color: 'green', label: 'Option 1' }], }; const fieldSchema = z.object({ @@ -60,7 +62,8 @@ const selectSchema = fieldSchema.merge( color: z.enum( Object.keys(mainColors) as [ThemeColor, ...ThemeColor[]], ), - text: z.string().min(1), + isDefault: z.boolean().optional(), + label: z.string().min(1), }), ) .nonempty(), @@ -90,7 +93,7 @@ const schema = z.discriminatedUnion('type', [ otherFieldTypesSchema, ]); -type PartialFormValues = Partial & +type PartialFormValues = Partial> & DeepPartial>; export const useFieldMetadataForm = () => { diff --git a/front/src/modules/settings/data-model/hooks/useFieldPreview.ts b/front/src/modules/settings/data-model/hooks/useFieldPreview.ts index c8ad82f75..9ea639d3c 100644 --- a/front/src/modules/settings/data-model/hooks/useFieldPreview.ts +++ b/front/src/modules/settings/data-model/hooks/useFieldPreview.ts @@ -2,8 +2,8 @@ import { useObjectMetadataItemForSettings } from '@/object-metadata/hooks/useObj import { useLazyLoadIcon } from '@/ui/input/hooks/useLazyLoadIcon'; import { Field, FieldMetadataType } from '~/generated-metadata/graphql'; -import { SettingsObjectFieldSelectFormValues } from '../components/SettingsObjectFieldSelectForm'; import { settingsFieldMetadataTypes } from '../constants/settingsFieldMetadataTypes'; +import { SettingsObjectFieldSelectFormOption } from '../types/SettingsObjectFieldSelectFormOption'; import { useFieldPreviewValue } from './useFieldPreviewValue'; import { useRelationFieldPreviewValue } from './useRelationFieldPreviewValue'; @@ -17,7 +17,7 @@ export const useFieldPreview = ({ fieldMetadata: Pick & { id?: string }; objectMetadataId: string; relationObjectMetadataId?: string; - selectOptions?: SettingsObjectFieldSelectFormValues; + selectOptions?: SettingsObjectFieldSelectFormOption[]; }) => { const { findObjectMetadataItemById } = useObjectMetadataItemForSettings(); const objectMetadataItem = findObjectMetadataItemById(objectMetadataId); @@ -44,17 +44,16 @@ export const useFieldPreview = ({ skip: fieldMetadata.type !== FieldMetadataType.Relation, }); - const defaultValue = - fieldMetadata.type === FieldMetadataType.Enum - ? selectOptions?.[0] - : settingsFieldMetadataTypes[fieldMetadata.type].defaultValue; + const { defaultValue } = settingsFieldMetadataTypes[fieldMetadata.type]; - const isValidSelectValue = + const defaultSelectValue = selectOptions?.[0]; + const selectValue = fieldMetadata.type === FieldMetadataType.Enum && - !!firstRecordFieldValue && - selectOptions?.some( - (selectOption) => selectOption.text === firstRecordFieldValue, - ); + typeof firstRecordFieldValue === 'string' + ? selectOptions?.find( + (selectOption) => selectOption.value === firstRecordFieldValue, + ) + : undefined; return { entityId: `${objectMetadataId}-field-form`, @@ -64,10 +63,10 @@ export const useFieldPreview = ({ objectMetadataItem, relationObjectMetadataItem, value: - (fieldMetadata.type === FieldMetadataType.Relation + fieldMetadata.type === FieldMetadataType.Relation ? relationValue - : fieldMetadata.type !== FieldMetadataType.Enum || isValidSelectValue - ? firstRecordFieldValue - : undefined) || defaultValue, + : fieldMetadata.type === FieldMetadataType.Enum + ? selectValue || defaultSelectValue + : firstRecordFieldValue || defaultValue, }; }; diff --git a/front/src/modules/settings/data-model/types/SettingsObjectFieldSelectFormOption.ts b/front/src/modules/settings/data-model/types/SettingsObjectFieldSelectFormOption.ts new file mode 100644 index 000000000..bce7e2e7f --- /dev/null +++ b/front/src/modules/settings/data-model/types/SettingsObjectFieldSelectFormOption.ts @@ -0,0 +1,8 @@ +import { ThemeColor } from '@/ui/theme/constants/colors'; + +export type SettingsObjectFieldSelectFormOption = { + color: ThemeColor; + isDefault?: boolean; + label: string; + value?: string; +}; diff --git a/front/src/modules/ui/input/components/TextInput.tsx b/front/src/modules/ui/input/components/TextInput.tsx index 23a01f2bd..209abc8a9 100644 --- a/front/src/modules/ui/input/components/TextInput.tsx +++ b/front/src/modules/ui/input/components/TextInput.tsx @@ -13,6 +13,7 @@ import { Key } from 'ts-key-enum'; import { IconAlertCircle } from '@/ui/display/icon'; import { IconEye, IconEyeOff } from '@/ui/display/icon/index'; +import { IconComponent } from '@/ui/display/icon/types/IconComponent'; import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { useCombinedRefs } from '~/hooks/useCombinedRefs'; @@ -29,6 +30,7 @@ export type TextInputComponentProps = Omit< fullWidth?: boolean; disableHotkeys?: boolean; error?: string; + RightIcon?: IconComponent; }; const StyledContainer = styled.div>` @@ -101,7 +103,7 @@ const StyledTrailingIconContainer = styled.div` const StyledTrailingIcon = styled.div` align-items: center; color: ${({ theme }) => theme.font.color.light}; - cursor: pointer; + cursor: ${({ onClick }) => (onClick ? 'pointer' : 'default')}; display: flex; justify-content: center; `; @@ -125,6 +127,7 @@ const TextInputComponent = ( placeholder, disabled, tabIndex, + RightIcon, }: TextInputComponentProps, // eslint-disable-next-line twenty/component-props-naming ref: ForwardedRef, @@ -201,6 +204,11 @@ const TextInputComponent = ( )} )} + {!error && type !== INPUT_TYPE_PASSWORD && !!RightIcon && ( + + + + )} {error && {error}} diff --git a/front/src/modules/ui/object/field/meta-types/display/components/SelectFieldDisplay.tsx b/front/src/modules/ui/object/field/meta-types/display/components/SelectFieldDisplay.tsx index 6e81e9f1b..a355daf7b 100644 --- a/front/src/modules/ui/object/field/meta-types/display/components/SelectFieldDisplay.tsx +++ b/front/src/modules/ui/object/field/meta-types/display/components/SelectFieldDisplay.tsx @@ -5,5 +5,5 @@ import { useSelectField } from '../../hooks/useSelectField'; export const SelectFieldDisplay = () => { const { fieldValue } = useSelectField(); - return ; + return ; }; diff --git a/front/src/modules/ui/object/field/meta-types/hooks/useSelectField.ts b/front/src/modules/ui/object/field/meta-types/hooks/useSelectField.ts index a06a33244..edfe9daee 100644 --- a/front/src/modules/ui/object/field/meta-types/hooks/useSelectField.ts +++ b/front/src/modules/ui/object/field/meta-types/hooks/useSelectField.ts @@ -26,15 +26,15 @@ export const useSelectField = () => { ); const fieldSelectValue = isFieldSelectValue(fieldValue) ? fieldValue - : { color: 'green' as ThemeColor, text: '' }; + : { color: 'green' as ThemeColor, label: '' }; const fieldInitialValue = useFieldInitialValue(); const initialValue = { color: 'green' as ThemeColor, - text: fieldInitialValue?.isEmpty + label: fieldInitialValue?.isEmpty ? '' - : fieldInitialValue?.value ?? fieldSelectValue?.text ?? '', + : fieldInitialValue?.value ?? fieldSelectValue?.label ?? '', }; return { diff --git a/front/src/modules/ui/object/field/types/FieldMetadata.ts b/front/src/modules/ui/object/field/types/FieldMetadata.ts index 7d9345a93..bb8a2fe86 100644 --- a/front/src/modules/ui/object/field/types/FieldMetadata.ts +++ b/front/src/modules/ui/object/field/types/FieldMetadata.ts @@ -116,6 +116,6 @@ export type FieldCurrencyValue = { }; export type FieldFullNameValue = { firstName: string; lastName: string }; export type FieldProbabilityValue = number; -export type FieldSelectValue = { color: ThemeColor; text: string }; +export type FieldSelectValue = { color: ThemeColor; label: string }; export type FieldRelationValue = EntityForSelect | null; diff --git a/front/src/modules/ui/object/field/types/guards/isFieldSelectValue.ts b/front/src/modules/ui/object/field/types/guards/isFieldSelectValue.ts index 8f333b093..5abc60965 100644 --- a/front/src/modules/ui/object/field/types/guards/isFieldSelectValue.ts +++ b/front/src/modules/ui/object/field/types/guards/isFieldSelectValue.ts @@ -5,7 +5,7 @@ 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(), + label: z.string(), }); export const isFieldSelectValue = ( diff --git a/front/src/pages/settings/data-model/SettingsObjectFieldEdit.tsx b/front/src/pages/settings/data-model/SettingsObjectFieldEdit.tsx index fece251da..930e91fc3 100644 --- a/front/src/pages/settings/data-model/SettingsObjectFieldEdit.tsx +++ b/front/src/pages/settings/data-model/SettingsObjectFieldEdit.tsx @@ -19,7 +19,10 @@ 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 { FieldMetadataType } from '~/generated-metadata/graphql'; +import { + FieldMetadataType, + RelationMetadataType, +} from '~/generated-metadata/graphql'; export const SettingsObjectFieldEdit = () => { const navigate = useNavigate(); @@ -70,10 +73,10 @@ export const SettingsObjectFieldEdit = () => { relation: { field: { icon: relationFieldMetadataItem?.icon, - label: relationFieldMetadataItem?.label, + label: relationFieldMetadataItem?.label || '', }, - objectMetadataId: relationObjectMetadataItem?.id, - type: relationType, + objectMetadataId: relationObjectMetadataItem?.id || '', + type: relationType || RelationMetadataType.OneToMany, }, }); }, [ diff --git a/front/src/pages/settings/data-model/SettingsObjectNewField/SettingsObjectNewFieldStep2.tsx b/front/src/pages/settings/data-model/SettingsObjectNewField/SettingsObjectNewFieldStep2.tsx index 91bb47fdc..e4e26078e 100644 --- a/front/src/pages/settings/data-model/SettingsObjectNewField/SettingsObjectNewFieldStep2.tsx +++ b/front/src/pages/settings/data-model/SettingsObjectNewField/SettingsObjectNewFieldStep2.tsx @@ -54,7 +54,8 @@ export const SettingsObjectNewFieldStep2 = () => { initForm({ relation: { field: { icon: activeObjectMetadataItem.icon }, - objectMetadataId: findObjectMetadataItemByNamePlural('people')?.id, + objectMetadataId: + findObjectMetadataItemByNamePlural('people')?.id || '', }, }); }, [