Skip to content

Commit

Permalink
Support for multiple values in the Phone field
Browse files Browse the repository at this point in the history
  • Loading branch information
gitstart-twenty committed Sep 10, 2024
1 parent 91187dc commit 57f9246
Show file tree
Hide file tree
Showing 52 changed files with 793 additions and 64 deletions.
1 change: 1 addition & 0 deletions packages/twenty-chrome-extension/src/generated/graphql.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2527,6 +2527,7 @@ export enum FieldMetadataType {
Number = 'NUMBER',
Numeric = 'NUMERIC',
Phone = 'PHONE',
Phones = 'PHONES',
Position = 'POSITION',
Rating = 'RATING',
RawJson = 'RAW_JSON',
Expand Down
1 change: 1 addition & 0 deletions packages/twenty-front/src/generated-metadata/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,7 @@ export enum FieldMetadataType {
Number = 'NUMBER',
Numeric = 'NUMERIC',
Phone = 'PHONE',
Phones = 'PHONES',
Position = 'POSITION',
Rating = 'RATING',
RawJson = 'RAW_JSON',
Expand Down
3 changes: 2 additions & 1 deletion packages/twenty-front/src/generated/graphql.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
import { gql } from '@apollo/client';
export type Maybe<T> = T | null;
export type InputMaybe<T> = Maybe<T>;
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
Expand Down Expand Up @@ -273,6 +273,7 @@ export enum FieldMetadataType {
Number = 'NUMBER',
Numeric = 'NUMERIC',
Phone = 'PHONE',
Phones = 'PHONES',
Position = 'POSITION',
Rating = 'RATING',
RawJson = 'RAW_JSON',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ export const SORTABLE_FIELD_METADATA_TYPES = [
FieldMetadataType.Currency,
FieldMetadataType.Actor,
FieldMetadataType.Links,
FieldMetadataType.Phones,
];
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export const formatFieldMetadataItemsAsFilterDefinitions = ({
FieldMetadataType.Currency,
FieldMetadataType.Rating,
FieldMetadataType.Actor,
FieldMetadataType.Phones,
].includes(field.type)
) {
return acc;
Expand Down Expand Up @@ -83,6 +84,8 @@ export const getFilterTypeFromFieldType = (fieldType: FieldMetadataType) => {
return 'EMAILS';
case FieldMetadataType.Phone:
return 'PHONE';
case FieldMetadataType.Phones:
return 'PHONES';
case FieldMetadataType.Relation:
return 'RELATION';
case FieldMetadataType.Select:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { RecordGqlOperationOrderBy } from '@/object-record/graphql/types/RecordG
import {
FieldEmailsValue,
FieldLinksValue,
FieldPhonesValue,
} from '@/object-record/record-field/types/FieldMetadata';
import { OrderBy } from '@/types/OrderBy';
import { FieldMetadataType } from '~/generated-metadata/graphql';
Expand Down Expand Up @@ -54,6 +55,14 @@ export const getOrderByForFieldMetadataType = (
} satisfies { [key in keyof FieldEmailsValue]?: OrderBy },
},
];
case FieldMetadataType.Phones:
return [
{
[field.name]: {
primaryPhoneNumber: direction ?? 'AscNullsLast',
} satisfies { [key in keyof FieldPhonesValue]?: OrderBy },
},
];
default:
return [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,5 +164,14 @@ ${mapObjectMetadataToGraphQLQuery({
}`;
}

if (fieldType === FieldMetadataType.Phones) {
return `${field.name}
{
primaryPhoneNumber
primaryPhoneCountryCode
additionalPhones
}`;
}

return '';
};
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,11 @@ export type EmailsFilter = {
primaryEmail?: StringFilter;
};

export type PhonesFilter = {
primaryPhoneNumber?: StringFilter;
primaryPhoneCountryCode?: StringFilter;
};

export type LeafFilter =
| UUIDFilter
| StringFilter
Expand All @@ -110,6 +115,7 @@ export type LeafFilter =
| AddressFilter
| LinksFilter
| ActorFilter
| PhonesFilter
| undefined;

export type AndObjectRecordFilter = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export const MultipleFiltersDropdownContent = ({
'LINKS',
'ADDRESS',
'ACTOR',
'PHONES',
].includes(filterDefinitionUsedInDropdown.type) && (
<ObjectFilterDropdownTextSearchInput />
)}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export type FilterType =
| 'TEXT'
| 'PHONE'
| 'PHONES'
| 'EMAIL'
| 'EMAILS'
| 'DATE_TIME'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export const getOperandsForFilterType = (
case 'LINK':
case 'LINKS':
case 'ACTOR':
case 'PHONES':
return [
ViewFilterOperand.Contains,
ViewFilterOperand.DoesNotContain,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { ActorFieldDisplay } from '@/object-record/record-field/meta-types/displ
import { BooleanFieldDisplay } from '@/object-record/record-field/meta-types/display/components/BooleanFieldDisplay';
import { EmailsFieldDisplay } from '@/object-record/record-field/meta-types/display/components/EmailsFieldDisplay';
import { LinksFieldDisplay } from '@/object-record/record-field/meta-types/display/components/LinksFieldDisplay';
import { PhonesFieldDisplay } from '@/object-record/record-field/meta-types/display/components/PhonesFieldDisplay';
import { RatingFieldDisplay } from '@/object-record/record-field/meta-types/display/components/RatingFieldDisplay';
import { RelationFromManyFieldDisplay } from '@/object-record/record-field/meta-types/display/components/RelationFromManyFieldDisplay';
import { RichTextFieldDisplay } from '@/object-record/record-field/meta-types/display/components/RichTextFieldDisplay';
Expand All @@ -13,6 +14,7 @@ import { isFieldBoolean } from '@/object-record/record-field/types/guards/isFiel
import { isFieldDisplayedAsPhone } from '@/object-record/record-field/types/guards/isFieldDisplayedAsPhone';
import { isFieldEmails } from '@/object-record/record-field/types/guards/isFieldEmails';
import { isFieldLinks } from '@/object-record/record-field/types/guards/isFieldLinks';
import { isFieldPhones } from '@/object-record/record-field/types/guards/isFieldPhones';
import { isFieldRating } from '@/object-record/record-field/types/guards/isFieldRating';
import { isFieldRelationFromManyObjects } from '@/object-record/record-field/types/guards/isFieldRelationFromManyObjects';
import { isFieldRelationToOneObject } from '@/object-record/record-field/types/guards/isFieldRelationToOneObject';
Expand Down Expand Up @@ -104,5 +106,7 @@ export const FieldDisplay = () => {
<ActorFieldDisplay />
) : isFieldEmails(fieldDefinition) ? (
<EmailsFieldDisplay />
) : isFieldPhones(fieldDefinition) ? (
<PhonesFieldDisplay />
) : null;
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { EmailsFieldInput } from '@/object-record/record-field/meta-types/input/
import { FullNameFieldInput } from '@/object-record/record-field/meta-types/input/components/FullNameFieldInput';
import { LinksFieldInput } from '@/object-record/record-field/meta-types/input/components/LinksFieldInput';
import { MultiSelectFieldInput } from '@/object-record/record-field/meta-types/input/components/MultiSelectFieldInput';
import { PhonesFieldInput } from '@/object-record/record-field/meta-types/input/components/PhonesFieldInput';
import { RawJsonFieldInput } from '@/object-record/record-field/meta-types/input/components/RawJsonFieldInput';
import { RelationFromManyFieldInput } from '@/object-record/record-field/meta-types/input/components/RelationFromManyFieldInput';
import { SelectFieldInput } from '@/object-record/record-field/meta-types/input/components/SelectFieldInput';
Expand All @@ -16,6 +17,7 @@ import { isFieldEmails } from '@/object-record/record-field/types/guards/isField
import { isFieldFullName } from '@/object-record/record-field/types/guards/isFieldFullName';
import { isFieldLinks } from '@/object-record/record-field/types/guards/isFieldLinks';
import { isFieldMultiSelect } from '@/object-record/record-field/types/guards/isFieldMultiSelect';
import { isFieldPhones } from '@/object-record/record-field/types/guards/isFieldPhones';
import { isFieldRawJson } from '@/object-record/record-field/types/guards/isFieldRawJson';
import { isFieldRelationFromManyObjects } from '@/object-record/record-field/types/guards/isFieldRelationFromManyObjects';
import { isFieldRelationToOneObject } from '@/object-record/record-field/types/guards/isFieldRelationToOneObject';
Expand Down Expand Up @@ -89,6 +91,8 @@ export const FieldInput = ({
onTab={onTab}
onShiftTab={onShiftTab}
/>
) : isFieldPhones(fieldDefinition) ? (
<PhonesFieldInput onCancel={onCancel} />
) : isFieldText(fieldDefinition) ? (
<TextFieldInput
onEnter={onEnter}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import { isFieldLinks } from '@/object-record/record-field/types/guards/isFieldL
import { isFieldLinksValue } from '@/object-record/record-field/types/guards/isFieldLinksValue';
import { isFieldMultiSelect } from '@/object-record/record-field/types/guards/isFieldMultiSelect';
import { isFieldMultiSelectValue } from '@/object-record/record-field/types/guards/isFieldMultiSelectValue';
import { isFieldPhones } from '@/object-record/record-field/types/guards/isFieldPhones';
import { isFieldPhonesValue } from '@/object-record/record-field/types/guards/isFieldPhonesValue';
import { isFieldRawJson } from '@/object-record/record-field/types/guards/isFieldRawJson';
import { isFieldRawJsonValue } from '@/object-record/record-field/types/guards/isFieldRawJsonValue';
import { isFieldRelationToOneObject } from '@/object-record/record-field/types/guards/isFieldRelationToOneObject';
Expand Down Expand Up @@ -104,6 +106,9 @@ export const usePersistField = () => {
const fieldIsPhone =
isFieldPhone(fieldDefinition) && isFieldPhoneValue(valueToPersist);

const fieldIsPhones =
isFieldPhones(fieldDefinition) && isFieldPhonesValue(valueToPersist);

const fieldIsSelect =
isFieldSelect(fieldDefinition) && isFieldSelectValue(valueToPersist);

Expand All @@ -130,6 +135,7 @@ export const usePersistField = () => {
fieldIsDateTime ||
fieldIsDate ||
fieldIsPhone ||
fieldIsPhones ||
fieldIsLink ||
fieldIsLinks ||
fieldIsCurrency ||
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { useFieldFocus } from '@/object-record/record-field/hooks/useFieldFocus';
import { usePhonesFieldDisplay } from '@/object-record/record-field/meta-types/hooks/usePhonesFieldDisplay';
import { PhonesDisplay } from '@/ui/field/display/components/PhonesDisplay';

export const PhonesFieldDisplay = () => {
const { fieldValue } = usePhonesFieldDisplay();

const { isFocused } = useFieldFocus();

return <PhonesDisplay value={fieldValue} isFocused={isFocused} />;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { useContext } from 'react';
import { useRecoilState, useRecoilValue } from 'recoil';

import { usePersistField } from '@/object-record/record-field/hooks/usePersistField';
import { useRecordFieldInput } from '@/object-record/record-field/hooks/useRecordFieldInput';
import { FieldPhonesValue } from '@/object-record/record-field/types/FieldMetadata';
import { isFieldPhones } from '@/object-record/record-field/types/guards/isFieldPhones';
import { phonesSchema } from '@/object-record/record-field/types/guards/isFieldPhonesValue';
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
import { FieldMetadataType } from '~/generated-metadata/graphql';

import { FieldContext } from '../../contexts/FieldContext';
import { assertFieldMetadata } from '../../types/guards/assertFieldMetadata';

export const usePhonesField = () => {
const { recordId, fieldDefinition, hotkeyScope } = useContext(FieldContext);

assertFieldMetadata(FieldMetadataType.Phones, isFieldPhones, fieldDefinition);

const fieldName = fieldDefinition.metadata.fieldName;

const [fieldValue, setFieldValue] = useRecoilState<FieldPhonesValue>(
recordStoreFamilySelector({
recordId,
fieldName: fieldName,
}),
);

const { setDraftValue, getDraftValueSelector } =
useRecordFieldInput<FieldPhonesValue>(`${recordId}-${fieldName}`);

const draftValue = useRecoilValue(getDraftValueSelector());

const persistField = usePersistField();

const persistPhonesField = (nextValue: FieldPhonesValue) => {
try {
persistField(phonesSchema.parse(nextValue));
} catch {
return;
}
};

return {
fieldDefinition,
fieldValue,
draftValue,
setDraftValue,
setFieldValue,
hotkeyScope,
persistPhonesField,
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { useContext } from 'react';

import { FieldPhonesValue } from '@/object-record/record-field/types/FieldMetadata';
import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';

import { FieldContext } from '../../contexts/FieldContext';

export const usePhonesFieldDisplay = () => {
const { recordId, fieldDefinition } = useContext(FieldContext);

const fieldName = fieldDefinition.metadata.fieldName;

const fieldValue = useRecordFieldValue<FieldPhonesValue | undefined>(
recordId,
fieldName,
);

return {
fieldDefinition,
fieldValue,
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useEmailsField } from '@/object-record/record-field/meta-types/hooks/us
import { EmailsFieldMenuItem } from '@/object-record/record-field/meta-types/input/components/EmailsFieldMenuItem';
import { useMemo } from 'react';
import { isDefined } from 'twenty-ui';
import { FieldMetadataType } from '~/generated-metadata/graphql';
import { MultiItemFieldInput } from './MultiItemFieldInput';

type EmailsFieldInputProps = {
Expand Down Expand Up @@ -34,6 +35,7 @@ export const EmailsFieldInput = ({ onCancel }: EmailsFieldInputProps) => {
onPersist={handlePersistEmails}
onCancel={onCancel}
placeholder="Email"
fieldMetadataType={FieldMetadataType.Emails}
renderItem={({
value: email,
index,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useLinksField } from '@/object-record/record-field/meta-types/hooks/use
import { LinksFieldMenuItem } from '@/object-record/record-field/meta-types/input/components/LinksFieldMenuItem';
import { useMemo } from 'react';
import { isDefined } from 'twenty-ui';
import { FieldMetadataType } from '~/generated-metadata/graphql';
import { absoluteUrlSchema } from '~/utils/validation-schemas/absoluteUrlSchema';
import { MultiItemFieldInput } from './MultiItemFieldInput';

Expand Down Expand Up @@ -47,6 +48,7 @@ export const LinksFieldInput = ({ onCancel }: LinksFieldInputProps) => {
onPersist={handlePersistLinks}
onCancel={onCancel}
placeholder="URL"
fieldMetadataType={FieldMetadataType.Links}
validateInput={(input) => absoluteUrlSchema.safeParse(input).success}
formatInput={(input) => ({ url: input, label: '' })}
renderItem={({
Expand Down
Loading

0 comments on commit 57f9246

Please sign in to comment.