mirror of
https://github.com/twentyhq/twenty.git
synced 2024-12-23 03:51:36 +03:00
Default address country 🗺️ & Phone prefix ☎️ (#8614)
# Default address 🗺️ country & Phone ☎️ country We add the ability to add a Default address country and a default Phone country for fields in the Data model. fix #8081 --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
parent
39a9cd0d51
commit
0527bc296e
@ -2,5 +2,6 @@ import { FieldMetadataType } from '~/generated-metadata/graphql';
|
|||||||
|
|
||||||
export const FIELD_NOT_OVERWRITTEN_AT_DRAFT = [
|
export const FIELD_NOT_OVERWRITTEN_AT_DRAFT = [
|
||||||
FieldMetadataType.Address,
|
FieldMetadataType.Address,
|
||||||
|
FieldMetadataType.Phones,
|
||||||
FieldMetadataType.Links,
|
FieldMetadataType.Links,
|
||||||
];
|
];
|
||||||
|
@ -35,8 +35,8 @@ export const useNumberField = () => {
|
|||||||
|
|
||||||
const persistNumberField = (newValue: string) => {
|
const persistNumberField = (newValue: string) => {
|
||||||
if (fieldDefinition?.metadata?.settings?.type === 'percentage') {
|
if (fieldDefinition?.metadata?.settings?.type === 'percentage') {
|
||||||
newValue = newValue.replaceAll('%', '');
|
const newValueEscaped = newValue.replaceAll('%', '');
|
||||||
if (!canBeCastAsNumberOrNull(newValue)) {
|
if (!canBeCastAsNumberOrNull(newValueEscaped)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const castedValue = castAsNumberOrNull(newValue);
|
const castedValue = castAsNumberOrNull(newValue);
|
||||||
|
@ -111,7 +111,7 @@ export const MultiItemFieldInput = <T,>({
|
|||||||
break;
|
break;
|
||||||
case FieldMetadataType.Phones:
|
case FieldMetadataType.Phones:
|
||||||
item = items[index] as PhoneRecord;
|
item = items[index] as PhoneRecord;
|
||||||
setInputValue(item.countryCode + item.number);
|
setInputValue(`+${item.callingCode}` + item.number);
|
||||||
break;
|
break;
|
||||||
case FieldMetadataType.Emails:
|
case FieldMetadataType.Emails:
|
||||||
item = items[index] as string;
|
item = items[index] as string;
|
||||||
|
@ -5,12 +5,14 @@ import { E164Number, parsePhoneNumber } from 'libphonenumber-js';
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import ReactPhoneNumberInput from 'react-phone-number-input';
|
import ReactPhoneNumberInput from 'react-phone-number-input';
|
||||||
import 'react-phone-number-input/style.css';
|
import 'react-phone-number-input/style.css';
|
||||||
import { isDefined, TEXT_INPUT_STYLE } from 'twenty-ui';
|
import { TEXT_INPUT_STYLE, isDefined } from 'twenty-ui';
|
||||||
|
|
||||||
import { MultiItemFieldInput } from './MultiItemFieldInput';
|
import { MultiItemFieldInput } from './MultiItemFieldInput';
|
||||||
|
|
||||||
|
import { useCountries } from '@/ui/input/components/internal/hooks/useCountries';
|
||||||
import { PhoneCountryPickerDropdownButton } from '@/ui/input/components/internal/phone/components/PhoneCountryPickerDropdownButton';
|
import { PhoneCountryPickerDropdownButton } from '@/ui/input/components/internal/phone/components/PhoneCountryPickerDropdownButton';
|
||||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||||
|
import { stripSimpleQuotesFromString } from '~/utils/string/stripSimpleQuotesFromString';
|
||||||
|
|
||||||
const StyledCustomPhoneInput = styled(ReactPhoneNumberInput)`
|
const StyledCustomPhoneInput = styled(ReactPhoneNumberInput)`
|
||||||
font-family: ${({ theme }) => theme.font.family};
|
font-family: ${({ theme }) => theme.font.family};
|
||||||
@ -48,33 +50,41 @@ type PhonesFieldInputProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const PhonesFieldInput = ({ onCancel }: PhonesFieldInputProps) => {
|
export const PhonesFieldInput = ({ onCancel }: PhonesFieldInputProps) => {
|
||||||
const { persistPhonesField, hotkeyScope, fieldValue } = usePhonesField();
|
const { persistPhonesField, hotkeyScope, draftValue, fieldDefinition } =
|
||||||
|
usePhonesField();
|
||||||
|
|
||||||
const phones = useMemo<{ number: string; countryCode: string }[]>(
|
const phones = useMemo<{ number: string; callingCode: string }[]>(() => {
|
||||||
() =>
|
if (!isDefined(draftValue)) {
|
||||||
[
|
return [];
|
||||||
fieldValue.primaryPhoneNumber
|
}
|
||||||
? {
|
return [
|
||||||
number: fieldValue.primaryPhoneNumber,
|
draftValue.primaryPhoneNumber
|
||||||
countryCode: fieldValue.primaryPhoneCountryCode,
|
? {
|
||||||
}
|
number: draftValue.primaryPhoneNumber,
|
||||||
: null,
|
callingCode: draftValue.primaryPhoneCountryCode,
|
||||||
...(fieldValue.additionalPhones ?? []),
|
}
|
||||||
].filter(isDefined),
|
: null,
|
||||||
[
|
...(draftValue.additionalPhones ?? []),
|
||||||
fieldValue.primaryPhoneNumber,
|
].filter(isDefined);
|
||||||
fieldValue.primaryPhoneCountryCode,
|
}, [draftValue]);
|
||||||
fieldValue.additionalPhones,
|
|
||||||
],
|
const defaultCallingCode =
|
||||||
);
|
stripSimpleQuotesFromString(
|
||||||
|
fieldDefinition?.defaultValue?.primaryPhoneCountryCode,
|
||||||
|
) ?? '+1';
|
||||||
|
|
||||||
|
// TODO : improve once we store the real country code
|
||||||
|
const defaultCountry = useCountries().find(
|
||||||
|
(obj) => obj.callingCode === defaultCallingCode,
|
||||||
|
)?.countryCode;
|
||||||
|
|
||||||
const handlePersistPhones = (
|
const handlePersistPhones = (
|
||||||
updatedPhones: { number: string; countryCode: string }[],
|
updatedPhones: { number: string; callingCode: string }[],
|
||||||
) => {
|
) => {
|
||||||
const [nextPrimaryPhone, ...nextAdditionalPhones] = updatedPhones;
|
const [nextPrimaryPhone, ...nextAdditionalPhones] = updatedPhones;
|
||||||
persistPhonesField({
|
persistPhonesField({
|
||||||
primaryPhoneNumber: nextPrimaryPhone?.number ?? '',
|
primaryPhoneNumber: nextPrimaryPhone?.number ?? '',
|
||||||
primaryPhoneCountryCode: nextPrimaryPhone?.countryCode ?? '',
|
primaryPhoneCountryCode: nextPrimaryPhone?.callingCode ?? '',
|
||||||
additionalPhones: nextAdditionalPhones,
|
additionalPhones: nextAdditionalPhones,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -93,12 +103,12 @@ export const PhonesFieldInput = ({ onCancel }: PhonesFieldInputProps) => {
|
|||||||
if (phone !== undefined) {
|
if (phone !== undefined) {
|
||||||
return {
|
return {
|
||||||
number: phone.nationalNumber,
|
number: phone.nationalNumber,
|
||||||
countryCode: `+${phone.countryCallingCode}`,
|
callingCode: `${phone.countryCallingCode}`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
number: '',
|
number: '',
|
||||||
countryCode: '',
|
callingCode: '',
|
||||||
};
|
};
|
||||||
}}
|
}}
|
||||||
renderItem={({
|
renderItem={({
|
||||||
@ -128,6 +138,7 @@ export const PhonesFieldInput = ({ onCancel }: PhonesFieldInputProps) => {
|
|||||||
international={true}
|
international={true}
|
||||||
withCountryCallingCode={true}
|
withCountryCallingCode={true}
|
||||||
countrySelectComponent={PhoneCountryPickerDropdownButton}
|
countrySelectComponent={PhoneCountryPickerDropdownButton}
|
||||||
|
defaultCountry={defaultCountry}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
|
@ -7,7 +7,7 @@ type PhonesFieldMenuItemProps = {
|
|||||||
onEdit?: () => void;
|
onEdit?: () => void;
|
||||||
onSetAsPrimary?: () => void;
|
onSetAsPrimary?: () => void;
|
||||||
onDelete?: () => void;
|
onDelete?: () => void;
|
||||||
phone: { number: string; countryCode: string };
|
phone: { number: string; callingCode: string };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PhonesFieldMenuItem = ({
|
export const PhonesFieldMenuItem = ({
|
||||||
@ -22,7 +22,7 @@ export const PhonesFieldMenuItem = ({
|
|||||||
<MultiItemFieldMenuItem
|
<MultiItemFieldMenuItem
|
||||||
dropdownId={dropdownId}
|
dropdownId={dropdownId}
|
||||||
isPrimary={isPrimary}
|
isPrimary={isPrimary}
|
||||||
value={phone.countryCode + phone.number}
|
value={{ number: phone.number, callingCode: phone.callingCode }}
|
||||||
onEdit={onEdit}
|
onEdit={onEdit}
|
||||||
onSetAsPrimary={onSetAsPrimary}
|
onSetAsPrimary={onSetAsPrimary}
|
||||||
onDelete={onDelete}
|
onDelete={onDelete}
|
||||||
|
@ -10,13 +10,13 @@ import { CurrencyCode } from './CurrencyCode';
|
|||||||
export type FieldUuidMetadata = {
|
export type FieldUuidMetadata = {
|
||||||
objectMetadataNameSingular?: string;
|
objectMetadataNameSingular?: string;
|
||||||
fieldName: string;
|
fieldName: string;
|
||||||
settings?: Record<string, never>;
|
settings?: null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FieldBooleanMetadata = {
|
export type FieldBooleanMetadata = {
|
||||||
objectMetadataNameSingular?: string;
|
objectMetadataNameSingular?: string;
|
||||||
fieldName: string;
|
fieldName: string;
|
||||||
settings?: Record<string, never>;
|
settings?: null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FieldTextMetadata = {
|
export type FieldTextMetadata = {
|
||||||
@ -61,13 +61,13 @@ export type FieldLinkMetadata = {
|
|||||||
objectMetadataNameSingular?: string;
|
objectMetadataNameSingular?: string;
|
||||||
placeHolder: string;
|
placeHolder: string;
|
||||||
fieldName: string;
|
fieldName: string;
|
||||||
settings?: Record<string, never>;
|
settings?: null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FieldLinksMetadata = {
|
export type FieldLinksMetadata = {
|
||||||
objectMetadataNameSingular?: string;
|
objectMetadataNameSingular?: string;
|
||||||
fieldName: string;
|
fieldName: string;
|
||||||
settings?: Record<string, never>;
|
settings?: null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FieldCurrencyMetadata = {
|
export type FieldCurrencyMetadata = {
|
||||||
@ -75,66 +75,66 @@ export type FieldCurrencyMetadata = {
|
|||||||
fieldName: string;
|
fieldName: string;
|
||||||
placeHolder: string;
|
placeHolder: string;
|
||||||
isPositive?: boolean;
|
isPositive?: boolean;
|
||||||
settings?: Record<string, never>;
|
settings?: null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FieldFullNameMetadata = {
|
export type FieldFullNameMetadata = {
|
||||||
objectMetadataNameSingular?: string;
|
objectMetadataNameSingular?: string;
|
||||||
placeHolder: string;
|
placeHolder: string;
|
||||||
fieldName: string;
|
fieldName: string;
|
||||||
settings?: Record<string, never>;
|
settings?: null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FieldEmailMetadata = {
|
export type FieldEmailMetadata = {
|
||||||
objectMetadataNameSingular?: string;
|
objectMetadataNameSingular?: string;
|
||||||
placeHolder: string;
|
placeHolder: string;
|
||||||
fieldName: string;
|
fieldName: string;
|
||||||
settings?: Record<string, never>;
|
settings?: null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FieldEmailsMetadata = {
|
export type FieldEmailsMetadata = {
|
||||||
objectMetadataNameSingular?: string;
|
objectMetadataNameSingular?: string;
|
||||||
fieldName: string;
|
fieldName: string;
|
||||||
settings?: Record<string, never>;
|
settings?: null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FieldPhoneMetadata = {
|
export type FieldPhoneMetadata = {
|
||||||
objectMetadataNameSingular?: string;
|
objectMetadataNameSingular?: string;
|
||||||
placeHolder: string;
|
placeHolder: string;
|
||||||
fieldName: string;
|
fieldName: string;
|
||||||
settings?: Record<string, never>;
|
settings?: null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FieldRatingMetadata = {
|
export type FieldRatingMetadata = {
|
||||||
objectMetadataNameSingular?: string;
|
objectMetadataNameSingular?: string;
|
||||||
fieldName: string;
|
fieldName: string;
|
||||||
settings?: Record<string, never>;
|
settings?: null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FieldAddressMetadata = {
|
export type FieldAddressMetadata = {
|
||||||
objectMetadataNameSingular?: string;
|
objectMetadataNameSingular?: string;
|
||||||
placeHolder: string;
|
placeHolder: string;
|
||||||
fieldName: string;
|
fieldName: string;
|
||||||
settings?: Record<string, never>;
|
settings?: null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FieldRawJsonMetadata = {
|
export type FieldRawJsonMetadata = {
|
||||||
objectMetadataNameSingular?: string;
|
objectMetadataNameSingular?: string;
|
||||||
fieldName: string;
|
fieldName: string;
|
||||||
placeHolder: string;
|
placeHolder: string;
|
||||||
settings?: Record<string, never>;
|
settings?: null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FieldRichTextMetadata = {
|
export type FieldRichTextMetadata = {
|
||||||
objectMetadataNameSingular?: string;
|
objectMetadataNameSingular?: string;
|
||||||
fieldName: string;
|
fieldName: string;
|
||||||
settings?: Record<string, never>;
|
settings?: null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FieldPositionMetadata = {
|
export type FieldPositionMetadata = {
|
||||||
objectMetadataNameSingular?: string;
|
objectMetadataNameSingular?: string;
|
||||||
fieldName: string;
|
fieldName: string;
|
||||||
settings?: Record<string, never>;
|
settings?: null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FieldRelationMetadata = {
|
export type FieldRelationMetadata = {
|
||||||
@ -146,7 +146,7 @@ export type FieldRelationMetadata = {
|
|||||||
relationType?: RelationDefinitionType;
|
relationType?: RelationDefinitionType;
|
||||||
targetFieldMetadataName?: string;
|
targetFieldMetadataName?: string;
|
||||||
useEditButton?: boolean;
|
useEditButton?: boolean;
|
||||||
settings?: Record<string, never>;
|
settings?: null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FieldSelectMetadata = {
|
export type FieldSelectMetadata = {
|
||||||
@ -154,39 +154,39 @@ export type FieldSelectMetadata = {
|
|||||||
fieldName: string;
|
fieldName: string;
|
||||||
options: { label: string; color: ThemeColor; value: string }[];
|
options: { label: string; color: ThemeColor; value: string }[];
|
||||||
isNullable: boolean;
|
isNullable: boolean;
|
||||||
settings?: Record<string, never>;
|
settings?: null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FieldMultiSelectMetadata = {
|
export type FieldMultiSelectMetadata = {
|
||||||
objectMetadataNameSingular?: string;
|
objectMetadataNameSingular?: string;
|
||||||
fieldName: string;
|
fieldName: string;
|
||||||
options: { label: string; color: ThemeColor; value: string }[];
|
options: { label: string; color: ThemeColor; value: string }[];
|
||||||
settings?: Record<string, never>;
|
settings?: null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FieldActorMetadata = {
|
export type FieldActorMetadata = {
|
||||||
objectMetadataNameSingular?: string;
|
objectMetadataNameSingular?: string;
|
||||||
fieldName: string;
|
fieldName: string;
|
||||||
settings?: Record<string, never>;
|
settings?: null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FieldArrayMetadata = {
|
export type FieldArrayMetadata = {
|
||||||
objectMetadataNameSingular?: string;
|
objectMetadataNameSingular?: string;
|
||||||
fieldName: string;
|
fieldName: string;
|
||||||
values: { label: string; value: string }[];
|
values: { label: string; value: string }[];
|
||||||
settings?: Record<string, never>;
|
settings?: null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FieldPhonesMetadata = {
|
export type FieldPhonesMetadata = {
|
||||||
objectMetadataNameSingular?: string;
|
objectMetadataNameSingular?: string;
|
||||||
fieldName: string;
|
fieldName: string;
|
||||||
settings?: Record<string, never>;
|
settings?: null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FieldTsVectorMetadata = {
|
export type FieldTsVectorMetadata = {
|
||||||
objectMetadataNameSingular?: string;
|
objectMetadataNameSingular?: string;
|
||||||
fieldName: string;
|
fieldName: string;
|
||||||
settings?: Record<string, never>;
|
settings?: null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FieldMetadata =
|
export type FieldMetadata =
|
||||||
@ -265,7 +265,7 @@ export type FieldActorValue = {
|
|||||||
|
|
||||||
export type FieldArrayValue = string[];
|
export type FieldArrayValue = string[];
|
||||||
|
|
||||||
export type PhoneRecord = { number: string; countryCode: string };
|
export type PhoneRecord = { number: string; callingCode: string };
|
||||||
|
|
||||||
export type FieldPhonesValue = {
|
export type FieldPhonesValue = {
|
||||||
primaryPhoneNumber: string;
|
primaryPhoneNumber: string;
|
||||||
|
@ -2,7 +2,7 @@ import { z } from 'zod';
|
|||||||
|
|
||||||
import { FieldAddressValue } from '../FieldMetadata';
|
import { FieldAddressValue } from '../FieldMetadata';
|
||||||
|
|
||||||
const addressSchema = z.object({
|
export const addressSchema = z.object({
|
||||||
addressStreet1: z.string(),
|
addressStreet1: z.string(),
|
||||||
addressStreet2: z.string().nullable(),
|
addressStreet2: z.string().nullable(),
|
||||||
addressCity: z.string().nullable(),
|
addressCity: z.string().nullable(),
|
||||||
|
@ -6,7 +6,7 @@ export const phonesSchema = z.object({
|
|||||||
primaryPhoneNumber: z.string(),
|
primaryPhoneNumber: z.string(),
|
||||||
primaryPhoneCountryCode: z.string(),
|
primaryPhoneCountryCode: z.string(),
|
||||||
additionalPhones: z
|
additionalPhones: z
|
||||||
.array(z.object({ number: z.string(), countryCode: z.string() }))
|
.array(z.object({ number: z.string(), callingCode: z.string() }))
|
||||||
.nullable(),
|
.nullable(),
|
||||||
}) satisfies z.ZodType<FieldPhonesValue>;
|
}) satisfies z.ZodType<FieldPhonesValue>;
|
||||||
|
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition';
|
import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition';
|
||||||
import { FieldInputDraftValue } from '@/object-record/record-field/types/FieldInputDraftValue';
|
import { FieldInputDraftValue } from '@/object-record/record-field/types/FieldInputDraftValue';
|
||||||
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||||
|
import { isFieldAddress } from '@/object-record/record-field/types/guards/isFieldAddress';
|
||||||
import { isFieldCurrency } from '@/object-record/record-field/types/guards/isFieldCurrency';
|
import { isFieldCurrency } from '@/object-record/record-field/types/guards/isFieldCurrency';
|
||||||
import { isFieldCurrencyValue } from '@/object-record/record-field/types/guards/isFieldCurrencyValue';
|
import { isFieldCurrencyValue } from '@/object-record/record-field/types/guards/isFieldCurrencyValue';
|
||||||
import { isFieldNumber } from '@/object-record/record-field/types/guards/isFieldNumber';
|
import { isFieldNumber } from '@/object-record/record-field/types/guards/isFieldNumber';
|
||||||
import { isFieldNumberValue } from '@/object-record/record-field/types/guards/isFieldNumberValue';
|
import { isFieldNumberValue } from '@/object-record/record-field/types/guards/isFieldNumberValue';
|
||||||
|
import { isFieldPhones } from '@/object-record/record-field/types/guards/isFieldPhones';
|
||||||
import { isFieldRawJson } from '@/object-record/record-field/types/guards/isFieldRawJson';
|
import { isFieldRawJson } from '@/object-record/record-field/types/guards/isFieldRawJson';
|
||||||
import { isFieldRawJsonValue } from '@/object-record/record-field/types/guards/isFieldRawJsonValue';
|
import { isFieldRawJsonValue } from '@/object-record/record-field/types/guards/isFieldRawJsonValue';
|
||||||
import { isFieldRelation } from '@/object-record/record-field/types/guards/isFieldRelation';
|
import { isFieldRelation } from '@/object-record/record-field/types/guards/isFieldRelation';
|
||||||
@ -12,6 +14,7 @@ import { computeEmptyDraftValue } from '@/object-record/record-field/utils/compu
|
|||||||
import { isFieldValueEmpty } from '@/object-record/record-field/utils/isFieldValueEmpty';
|
import { isFieldValueEmpty } from '@/object-record/record-field/utils/isFieldValueEmpty';
|
||||||
import { isDefined } from '~/utils/isDefined';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
||||||
|
import { stripSimpleQuotesFromString } from '~/utils/string/stripSimpleQuotesFromString';
|
||||||
|
|
||||||
type computeDraftValueFromFieldValueParams<FieldValue> = {
|
type computeDraftValueFromFieldValueParams<FieldValue> = {
|
||||||
fieldDefinition: Pick<FieldDefinition<FieldMetadata>, 'type' | 'metadata'>;
|
fieldDefinition: Pick<FieldDefinition<FieldMetadata>, 'type' | 'metadata'>;
|
||||||
@ -42,6 +45,38 @@ export const computeDraftValueFromFieldValue = <FieldValue>({
|
|||||||
} as unknown as FieldInputDraftValue<FieldValue>;
|
} as unknown as FieldInputDraftValue<FieldValue>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isFieldAddress(fieldDefinition)) {
|
||||||
|
if (
|
||||||
|
isFieldValueEmpty({ fieldValue, fieldDefinition }) &&
|
||||||
|
!!fieldDefinition?.defaultValue?.addressCountry
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
...fieldValue,
|
||||||
|
addressCountry: stripSimpleQuotesFromString(
|
||||||
|
fieldDefinition?.defaultValue?.addressCountry,
|
||||||
|
),
|
||||||
|
} as unknown as FieldInputDraftValue<FieldValue>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fieldValue as FieldInputDraftValue<FieldValue>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isFieldPhones(fieldDefinition)) {
|
||||||
|
if (
|
||||||
|
isFieldValueEmpty({ fieldValue, fieldDefinition }) &&
|
||||||
|
!!fieldDefinition?.defaultValue?.primaryPhoneCountryCode
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
...fieldValue,
|
||||||
|
primaryPhoneCountryCode: stripSimpleQuotesFromString(
|
||||||
|
fieldDefinition?.defaultValue?.primaryPhoneCountryCode,
|
||||||
|
),
|
||||||
|
} as unknown as FieldInputDraftValue<FieldValue>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fieldValue as FieldInputDraftValue<FieldValue>;
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
isFieldNumber(fieldDefinition) &&
|
isFieldNumber(fieldDefinition) &&
|
||||||
isFieldNumberValue(fieldValue) &&
|
isFieldNumberValue(fieldValue) &&
|
||||||
|
@ -5,17 +5,10 @@ import {
|
|||||||
StyledSettingsOptionCardTitle,
|
StyledSettingsOptionCardTitle,
|
||||||
} from '@/settings/components/SettingsOptions/SettingsOptionCardContentBase';
|
} from '@/settings/components/SettingsOptions/SettingsOptionCardContentBase';
|
||||||
import { SettingsOptionIconCustomizer } from '@/settings/components/SettingsOptions/SettingsOptionIconCustomizer';
|
import { SettingsOptionIconCustomizer } from '@/settings/components/SettingsOptions/SettingsOptionIconCustomizer';
|
||||||
import { Select } from '@/ui/input/components/Select';
|
import { Select, SelectValue } from '@/ui/input/components/Select';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { IconComponent } from 'twenty-ui';
|
import { IconComponent } from 'twenty-ui';
|
||||||
|
|
||||||
const StyledSettingsOptionCardSelect = styled(Select)`
|
|
||||||
margin-left: auto;
|
|
||||||
width: 120px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
type SelectValue = string | number | boolean | null;
|
|
||||||
|
|
||||||
type SettingsOptionCardContentSelectProps<Value extends SelectValue> = {
|
type SettingsOptionCardContentSelectProps<Value extends SelectValue> = {
|
||||||
Icon?: IconComponent;
|
Icon?: IconComponent;
|
||||||
title: React.ReactNode;
|
title: React.ReactNode;
|
||||||
@ -23,7 +16,7 @@ type SettingsOptionCardContentSelectProps<Value extends SelectValue> = {
|
|||||||
divider?: boolean;
|
divider?: boolean;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
value: Value;
|
value: Value;
|
||||||
onChange: (value: SelectValue) => void;
|
onChange: (value: Value) => void;
|
||||||
options: {
|
options: {
|
||||||
value: Value;
|
value: Value;
|
||||||
label: string;
|
label: string;
|
||||||
@ -34,6 +27,10 @@ type SettingsOptionCardContentSelectProps<Value extends SelectValue> = {
|
|||||||
fullWidth?: boolean;
|
fullWidth?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const StyledSelectContainer = styled.div`
|
||||||
|
margin-left: auto;
|
||||||
|
`;
|
||||||
|
|
||||||
export const SettingsOptionCardContentSelect = <Value extends SelectValue>({
|
export const SettingsOptionCardContentSelect = <Value extends SelectValue>({
|
||||||
Icon,
|
Icon,
|
||||||
title,
|
title,
|
||||||
@ -60,16 +57,18 @@ export const SettingsOptionCardContentSelect = <Value extends SelectValue>({
|
|||||||
{description}
|
{description}
|
||||||
</StyledSettingsOptionCardDescription>
|
</StyledSettingsOptionCardDescription>
|
||||||
</div>
|
</div>
|
||||||
<StyledSettingsOptionCardSelect
|
<StyledSelectContainer>
|
||||||
className={selectClassName}
|
<Select<Value>
|
||||||
dropdownWidth={fullWidth ? 'auto' : 120}
|
className={selectClassName}
|
||||||
disabled={disabled}
|
dropdownWidth={fullWidth ? 'auto' : 120}
|
||||||
dropdownId={dropdownId}
|
disabled={disabled}
|
||||||
value={value}
|
dropdownId={dropdownId}
|
||||||
onChange={onChange}
|
value={value}
|
||||||
options={options}
|
onChange={onChange}
|
||||||
selectSizeVariant="small"
|
options={options}
|
||||||
/>
|
selectSizeVariant="small"
|
||||||
|
/>
|
||||||
|
</StyledSelectContainer>
|
||||||
</StyledSettingsOptionCardContent>
|
</StyledSettingsOptionCardContent>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -91,7 +91,7 @@ export const SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS = {
|
|||||||
exampleValue: {
|
exampleValue: {
|
||||||
primaryPhoneNumber: '234-567-890',
|
primaryPhoneNumber: '234-567-890',
|
||||||
primaryPhoneCountryCode: '+1',
|
primaryPhoneCountryCode: '+1',
|
||||||
additionalPhones: [{ number: '234-567-890', countryCode: '+1' }],
|
additionalPhones: [{ number: '234-567-890', callingCode: '+1' }],
|
||||||
},
|
},
|
||||||
subFields: [
|
subFields: [
|
||||||
'primaryPhoneNumber',
|
'primaryPhoneNumber',
|
||||||
@ -151,7 +151,7 @@ export const SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS = {
|
|||||||
},
|
},
|
||||||
exampleValue: {
|
exampleValue: {
|
||||||
addressStreet1: '456 Oak Street',
|
addressStreet1: '456 Oak Street',
|
||||||
addressStreet2: 'Unit 3B',
|
addressStreet2: '',
|
||||||
addressCity: 'Springfield',
|
addressCity: 'Springfield',
|
||||||
addressState: 'California',
|
addressState: 'California',
|
||||||
addressCountry: 'United States',
|
addressCountry: 'United States',
|
||||||
|
@ -0,0 +1,86 @@
|
|||||||
|
import { Controller, useFormContext } from 'react-hook-form';
|
||||||
|
|
||||||
|
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||||
|
import { addressSchema as addressFieldDefaultValueSchema } from '@/object-record/record-field/types/guards/isFieldAddressValue';
|
||||||
|
import { SettingsOptionCardContentSelect } from '@/settings/components/SettingsOptions/SettingsOptionCardContentSelect';
|
||||||
|
import { useCountries } from '@/ui/input/components/internal/hooks/useCountries';
|
||||||
|
import { IconMap } from 'twenty-ui';
|
||||||
|
import { z } from 'zod';
|
||||||
|
import { applySimpleQuotesToString } from '~/utils/string/applySimpleQuotesToString';
|
||||||
|
import { stripSimpleQuotesFromString } from '~/utils/string/stripSimpleQuotesFromString';
|
||||||
|
|
||||||
|
type SettingsDataModelFieldAddressFormProps = {
|
||||||
|
disabled?: boolean;
|
||||||
|
defaultCountry?: string;
|
||||||
|
fieldMetadataItem: Pick<
|
||||||
|
FieldMetadataItem,
|
||||||
|
'icon' | 'label' | 'type' | 'defaultValue' | 'settings'
|
||||||
|
>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const settingsDataModelFieldAddressFormSchema = z.object({
|
||||||
|
defaultValue: addressFieldDefaultValueSchema,
|
||||||
|
});
|
||||||
|
|
||||||
|
export type SettingsDataModelFieldTextFormValues = z.infer<
|
||||||
|
typeof settingsDataModelFieldAddressFormSchema
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const SettingsDataModelFieldAddressForm = ({
|
||||||
|
disabled,
|
||||||
|
fieldMetadataItem,
|
||||||
|
}: SettingsDataModelFieldAddressFormProps) => {
|
||||||
|
const { control } = useFormContext<SettingsDataModelFieldTextFormValues>();
|
||||||
|
const countries = useCountries()
|
||||||
|
.sort((a, b) => a.countryName.localeCompare(b.countryName))
|
||||||
|
.map((country) => ({
|
||||||
|
label: country.countryName,
|
||||||
|
value: country.countryName,
|
||||||
|
}));
|
||||||
|
countries.unshift({
|
||||||
|
label: 'No country',
|
||||||
|
value: '',
|
||||||
|
});
|
||||||
|
const defaultDefaultValue = {
|
||||||
|
addressStreet1: "''",
|
||||||
|
addressStreet2: null,
|
||||||
|
addressCity: null,
|
||||||
|
addressState: null,
|
||||||
|
addressPostcode: null,
|
||||||
|
addressCountry: null,
|
||||||
|
addressLat: null,
|
||||||
|
addressLng: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Controller
|
||||||
|
name="defaultValue"
|
||||||
|
defaultValue={{
|
||||||
|
...defaultDefaultValue,
|
||||||
|
...fieldMetadataItem?.defaultValue,
|
||||||
|
}}
|
||||||
|
control={control}
|
||||||
|
render={({ field: { onChange, value } }) => {
|
||||||
|
const defaultCountry = value?.addressCountry || '';
|
||||||
|
return (
|
||||||
|
<SettingsOptionCardContentSelect<string>
|
||||||
|
Icon={IconMap}
|
||||||
|
dropdownId="selectDefaultCountry"
|
||||||
|
title="Default Country"
|
||||||
|
description="The default country for new addresses"
|
||||||
|
value={stripSimpleQuotesFromString(defaultCountry)}
|
||||||
|
onChange={(newCountry) =>
|
||||||
|
onChange({
|
||||||
|
...value,
|
||||||
|
addressCountry: applySimpleQuotesToString(newCountry),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
disabled={disabled}
|
||||||
|
options={countries}
|
||||||
|
fullWidth={true}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,45 @@
|
|||||||
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
|
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||||
|
|
||||||
|
import { SettingsDataModelPreviewFormCard } from '@/settings/data-model/components/SettingsDataModelPreviewFormCard';
|
||||||
|
import { SettingsDataModelFieldAddressForm } from '@/settings/data-model/fields/forms/address/components/SettingsDataModelFieldAddressForm';
|
||||||
|
import {
|
||||||
|
SettingsDataModelFieldPreviewCard,
|
||||||
|
SettingsDataModelFieldPreviewCardProps,
|
||||||
|
} from '@/settings/data-model/fields/preview/components/SettingsDataModelFieldPreviewCard';
|
||||||
|
|
||||||
|
type SettingsDataModelFieldAddressSettingsFormCardProps = {
|
||||||
|
disabled?: boolean;
|
||||||
|
fieldMetadataItem: Pick<
|
||||||
|
FieldMetadataItem,
|
||||||
|
'icon' | 'label' | 'type' | 'defaultValue'
|
||||||
|
>;
|
||||||
|
} & Pick<SettingsDataModelFieldPreviewCardProps, 'objectMetadataItem'>;
|
||||||
|
|
||||||
|
const StyledFieldPreviewCard = styled(SettingsDataModelFieldPreviewCard)`
|
||||||
|
flex: 1 1 100%;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const SettingsDataModelFieldAddressSettingsFormCard = ({
|
||||||
|
disabled,
|
||||||
|
fieldMetadataItem,
|
||||||
|
objectMetadataItem,
|
||||||
|
}: SettingsDataModelFieldAddressSettingsFormCardProps) => {
|
||||||
|
return (
|
||||||
|
<SettingsDataModelPreviewFormCard
|
||||||
|
preview={
|
||||||
|
<StyledFieldPreviewCard
|
||||||
|
fieldMetadataItem={fieldMetadataItem}
|
||||||
|
objectMetadataItem={objectMetadataItem}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
form={
|
||||||
|
<SettingsDataModelFieldAddressForm
|
||||||
|
disabled={disabled}
|
||||||
|
fieldMetadataItem={fieldMetadataItem}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
@ -5,6 +5,8 @@ import { z } from 'zod';
|
|||||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||||
import { SettingsDataModelPreviewFormCard } from '@/settings/data-model/components/SettingsDataModelPreviewFormCard';
|
import { SettingsDataModelPreviewFormCard } from '@/settings/data-model/components/SettingsDataModelPreviewFormCard';
|
||||||
import { SETTINGS_FIELD_TYPE_CONFIGS } from '@/settings/data-model/constants/SettingsFieldTypeConfigs';
|
import { SETTINGS_FIELD_TYPE_CONFIGS } from '@/settings/data-model/constants/SettingsFieldTypeConfigs';
|
||||||
|
import { settingsDataModelFieldAddressFormSchema } from '@/settings/data-model/fields/forms/address/components/SettingsDataModelFieldAddressForm';
|
||||||
|
import { SettingsDataModelFieldAddressSettingsFormCard } from '@/settings/data-model/fields/forms/address/components/SettingsDataModelFieldAddressSettingsFormCard';
|
||||||
import { settingsDataModelFieldBooleanFormSchema } from '@/settings/data-model/fields/forms/boolean/components/SettingsDataModelFieldBooleanForm';
|
import { settingsDataModelFieldBooleanFormSchema } from '@/settings/data-model/fields/forms/boolean/components/SettingsDataModelFieldBooleanForm';
|
||||||
import { SettingsDataModelFieldBooleanSettingsFormCard } from '@/settings/data-model/fields/forms/boolean/components/SettingsDataModelFieldBooleanSettingsFormCard';
|
import { SettingsDataModelFieldBooleanSettingsFormCard } from '@/settings/data-model/fields/forms/boolean/components/SettingsDataModelFieldBooleanSettingsFormCard';
|
||||||
import { settingsDataModelFieldtextFormSchema } from '@/settings/data-model/fields/forms/components/text/SettingsDataModelFieldTextForm';
|
import { settingsDataModelFieldtextFormSchema } from '@/settings/data-model/fields/forms/components/text/SettingsDataModelFieldTextForm';
|
||||||
@ -15,6 +17,8 @@ import { settingsDataModelFieldDateFormSchema } from '@/settings/data-model/fiel
|
|||||||
import { SettingsDataModelFieldDateSettingsFormCard } from '@/settings/data-model/fields/forms/date/components/SettingsDataModelFieldDateSettingsFormCard';
|
import { SettingsDataModelFieldDateSettingsFormCard } from '@/settings/data-model/fields/forms/date/components/SettingsDataModelFieldDateSettingsFormCard';
|
||||||
import { settingsDataModelFieldNumberFormSchema } from '@/settings/data-model/fields/forms/number/components/SettingsDataModelFieldNumberForm';
|
import { settingsDataModelFieldNumberFormSchema } from '@/settings/data-model/fields/forms/number/components/SettingsDataModelFieldNumberForm';
|
||||||
import { SettingsDataModelFieldNumberSettingsFormCard } from '@/settings/data-model/fields/forms/number/components/SettingsDataModelFieldNumberSettingsFormCard';
|
import { SettingsDataModelFieldNumberSettingsFormCard } from '@/settings/data-model/fields/forms/number/components/SettingsDataModelFieldNumberSettingsFormCard';
|
||||||
|
import { settingsDataModelFieldPhonesFormSchema } from '@/settings/data-model/fields/forms/phones/components/SettingsDataModelFieldPhonesForm';
|
||||||
|
import { SettingsDataModelFieldPhonesSettingsFormCard } from '@/settings/data-model/fields/forms/phones/components/SettingsDataModelFieldPhonesSettingsFormCard';
|
||||||
import { settingsDataModelFieldRelationFormSchema } from '@/settings/data-model/fields/forms/relation/components/SettingsDataModelFieldRelationForm';
|
import { settingsDataModelFieldRelationFormSchema } from '@/settings/data-model/fields/forms/relation/components/SettingsDataModelFieldRelationForm';
|
||||||
import { SettingsDataModelFieldRelationSettingsFormCard } from '@/settings/data-model/fields/forms/relation/components/SettingsDataModelFieldRelationSettingsFormCard';
|
import { SettingsDataModelFieldRelationSettingsFormCard } from '@/settings/data-model/fields/forms/relation/components/SettingsDataModelFieldRelationSettingsFormCard';
|
||||||
import {
|
import {
|
||||||
@ -64,6 +68,14 @@ const textFieldFormSchema = z
|
|||||||
.object({ type: z.literal(FieldMetadataType.Text) })
|
.object({ type: z.literal(FieldMetadataType.Text) })
|
||||||
.merge(settingsDataModelFieldtextFormSchema);
|
.merge(settingsDataModelFieldtextFormSchema);
|
||||||
|
|
||||||
|
const addressFieldFormSchema = z
|
||||||
|
.object({ type: z.literal(FieldMetadataType.Address) })
|
||||||
|
.merge(settingsDataModelFieldAddressFormSchema);
|
||||||
|
|
||||||
|
const phonesFieldFormSchema = z
|
||||||
|
.object({ type: z.literal(FieldMetadataType.Phones) })
|
||||||
|
.merge(settingsDataModelFieldPhonesFormSchema);
|
||||||
|
|
||||||
const otherFieldsFormSchema = z.object({
|
const otherFieldsFormSchema = z.object({
|
||||||
type: z.enum(
|
type: z.enum(
|
||||||
Object.keys(
|
Object.keys(
|
||||||
@ -76,6 +88,8 @@ const otherFieldsFormSchema = z.object({
|
|||||||
FieldMetadataType.Date,
|
FieldMetadataType.Date,
|
||||||
FieldMetadataType.DateTime,
|
FieldMetadataType.DateTime,
|
||||||
FieldMetadataType.Number,
|
FieldMetadataType.Number,
|
||||||
|
FieldMetadataType.Address,
|
||||||
|
FieldMetadataType.Phones,
|
||||||
FieldMetadataType.Text,
|
FieldMetadataType.Text,
|
||||||
]),
|
]),
|
||||||
) as [FieldMetadataType, ...FieldMetadataType[]],
|
) as [FieldMetadataType, ...FieldMetadataType[]],
|
||||||
@ -94,6 +108,8 @@ export const settingsDataModelFieldSettingsFormSchema = z.discriminatedUnion(
|
|||||||
multiSelectFieldFormSchema,
|
multiSelectFieldFormSchema,
|
||||||
numberFieldFormSchema,
|
numberFieldFormSchema,
|
||||||
textFieldFormSchema,
|
textFieldFormSchema,
|
||||||
|
addressFieldFormSchema,
|
||||||
|
phonesFieldFormSchema,
|
||||||
otherFieldsFormSchema,
|
otherFieldsFormSchema,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@ -195,6 +211,24 @@ export const SettingsDataModelFieldSettingsFormCard = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (fieldMetadataItem.type === FieldMetadataType.Address) {
|
||||||
|
return (
|
||||||
|
<SettingsDataModelFieldAddressSettingsFormCard
|
||||||
|
fieldMetadataItem={fieldMetadataItem}
|
||||||
|
objectMetadataItem={objectMetadataItem}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fieldMetadataItem.type === FieldMetadataType.Phones) {
|
||||||
|
return (
|
||||||
|
<SettingsDataModelFieldPhonesSettingsFormCard
|
||||||
|
fieldMetadataItem={fieldMetadataItem}
|
||||||
|
objectMetadataItem={objectMetadataItem}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
fieldMetadataItem.type === FieldMetadataType.Select ||
|
fieldMetadataItem.type === FieldMetadataType.Select ||
|
||||||
fieldMetadataItem.type === FieldMetadataType.MultiSelect
|
fieldMetadataItem.type === FieldMetadataType.MultiSelect
|
||||||
|
@ -0,0 +1,80 @@
|
|||||||
|
import { Controller, useFormContext } from 'react-hook-form';
|
||||||
|
|
||||||
|
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||||
|
import { phonesSchema as phonesFieldDefaultValueSchema } from '@/object-record/record-field/types/guards/isFieldPhonesValue';
|
||||||
|
import { SettingsOptionCardContentSelect } from '@/settings/components/SettingsOptions/SettingsOptionCardContentSelect';
|
||||||
|
import { useCountries } from '@/ui/input/components/internal/hooks/useCountries';
|
||||||
|
import { IconMap } from 'twenty-ui';
|
||||||
|
import { z } from 'zod';
|
||||||
|
import { applySimpleQuotesToString } from '~/utils/string/applySimpleQuotesToString';
|
||||||
|
import { stripSimpleQuotesFromString } from '~/utils/string/stripSimpleQuotesFromString';
|
||||||
|
|
||||||
|
type SettingsDataModelFieldPhonesFormProps = {
|
||||||
|
disabled?: boolean;
|
||||||
|
defaultCountryCode?: string;
|
||||||
|
fieldMetadataItem: Pick<
|
||||||
|
FieldMetadataItem,
|
||||||
|
'icon' | 'label' | 'type' | 'defaultValue' | 'settings'
|
||||||
|
>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const settingsDataModelFieldPhonesFormSchema = z.object({
|
||||||
|
defaultValue: phonesFieldDefaultValueSchema,
|
||||||
|
});
|
||||||
|
|
||||||
|
export type SettingsDataModelFieldTextFormValues = z.infer<
|
||||||
|
typeof settingsDataModelFieldPhonesFormSchema
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const SettingsDataModelFieldPhonesForm = ({
|
||||||
|
disabled,
|
||||||
|
fieldMetadataItem,
|
||||||
|
}: SettingsDataModelFieldPhonesFormProps) => {
|
||||||
|
const { control } = useFormContext<SettingsDataModelFieldTextFormValues>();
|
||||||
|
|
||||||
|
const countries = useCountries()
|
||||||
|
.sort((a, b) => a.countryName.localeCompare(b.countryName))
|
||||||
|
.map((country) => ({
|
||||||
|
label: `${country.countryName} (+${country.callingCode})`,
|
||||||
|
value: `${country.callingCode}`,
|
||||||
|
}));
|
||||||
|
countries.unshift({ label: 'No country', value: '' });
|
||||||
|
const defaultDefaultValue = {
|
||||||
|
primaryPhoneNumber: "''",
|
||||||
|
primaryPhoneCountryCode: "''",
|
||||||
|
additionalPhones: null,
|
||||||
|
};
|
||||||
|
const fieldMetadataItemDefaultValue = fieldMetadataItem?.defaultValue;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Controller
|
||||||
|
name="defaultValue"
|
||||||
|
defaultValue={{
|
||||||
|
...defaultDefaultValue,
|
||||||
|
...fieldMetadataItemDefaultValue,
|
||||||
|
}}
|
||||||
|
control={control}
|
||||||
|
render={({ field: { onChange, value } }) => {
|
||||||
|
return (
|
||||||
|
<SettingsOptionCardContentSelect<string>
|
||||||
|
Icon={IconMap}
|
||||||
|
dropdownId="selectDefaultCountryCode"
|
||||||
|
title="Default Country Code"
|
||||||
|
description="The default country code for new phone numbers."
|
||||||
|
value={stripSimpleQuotesFromString(value?.primaryPhoneCountryCode)}
|
||||||
|
onChange={(newPhoneCountryCode) =>
|
||||||
|
onChange({
|
||||||
|
...value,
|
||||||
|
primaryPhoneCountryCode:
|
||||||
|
applySimpleQuotesToString(newPhoneCountryCode),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
disabled={disabled}
|
||||||
|
options={countries}
|
||||||
|
fullWidth={true}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,46 @@
|
|||||||
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
|
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||||
|
|
||||||
|
import { SettingsDataModelPreviewFormCard } from '@/settings/data-model/components/SettingsDataModelPreviewFormCard';
|
||||||
|
|
||||||
|
import { SettingsDataModelFieldPhonesForm } from '@/settings/data-model/fields/forms/phones/components/SettingsDataModelFieldPhonesForm';
|
||||||
|
import {
|
||||||
|
SettingsDataModelFieldPreviewCard,
|
||||||
|
SettingsDataModelFieldPreviewCardProps,
|
||||||
|
} from '@/settings/data-model/fields/preview/components/SettingsDataModelFieldPreviewCard';
|
||||||
|
|
||||||
|
type SettingsDataModelFieldPhonesSettingsFormCardProps = {
|
||||||
|
disabled?: boolean;
|
||||||
|
fieldMetadataItem: Pick<
|
||||||
|
FieldMetadataItem,
|
||||||
|
'icon' | 'label' | 'type' | 'defaultValue'
|
||||||
|
>;
|
||||||
|
} & Pick<SettingsDataModelFieldPreviewCardProps, 'objectMetadataItem'>;
|
||||||
|
|
||||||
|
const StyledFieldPreviewCard = styled(SettingsDataModelFieldPreviewCard)`
|
||||||
|
flex: 1 1 100%;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const SettingsDataModelFieldPhonesSettingsFormCard = ({
|
||||||
|
disabled,
|
||||||
|
fieldMetadataItem,
|
||||||
|
objectMetadataItem,
|
||||||
|
}: SettingsDataModelFieldPhonesSettingsFormCardProps) => {
|
||||||
|
return (
|
||||||
|
<SettingsDataModelPreviewFormCard
|
||||||
|
preview={
|
||||||
|
<StyledFieldPreviewCard
|
||||||
|
fieldMetadataItem={fieldMetadataItem}
|
||||||
|
objectMetadataItem={objectMetadataItem}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
form={
|
||||||
|
<SettingsDataModelFieldPhonesForm
|
||||||
|
disabled={disabled}
|
||||||
|
fieldMetadataItem={fieldMetadataItem}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
@ -2,9 +2,11 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSi
|
|||||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||||
import { useRelationFieldPreviewValue } from '@/settings/data-model/fields/preview/hooks/useRelationFieldPreviewValue';
|
import { useRelationFieldPreviewValue } from '@/settings/data-model/fields/preview/hooks/useRelationFieldPreviewValue';
|
||||||
|
import { getAddressFieldPreviewValue } from '@/settings/data-model/fields/preview/utils/getAddressFieldPreviewValue';
|
||||||
import { getCurrencyFieldPreviewValue } from '@/settings/data-model/fields/preview/utils/getCurrencyFieldPreviewValue';
|
import { getCurrencyFieldPreviewValue } from '@/settings/data-model/fields/preview/utils/getCurrencyFieldPreviewValue';
|
||||||
import { getFieldPreviewValue } from '@/settings/data-model/fields/preview/utils/getFieldPreviewValue';
|
import { getFieldPreviewValue } from '@/settings/data-model/fields/preview/utils/getFieldPreviewValue';
|
||||||
import { getMultiSelectFieldPreviewValue } from '@/settings/data-model/fields/preview/utils/getMultiSelectFieldPreviewValue';
|
import { getMultiSelectFieldPreviewValue } from '@/settings/data-model/fields/preview/utils/getMultiSelectFieldPreviewValue';
|
||||||
|
import { getPhonesFieldPreviewValue } from '@/settings/data-model/fields/preview/utils/getPhonesFieldPreviewValue';
|
||||||
import { getSelectFieldPreviewValue } from '@/settings/data-model/fields/preview/utils/getSelectFieldPreviewValue';
|
import { getSelectFieldPreviewValue } from '@/settings/data-model/fields/preview/utils/getSelectFieldPreviewValue';
|
||||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||||
|
|
||||||
@ -45,6 +47,10 @@ export const useFieldPreviewValue = ({
|
|||||||
return getSelectFieldPreviewValue({ fieldMetadataItem });
|
return getSelectFieldPreviewValue({ fieldMetadataItem });
|
||||||
case FieldMetadataType.MultiSelect:
|
case FieldMetadataType.MultiSelect:
|
||||||
return getMultiSelectFieldPreviewValue({ fieldMetadataItem });
|
return getMultiSelectFieldPreviewValue({ fieldMetadataItem });
|
||||||
|
case FieldMetadataType.Address:
|
||||||
|
return getAddressFieldPreviewValue({ fieldMetadataItem });
|
||||||
|
case FieldMetadataType.Phones:
|
||||||
|
return getPhonesFieldPreviewValue({ fieldMetadataItem });
|
||||||
default:
|
default:
|
||||||
return getFieldPreviewValue({ fieldMetadataItem });
|
return getFieldPreviewValue({ fieldMetadataItem });
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,34 @@
|
|||||||
|
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||||
|
import { FieldAddressValue } from '@/object-record/record-field/types/FieldMetadata';
|
||||||
|
import { getSettingsFieldTypeConfig } from '@/settings/data-model/utils/getSettingsFieldTypeConfig';
|
||||||
|
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||||
|
import { stripSimpleQuotesFromString } from '~/utils/string/stripSimpleQuotesFromString';
|
||||||
|
|
||||||
|
export const getAddressFieldPreviewValue = ({
|
||||||
|
fieldMetadataItem,
|
||||||
|
}: {
|
||||||
|
fieldMetadataItem: Pick<
|
||||||
|
FieldMetadataItem,
|
||||||
|
'defaultValue' | 'options' | 'type'
|
||||||
|
>;
|
||||||
|
}): FieldAddressValue | null => {
|
||||||
|
if (fieldMetadataItem.type !== FieldMetadataType.Address) return null;
|
||||||
|
|
||||||
|
const addressFieldTypeConfig = getSettingsFieldTypeConfig(
|
||||||
|
FieldMetadataType.Address,
|
||||||
|
);
|
||||||
|
|
||||||
|
const placeholderDefaultValue = addressFieldTypeConfig.exampleValue;
|
||||||
|
|
||||||
|
const addressCountry =
|
||||||
|
fieldMetadataItem.defaultValue?.addressCountry &&
|
||||||
|
fieldMetadataItem.defaultValue.addressCountry !== ''
|
||||||
|
? stripSimpleQuotesFromString(
|
||||||
|
fieldMetadataItem.defaultValue?.addressCountry,
|
||||||
|
)
|
||||||
|
: null;
|
||||||
|
return {
|
||||||
|
...placeholderDefaultValue,
|
||||||
|
addressCountry: addressCountry,
|
||||||
|
};
|
||||||
|
};
|
@ -0,0 +1,33 @@
|
|||||||
|
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||||
|
import { FieldPhonesValue } from '@/object-record/record-field/types/FieldMetadata';
|
||||||
|
import { getSettingsFieldTypeConfig } from '@/settings/data-model/utils/getSettingsFieldTypeConfig';
|
||||||
|
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||||
|
import { stripSimpleQuotesFromString } from '~/utils/string/stripSimpleQuotesFromString';
|
||||||
|
|
||||||
|
export const getPhonesFieldPreviewValue = ({
|
||||||
|
fieldMetadataItem,
|
||||||
|
}: {
|
||||||
|
fieldMetadataItem: Pick<
|
||||||
|
FieldMetadataItem,
|
||||||
|
'defaultValue' | 'options' | 'type'
|
||||||
|
>;
|
||||||
|
}): FieldPhonesValue | null => {
|
||||||
|
if (fieldMetadataItem.type !== FieldMetadataType.Phones) return null;
|
||||||
|
|
||||||
|
const phonesFieldTypeConfig = getSettingsFieldTypeConfig(
|
||||||
|
FieldMetadataType.Phones,
|
||||||
|
);
|
||||||
|
|
||||||
|
const placeholderDefaultValue = phonesFieldTypeConfig.exampleValue;
|
||||||
|
const primaryPhoneCountryCode =
|
||||||
|
fieldMetadataItem.defaultValue?.primaryPhoneCountryCode &&
|
||||||
|
fieldMetadataItem.defaultValue.primaryPhoneCountryCode !== ''
|
||||||
|
? `+${stripSimpleQuotesFromString(
|
||||||
|
fieldMetadataItem.defaultValue?.primaryPhoneCountryCode,
|
||||||
|
)}`
|
||||||
|
: null;
|
||||||
|
return {
|
||||||
|
...placeholderDefaultValue,
|
||||||
|
primaryPhoneCountryCode,
|
||||||
|
};
|
||||||
|
};
|
@ -4,28 +4,37 @@ import { ContactLink } from 'twenty-ui';
|
|||||||
|
|
||||||
import { isDefined } from '~/utils/isDefined';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
type PhoneDisplayProps = {
|
interface PhoneDisplayProps {
|
||||||
value: string | null | undefined;
|
value: PhoneDisplayValueProps;
|
||||||
|
}
|
||||||
|
type PhoneDisplayValueProps = {
|
||||||
|
number: string | null | undefined;
|
||||||
|
callingCode: string | null | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: see if we can find a faster way to format the phone number
|
export const PhoneDisplay = ({
|
||||||
export const PhoneDisplay = ({ value }: PhoneDisplayProps) => {
|
value: { number, callingCode },
|
||||||
if (!isDefined(value)) {
|
}: PhoneDisplayProps) => {
|
||||||
return <ContactLink href="#">{value}</ContactLink>;
|
if (!isDefined(number)) return <ContactLink href="#">{number}</ContactLink>;
|
||||||
}
|
|
||||||
|
const callingCodeSanitized = callingCode?.replace('+', '');
|
||||||
|
|
||||||
let parsedPhoneNumber: PhoneNumber | null = null;
|
let parsedPhoneNumber: PhoneNumber | null = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// TODO: parse according to locale not hard coded FR
|
parsedPhoneNumber = parsePhoneNumber(number, {
|
||||||
parsedPhoneNumber = parsePhoneNumber(value, 'FR');
|
defaultCallingCode: callingCodeSanitized || '1',
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return <ContactLink href="#">{value}</ContactLink>;
|
if (!(error instanceof Error))
|
||||||
|
return <ContactLink href="#">{number}</ContactLink>;
|
||||||
|
if (error.message === 'NOT_A_NUMBER')
|
||||||
|
return <ContactLink href="#">{`+${callingCodeSanitized}`}</ContactLink>;
|
||||||
|
return <ContactLink href="#">{number}</ContactLink>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const URI = parsedPhoneNumber.getURI();
|
const URI = parsedPhoneNumber.getURI();
|
||||||
const formatedPhoneNumber = parsedPhoneNumber.formatInternational();
|
const formatedPhoneNumber = parsedPhoneNumber.formatInternational();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ContactLink
|
<ContactLink
|
||||||
href={URI}
|
href={URI}
|
||||||
@ -33,7 +42,7 @@ export const PhoneDisplay = ({ value }: PhoneDisplayProps) => {
|
|||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{formatedPhoneNumber || value}
|
{formatedPhoneNumber || number}
|
||||||
</ContactLink>
|
</ContactLink>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -36,16 +36,16 @@ export const PhonesDisplay = ({ value, isFocused }: PhonesDisplayProps) => {
|
|||||||
value?.primaryPhoneNumber
|
value?.primaryPhoneNumber
|
||||||
? {
|
? {
|
||||||
number: value.primaryPhoneNumber,
|
number: value.primaryPhoneNumber,
|
||||||
countryCode: value.primaryPhoneCountryCode,
|
callingCode: value.primaryPhoneCountryCode,
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
...parseAdditionalPhones(value?.additionalPhones),
|
...parseAdditionalPhones(value?.additionalPhones),
|
||||||
]
|
]
|
||||||
.filter(isDefined)
|
.filter(isDefined)
|
||||||
.map(({ number, countryCode }) => {
|
.map(({ number, callingCode }) => {
|
||||||
return {
|
return {
|
||||||
number,
|
number,
|
||||||
countryCode,
|
callingCode,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
[
|
[
|
||||||
@ -65,9 +65,9 @@ export const PhonesDisplay = ({ value, isFocused }: PhonesDisplayProps) => {
|
|||||||
|
|
||||||
return isFocused ? (
|
return isFocused ? (
|
||||||
<ExpandableList isChipCountDisplayed>
|
<ExpandableList isChipCountDisplayed>
|
||||||
{phones.map(({ number, countryCode }, index) => {
|
{phones.map(({ number, callingCode }, index) => {
|
||||||
const { parsedPhone, invalidPhone } =
|
const { parsedPhone, invalidPhone } =
|
||||||
parsePhoneNumberOrReturnInvalidValue(countryCode + number);
|
parsePhoneNumberOrReturnInvalidValue(`+${callingCode}` + number);
|
||||||
const URI = parsedPhone?.getURI();
|
const URI = parsedPhone?.getURI();
|
||||||
return (
|
return (
|
||||||
<RoundedLink
|
<RoundedLink
|
||||||
@ -82,9 +82,9 @@ export const PhonesDisplay = ({ value, isFocused }: PhonesDisplayProps) => {
|
|||||||
</ExpandableList>
|
</ExpandableList>
|
||||||
) : (
|
) : (
|
||||||
<StyledContainer>
|
<StyledContainer>
|
||||||
{phones.map(({ number, countryCode }, index) => {
|
{phones.map(({ number, callingCode }, index) => {
|
||||||
const { parsedPhone, invalidPhone } =
|
const { parsedPhone, invalidPhone } =
|
||||||
parsePhoneNumberOrReturnInvalidValue(countryCode + number);
|
parsePhoneNumberOrReturnInvalidValue(`+${callingCode}` + number);
|
||||||
const URI = parsedPhone?.getURI();
|
const URI = parsedPhone?.getURI();
|
||||||
return (
|
return (
|
||||||
<RoundedLink
|
<RoundedLink
|
||||||
|
@ -26,7 +26,9 @@ type CallToActionButton = {
|
|||||||
Icon?: IconComponent;
|
Icon?: IconComponent;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SelectProps<Value extends string | number | boolean | null> = {
|
export type SelectValue = string | number | boolean | null;
|
||||||
|
|
||||||
|
export type SelectProps<Value extends SelectValue> = {
|
||||||
className?: string;
|
className?: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
selectSizeVariant?: SelectSizeVariant;
|
selectSizeVariant?: SelectSizeVariant;
|
||||||
@ -57,7 +59,7 @@ const StyledLabel = styled.span`
|
|||||||
margin-bottom: ${({ theme }) => theme.spacing(1)};
|
margin-bottom: ${({ theme }) => theme.spacing(1)};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const Select = <Value extends string | number | boolean | null>({
|
export const Select = <Value extends SelectValue>({
|
||||||
className,
|
className,
|
||||||
disabled: disabledFromProps,
|
disabled: disabledFromProps,
|
||||||
selectSizeVariant,
|
selectSizeVariant,
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { IconComponentProps } from 'twenty-ui';
|
import { IconCircleOff, IconComponentProps } from 'twenty-ui';
|
||||||
|
|
||||||
import { SELECT_COUNTRY_DROPDOWN_ID } from '@/ui/input/components/internal/country/constants/SelectCountryDropdownId';
|
import { SELECT_COUNTRY_DROPDOWN_ID } from '@/ui/input/components/internal/country/constants/SelectCountryDropdownId';
|
||||||
import { useCountries } from '@/ui/input/components/internal/hooks/useCountries';
|
import { useCountries } from '@/ui/input/components/internal/hooks/useCountries';
|
||||||
@ -15,12 +15,20 @@ export const CountrySelect = ({
|
|||||||
const countries = useCountries();
|
const countries = useCountries();
|
||||||
|
|
||||||
const options: SelectOption<string>[] = useMemo(() => {
|
const options: SelectOption<string>[] = useMemo(() => {
|
||||||
return countries.map<SelectOption<string>>(({ countryName, Flag }) => ({
|
const countryList = countries.map<SelectOption<string>>(
|
||||||
label: countryName,
|
({ countryName, Flag }) => ({
|
||||||
value: countryName,
|
label: countryName,
|
||||||
Icon: (props: IconComponentProps) =>
|
value: countryName,
|
||||||
Flag({ width: props.size, height: props.size }), // TODO : improve this ?
|
Icon: (props: IconComponentProps) =>
|
||||||
}));
|
Flag({ width: props.size, height: props.size }), // TODO : improve this ?
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
countryList.unshift({
|
||||||
|
label: 'No country',
|
||||||
|
value: '',
|
||||||
|
Icon: IconCircleOff,
|
||||||
|
});
|
||||||
|
return countryList;
|
||||||
}, [countries]);
|
}, [countries]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import * as Flags from 'country-flag-icons/react/3x2';
|
import * as Flags from 'country-flag-icons/react/3x2';
|
||||||
import { CountryCallingCode } from 'libphonenumber-js';
|
import { CountryCallingCode, CountryCode } from 'libphonenumber-js';
|
||||||
|
|
||||||
export type Country = {
|
export type Country = {
|
||||||
countryCode: string;
|
countryCode: CountryCode;
|
||||||
countryName: string;
|
countryName: string;
|
||||||
callingCode: CountryCallingCode;
|
callingCode: CountryCallingCode;
|
||||||
Flag: Flags.FlagComponent;
|
Flag: Flags.FlagComponent;
|
||||||
|
@ -6,3 +6,20 @@ export const stripSimpleQuotesFromString = <Input extends string>(
|
|||||||
(simpleQuotesStringSchema.safeParse(value).success
|
(simpleQuotesStringSchema.safeParse(value).success
|
||||||
? value.slice(1, -1)
|
? value.slice(1, -1)
|
||||||
: value) as Input extends `'${infer Output}'` ? Output : Input;
|
: value) as Input extends `'${infer Output}'` ? Output : Input;
|
||||||
|
|
||||||
|
export const stripSimpleQuotesFromStringRecursive = (obj: any): any => {
|
||||||
|
if (typeof obj === 'string') {
|
||||||
|
return stripSimpleQuotesFromString(obj);
|
||||||
|
} else if (Array.isArray(obj)) {
|
||||||
|
return obj.map(stripSimpleQuotesFromStringRecursive);
|
||||||
|
} else if (typeof obj === 'object' && obj !== null) {
|
||||||
|
const newObj: any = {};
|
||||||
|
for (const key in obj) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(obj, key) === true) {
|
||||||
|
newObj[key] = stripSimpleQuotesFromStringRecursive(obj[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newObj;
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
};
|
||||||
|
@ -23,7 +23,7 @@ enum ValueType {
|
|||||||
NUMBER = 'number',
|
NUMBER = 'number',
|
||||||
}
|
}
|
||||||
|
|
||||||
class SettingsValidation {
|
class NumberSettingsValidation {
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsInt()
|
@IsInt()
|
||||||
@Min(0)
|
@Min(0)
|
||||||
@ -32,7 +32,9 @@ class SettingsValidation {
|
|||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsEnum(ValueType)
|
@IsEnum(ValueType)
|
||||||
type?: 'percentage' | 'number';
|
type?: 'percentage' | 'number';
|
||||||
|
}
|
||||||
|
|
||||||
|
class TextSettingsValidation {
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsInt()
|
@IsInt()
|
||||||
@Min(0)
|
@Min(0)
|
||||||
@ -55,17 +57,19 @@ export class FieldMetadataValidationService<
|
|||||||
}) {
|
}) {
|
||||||
switch (fieldType) {
|
switch (fieldType) {
|
||||||
case FieldMetadataType.NUMBER:
|
case FieldMetadataType.NUMBER:
|
||||||
|
await this.validateSettings(NumberSettingsValidation, settings);
|
||||||
|
break;
|
||||||
case FieldMetadataType.TEXT:
|
case FieldMetadataType.TEXT:
|
||||||
await this.validateSettings(settings);
|
await this.validateSettings(TextSettingsValidation, settings);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async validateSettings(settings: any) {
|
private async validateSettings(validator: any, settings: any) {
|
||||||
try {
|
try {
|
||||||
const settingsInstance = plainToInstance(SettingsValidation, settings);
|
const settingsInstance = plainToInstance(validator, settings);
|
||||||
|
|
||||||
await validateOrReject(settingsInstance);
|
await validateOrReject(settingsInstance);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -0,0 +1,59 @@
|
|||||||
|
import { FieldMetadataSettings } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface';
|
||||||
|
|
||||||
|
import { FieldMetadataValidationService } from 'src/engine/metadata-modules/field-metadata/field-metadata-validation.service';
|
||||||
|
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||||
|
import { FieldMetadataException } from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
|
||||||
|
|
||||||
|
describe('FieldMetadataValidationService', () => {
|
||||||
|
let service: FieldMetadataValidationService;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
service = new FieldMetadataValidationService();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should validate NUMBER settings successfully', async () => {
|
||||||
|
const settings = { decimals: 2, type: 'number' } as FieldMetadataSettings;
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
service.validateSettingsOrThrow({
|
||||||
|
fieldType: FieldMetadataType.NUMBER,
|
||||||
|
settings,
|
||||||
|
}),
|
||||||
|
).resolves.not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an error for invalid NUMBER settings', async () => {
|
||||||
|
const settings = { type: 'invalidType' } as FieldMetadataSettings;
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
service.validateSettingsOrThrow({
|
||||||
|
fieldType: FieldMetadataType.NUMBER,
|
||||||
|
settings,
|
||||||
|
}),
|
||||||
|
).rejects.toThrow(FieldMetadataException);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should validate TEXT settings successfully', async () => {
|
||||||
|
const settings = { displayedMaxRows: 10 } as FieldMetadataSettings;
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
service.validateSettingsOrThrow({
|
||||||
|
fieldType: FieldMetadataType.TEXT,
|
||||||
|
settings,
|
||||||
|
}),
|
||||||
|
).resolves.not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an error for invalid TEXT settings', async () => {
|
||||||
|
const settings = {
|
||||||
|
displayedMaxRows: 'NotANumber',
|
||||||
|
} as FieldMetadataSettings;
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
service.validateSettingsOrThrow({
|
||||||
|
fieldType: FieldMetadataType.TEXT,
|
||||||
|
settings,
|
||||||
|
}),
|
||||||
|
).rejects.toThrow(FieldMetadataException);
|
||||||
|
});
|
||||||
|
});
|
@ -172,7 +172,7 @@ const get_subfieldsFromField = (nodeField: NodeField): NodeField[] => {
|
|||||||
description: 'Additional Phones',
|
description: 'Additional Phones',
|
||||||
isNullable: true,
|
isNullable: true,
|
||||||
defaultValue: null,
|
defaultValue: null,
|
||||||
placeholder: '{ number: "", countryCode: "" }',
|
placeholder: '{ number: "", callingCode: "" }',
|
||||||
list: true,
|
list: true,
|
||||||
};
|
};
|
||||||
return [primaryPhoneNumber, primaryPhoneCountryCode, additionalPhones];
|
return [primaryPhoneNumber, primaryPhoneCountryCode, additionalPhones];
|
||||||
|
Loading…
Reference in New Issue
Block a user