feat: select default Unit for Currency field (#2996)

Closes #2347

Co-authored-by: Thais GUIGON <thaisguigon@macbook-pro.home>
This commit is contained in:
Thaïs
2023-12-15 11:01:06 +01:00
committed by GitHub
parent 5f7442cf23
commit 1eb5bebaf7
15 changed files with 190 additions and 18 deletions

View File

@ -19,6 +19,7 @@ export const useFieldMetadataItem = () => {
const createMetadataField = (
input: Pick<Field, 'label' | 'icon' | 'description'> & {
defaultValue?: unknown;
objectMetadataId: string;
options?: Omit<FieldMetadataOption, 'id'>[];
type: FieldMetadataType;
@ -26,6 +27,7 @@ export const useFieldMetadataItem = () => {
) =>
createOneFieldMetadataItem({
...formatFieldMetadataItemInput(input),
defaultValue: input.defaultValue,
objectMetadataId: input.objectMetadataId,
type: input.type as FieldType,
});

View File

@ -21,7 +21,7 @@ export type FieldMetadataItem = Omit<
>;
})
| null;
defaultValue?: string;
defaultValue?: unknown;
options?: {
color: ThemeColor;
id: string;

View File

@ -1,6 +1,7 @@
import { useContext } from 'react';
import { useRecoilState } from 'recoil';
import { CurrencyCode } from '@/object-record/field/types/CurrencyCode';
import { FieldInitialValue } from '@/object-record/field/types/FieldInitialValue';
import { canBeCastAsIntegerOrNull } from '~/utils/cast-as-integer-or-null';
import {
@ -22,17 +23,17 @@ const initializeValue = (
fieldValue: FieldCurrencyValue,
) => {
if (fieldInitialValue?.isEmpty) {
return { amount: null, currencyCode: 'USD' };
return { amount: null, currencyCode: CurrencyCode.USD };
}
if (!isNaN(Number(fieldInitialValue?.value))) {
return {
amount: Number(fieldInitialValue?.value),
currencyCode: 'USD',
currencyCode: CurrencyCode.USD,
};
}
if (!fieldValue) {
return { amount: null, currencyCode: 'USD' };
return { amount: null, currencyCode: CurrencyCode.USD };
}
return {
@ -73,7 +74,7 @@ export const useCurrencyField = () => {
amountMicros: isNaN(amount)
? null
: convertCurrencyToCurrencyMicros(amount),
currencyCode: currencyCode,
currencyCode,
};
if (!isFieldCurrencyValue(newCurrencyValue)) {

View File

@ -0,0 +1,10 @@
export enum CurrencyCode {
CAD = 'CAD',
CHF = 'CHF',
CNY = 'CNY',
EUR = 'EUR',
GBP = 'GBP',
HKD = 'HKD',
JPY = 'JPY',
USD = 'USD',
}

View File

@ -1,6 +1,8 @@
import { EntityForSelect } from '@/object-record/relation-picker/types/EntityForSelect';
import { ThemeColor } from '@/ui/theme/constants/colors';
import { CurrencyCode } from './CurrencyCode';
export type FieldUuidMetadata = {
objectMetadataNameSingular?: string;
fieldName: string;
@ -111,7 +113,7 @@ export type FieldPhoneValue = string;
export type FieldEmailValue = string;
export type FieldLinkValue = { url: string; label: string };
export type FieldCurrencyValue = {
currencyCode: string;
currencyCode: CurrencyCode;
amountMicros: number | null;
};
export type FieldFullNameValue = { firstName: string; lastName: string };

View File

@ -1,9 +1,10 @@
import { z } from 'zod';
import { CurrencyCode } from '../CurrencyCode';
import { FieldCurrencyValue } from '../FieldMetadata';
const currencySchema = z.object({
currencyCode: z.string().nullable(),
currencyCode: z.nativeEnum(CurrencyCode).nullable(),
amountMicros: z.number().nullable(),
});

View File

@ -0,0 +1,39 @@
import { CurrencyCode } from '@/object-record/field/types/CurrencyCode';
import { Select } from '@/ui/input/components/Select';
import { CardContent } from '@/ui/layout/card/components/CardContent';
import { settingsFieldCurrencyCodes } from '../constants/settingsFieldCurrencyCodes';
export type SettingsObjectFieldCurrencyFormValues = {
currencyCode: CurrencyCode;
};
type SettingsObjectFieldCurrencyFormProps = {
disabled?: boolean;
onChange: (values: Partial<SettingsObjectFieldCurrencyFormValues>) => void;
values: SettingsObjectFieldCurrencyFormValues;
};
export const SettingsObjectFieldCurrencyForm = ({
disabled,
onChange,
values,
}: SettingsObjectFieldCurrencyFormProps) => (
<CardContent>
<Select
fullWidth
disabled={disabled}
label="Unit"
dropdownScopeId="currency-unit-select"
value={values.currencyCode}
options={Object.entries(settingsFieldCurrencyCodes).map(
([value, { label, Icon }]) => ({
label,
value: value as CurrencyCode,
Icon,
}),
)}
onChange={(value) => onChange({ currencyCode: value })}
/>
</CardContent>
);

View File

@ -8,6 +8,10 @@ import { Field, FieldMetadataType } from '~/generated-metadata/graphql';
import { relationTypes } from '../constants/relationTypes';
import { settingsFieldMetadataTypes } from '../constants/settingsFieldMetadataTypes';
import {
SettingsObjectFieldCurrencyForm,
SettingsObjectFieldCurrencyFormValues,
} from './SettingsObjectFieldCurrencyForm';
import {
SettingsObjectFieldPreview,
SettingsObjectFieldPreviewProps,
@ -24,11 +28,13 @@ import { SettingsObjectFieldTypeCard } from './SettingsObjectFieldTypeCard';
export type SettingsObjectFieldTypeSelectSectionFormValues = {
type: FieldMetadataType;
currency: SettingsObjectFieldCurrencyFormValues;
relation: SettingsObjectFieldRelationFormValues;
select: SettingsObjectFieldSelectFormValues;
};
type SettingsObjectFieldTypeSelectSectionProps = {
disableCurrencyForm?: boolean;
excludedFieldTypes?: FieldMetadataType[];
fieldMetadata: Pick<Field, 'icon' | 'label'> & { id?: string };
onChange: (
@ -53,6 +59,7 @@ const StyledRelationImage = styled.img<{ flip?: boolean }>`
`;
export const SettingsObjectFieldTypeSelectSection = ({
disableCurrencyForm,
excludedFieldTypes,
fieldMetadata,
objectMetadataId,
@ -60,6 +67,7 @@ export const SettingsObjectFieldTypeSelectSection = ({
relationFieldMetadata,
values,
}: SettingsObjectFieldTypeSelectSectionProps) => {
const currencyFormConfig = values.currency;
const relationFormConfig = values.relation;
const selectFormConfig = values.select;
@ -139,7 +147,17 @@ export const SettingsObjectFieldTypeSelectSection = ({
</>
}
form={
values.type === FieldMetadataType.Relation ? (
values.type === FieldMetadataType.Currency ? (
<SettingsObjectFieldCurrencyForm
disabled={disableCurrencyForm}
values={currencyFormConfig}
onChange={(nextValues) =>
onChange({
currency: { ...currencyFormConfig, ...nextValues },
})
}
/>
) : values.type === FieldMetadataType.Relation ? (
<SettingsObjectFieldRelationForm
disableFieldEdition={
relationFieldMetadata && !relationFieldMetadata.isCustom

View File

@ -0,0 +1,48 @@
import { CurrencyCode } from '@/object-record/field/types/CurrencyCode';
import {
IconCurrencyDollar,
IconCurrencyEuro,
IconCurrencyFrank,
IconCurrencyPound,
IconCurrencyYen,
IconCurrencyYuan,
} from '@/ui/display/icon';
import { IconComponent } from '@/ui/display/icon/types/IconComponent';
export const settingsFieldCurrencyCodes: Record<
CurrencyCode,
{ label: string; Icon: IconComponent }
> = {
USD: {
label: 'United States dollar',
Icon: IconCurrencyDollar,
},
EUR: {
label: 'Euro',
Icon: IconCurrencyEuro,
},
JPY: {
label: 'Japanese yen',
Icon: IconCurrencyYen,
},
GBP: {
label: 'British pound',
Icon: IconCurrencyPound,
},
CAD: {
label: 'Canadian dollar',
Icon: IconCurrencyDollar,
},
CHF: {
label: 'Swiss franc',
Icon: IconCurrencyFrank,
},
CNY: {
label: 'Chinese yuan',
Icon: IconCurrencyYuan,
},
HKD: {
label: 'Hong Kong dollar',
Icon: IconCurrencyDollar,
},
};

View File

@ -1,3 +1,4 @@
import { CurrencyCode } from '@/object-record/field/types/CurrencyCode';
import {
IconCalendarEvent,
IconCheck,
@ -72,7 +73,7 @@ export const settingsFieldMetadataTypes: Partial<
[FieldMetadataType.Currency]: {
label: 'Currency',
Icon: IconCoins,
defaultValue: { amountMicros: 2000000000, currencyCode: 'USD' },
defaultValue: { amountMicros: 2000000000, currencyCode: CurrencyCode.USD },
},
[FieldMetadataType.Relation]: {
label: 'Relation',

View File

@ -3,6 +3,7 @@ import { DeepPartial } from 'react-hook-form';
import { v4 } from 'uuid';
import { z } from 'zod';
import { CurrencyCode } from '@/object-record/field/types/CurrencyCode';
import { themeColorSchema } from '@/ui/theme/utils/themeColorSchema';
import {
FieldMetadataType,
@ -16,15 +17,13 @@ type FormValues = {
description?: string;
icon: string;
label: string;
type: FieldMetadataType;
relation: SettingsObjectFieldTypeSelectSectionFormValues['relation'];
select: SettingsObjectFieldTypeSelectSectionFormValues['select'];
};
} & SettingsObjectFieldTypeSelectSectionFormValues;
export const fieldMetadataFormDefaultValues: FormValues = {
icon: 'IconUsers',
label: '',
type: FieldMetadataType.Text,
currency: { currencyCode: CurrencyCode.USD },
relation: {
type: RelationMetadataType.OneToMany,
objectMetadataId: '',
@ -39,6 +38,15 @@ const fieldSchema = z.object({
label: z.string().min(1),
});
const currencySchema = fieldSchema.merge(
z.object({
type: z.literal(FieldMetadataType.Currency),
currency: z.object({
currencyCode: z.nativeEnum(CurrencyCode),
}),
}),
);
const relationSchema = fieldSchema.merge(
z.object({
type: z.literal(FieldMetadataType.Relation),
@ -71,14 +79,17 @@ const selectSchema = fieldSchema.merge(
);
const {
Select: _Select,
Currency: _Currency,
Relation: _Relation,
Select: _Select,
...otherFieldTypes
} = FieldMetadataType;
type OtherFieldType = Exclude<
FieldMetadataType,
FieldMetadataType.Relation | FieldMetadataType.Select
| FieldMetadataType.Currency
| FieldMetadataType.Relation
| FieldMetadataType.Select
>;
const otherFieldTypesSchema = fieldSchema.merge(
@ -90,6 +101,7 @@ const otherFieldTypesSchema = fieldSchema.merge(
);
const schema = z.discriminatedUnion('type', [
currencySchema,
relationSchema,
selectSchema,
otherFieldTypesSchema,
@ -107,6 +119,7 @@ export const useFieldMetadataForm = () => {
fieldMetadataFormDefaultValues,
);
const [hasFieldFormChanged, setHasFieldFormChanged] = useState(false);
const [hasCurrencyFormChanged, setHasCurrencyFormChanged] = useState(false);
const [hasRelationFormChanged, setHasRelationFormChanged] = useState(false);
const [hasSelectFormChanged, setHasSelectFormChanged] = useState(false);
const [validationResult, setValidationResult] = useState(
@ -119,6 +132,7 @@ export const useFieldMetadataForm = () => {
): FormValues => ({
...previousValues,
...nextValues,
currency: { ...previousValues.currency, ...nextValues.currency },
relation: {
...previousValues.relation,
...nextValues.relation,
@ -150,11 +164,13 @@ export const useFieldMetadataForm = () => {
setValidationResult(schema.safeParse(nextFormValues));
const {
currency: initialCurrencyFormValues,
relation: initialRelationFormValues,
select: initialSelectFormValues,
...initialFieldFormValues
} = initialFormValues;
const {
currency: nextCurrencyFormValues,
relation: nextRelationFormValues,
select: nextSelectFormValues,
...nextFieldFormValues
@ -163,6 +179,10 @@ export const useFieldMetadataForm = () => {
setHasFieldFormChanged(
!isDeeplyEqual(initialFieldFormValues, nextFieldFormValues),
);
setHasCurrencyFormChanged(
nextFieldFormValues.type === FieldMetadataType.Currency &&
!isDeeplyEqual(initialCurrencyFormValues, nextCurrencyFormValues),
);
setHasRelationFormChanged(
nextFieldFormValues.type === FieldMetadataType.Relation &&
!isDeeplyEqual(initialRelationFormValues, nextRelationFormValues),
@ -178,7 +198,10 @@ export const useFieldMetadataForm = () => {
handleFormChange,
hasFieldFormChanged,
hasFormChanged:
hasFieldFormChanged || hasRelationFormChanged || hasSelectFormChanged,
hasFieldFormChanged ||
hasCurrencyFormChanged ||
hasRelationFormChanged ||
hasSelectFormChanged,
hasRelationFormChanged,
hasSelectFormChanged,
initForm,

View File

@ -37,6 +37,11 @@ export {
IconMessageCircle as IconComment,
IconCopy,
IconCurrencyDollar,
IconCurrencyEuro,
IconCurrencyFrank,
IconCurrencyPound,
IconCurrencyYen,
IconCurrencyYuan,
IconDatabase,
IconDeviceFloppy,
IconDotsVertical,

View File

@ -93,7 +93,10 @@ export const Select = <Value extends string | number | null>({
);
return disabled ? (
selectControl
<>
{!!label && <StyledLabel>{label}</StyledLabel>}
{selectControl}
</>
) : (
<DropdownScope dropdownScopeId={dropdownScopeId}>
<div className={className}>

View File

@ -8,6 +8,7 @@ import { getFieldSlug } from '@/object-metadata/utils/getFieldSlug';
import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons';
import { SettingsHeaderContainer } from '@/settings/components/SettingsHeaderContainer';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SettingsObjectFieldCurrencyFormValues } from '@/settings/data-model/components/SettingsObjectFieldCurrencyForm';
import { SettingsObjectFieldFormSection } from '@/settings/data-model/components/SettingsObjectFieldFormSection';
import { SettingsObjectFieldTypeSelectSection } from '@/settings/data-model/components/SettingsObjectFieldTypeSelectSection';
import { useFieldMetadataForm } from '@/settings/data-model/hooks/useFieldMetadataForm';
@ -66,9 +67,16 @@ export const SettingsObjectFieldEdit = () => {
return;
}
const { defaultValue } = activeMetadataField;
const currencyDefaultValue =
activeMetadataField.type === FieldMetadataType.Currency
? (defaultValue as SettingsObjectFieldCurrencyFormValues | undefined)
: undefined;
const selectOptions = activeMetadataField.options?.map((option) => ({
...option,
isDefault: activeMetadataField.defaultValue === option.value,
isDefault: defaultValue === option.value,
}));
selectOptions?.sort(
(optionA, optionB) => optionA.position - optionB.position,
@ -79,6 +87,7 @@ export const SettingsObjectFieldEdit = () => {
label: activeMetadataField.label,
description: activeMetadataField.description ?? undefined,
type: activeMetadataField.type,
...(currencyDefaultValue ? { currency: currencyDefaultValue } : {}),
relation: {
field: {
icon: relationFieldMetadataItem?.icon,
@ -178,6 +187,7 @@ export const SettingsObjectFieldEdit = () => {
onChange={handleFormChange}
/>
<SettingsObjectFieldTypeSelectSection
disableCurrencyForm
fieldMetadata={{
icon: formValues.icon,
label: formValues.label || 'Employees',
@ -188,6 +198,7 @@ export const SettingsObjectFieldEdit = () => {
relationFieldMetadata={relationFieldMetadataItem}
values={{
type: formValues.type,
currency: formValues.currency,
relation: formValues.relation,
select: formValues.select,
}}

View File

@ -195,6 +195,13 @@ export const SettingsObjectNewFieldStep2 = () => {
});
} else {
const createdMetadataField = await createMetadataField({
defaultValue:
validatedFormValues.type === FieldMetadataType.Currency
? {
amountMicros: null,
currencyCode: validatedFormValues.currency.currencyCode,
}
: undefined,
description: validatedFormValues.description,
icon: validatedFormValues.icon,
label: validatedFormValues.label ?? '',
@ -294,6 +301,7 @@ export const SettingsObjectNewFieldStep2 = () => {
onChange={handleFormChange}
values={{
type: formValues.type,
currency: formValues.currency,
relation: formValues.relation,
select: formValues.select,
}}