feat: select default Unit for Currency field (#2996)
Closes #2347 Co-authored-by: Thais GUIGON <thaisguigon@macbook-pro.home>
This commit is contained in:
@ -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,
|
||||
});
|
||||
|
||||
@ -21,7 +21,7 @@ export type FieldMetadataItem = Omit<
|
||||
>;
|
||||
})
|
||||
| null;
|
||||
defaultValue?: string;
|
||||
defaultValue?: unknown;
|
||||
options?: {
|
||||
color: ThemeColor;
|
||||
id: string;
|
||||
|
||||
@ -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)) {
|
||||
|
||||
@ -0,0 +1,10 @@
|
||||
export enum CurrencyCode {
|
||||
CAD = 'CAD',
|
||||
CHF = 'CHF',
|
||||
CNY = 'CNY',
|
||||
EUR = 'EUR',
|
||||
GBP = 'GBP',
|
||||
HKD = 'HKD',
|
||||
JPY = 'JPY',
|
||||
USD = 'USD',
|
||||
}
|
||||
@ -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 };
|
||||
|
||||
@ -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(),
|
||||
});
|
||||
|
||||
|
||||
@ -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>
|
||||
);
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
},
|
||||
};
|
||||
@ -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',
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -37,6 +37,11 @@ export {
|
||||
IconMessageCircle as IconComment,
|
||||
IconCopy,
|
||||
IconCurrencyDollar,
|
||||
IconCurrencyEuro,
|
||||
IconCurrencyFrank,
|
||||
IconCurrencyPound,
|
||||
IconCurrencyYen,
|
||||
IconCurrencyYuan,
|
||||
IconDatabase,
|
||||
IconDeviceFloppy,
|
||||
IconDotsVertical,
|
||||
|
||||
@ -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}>
|
||||
|
||||
@ -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,
|
||||
}}
|
||||
|
||||
@ -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,
|
||||
}}
|
||||
|
||||
Reference in New Issue
Block a user