diff --git a/front/src/generated/graphql.tsx b/front/src/generated/graphql.tsx index e6d28a1ac..2fc34cbe5 100644 --- a/front/src/generated/graphql.tsx +++ b/front/src/generated/graphql.tsx @@ -620,15 +620,18 @@ export type Company = { accountOwnerId?: Maybe; activities: Array; address: Scalars['String']; + annualRecurringRevenue?: Maybe; comments: Array; createdAt: Scalars['DateTime']; domainName: Scalars['String']; employees?: Maybe; id: Scalars['ID']; + idealCustomerProfile: Scalars['Boolean']; linkedinUrl?: Maybe; name: Scalars['String']; people?: Maybe>; updatedAt: Scalars['DateTime']; + xUrl?: Maybe; }; export type CompanyCreateInput = { @@ -637,26 +640,32 @@ export type CompanyCreateInput = { PipelineProgress?: InputMaybe; accountOwner?: InputMaybe; address: Scalars['String']; + annualRecurringRevenue?: InputMaybe; createdAt?: InputMaybe; domainName: Scalars['String']; employees?: InputMaybe; id?: InputMaybe; + idealCustomerProfile?: InputMaybe; linkedinUrl?: InputMaybe; name: Scalars['String']; people?: InputMaybe; updatedAt?: InputMaybe; + xUrl?: InputMaybe; }; export type CompanyCreateManyInput = { accountOwnerId?: InputMaybe; address: Scalars['String']; + annualRecurringRevenue?: InputMaybe; createdAt?: InputMaybe; domainName: Scalars['String']; employees?: InputMaybe; id?: InputMaybe; + idealCustomerProfile?: InputMaybe; linkedinUrl?: InputMaybe; name: Scalars['String']; updatedAt?: InputMaybe; + xUrl?: InputMaybe; }; export type CompanyCreateNestedOneWithoutActivityTargetInput = { @@ -688,14 +697,17 @@ export type CompanyOrderByWithRelationInput = { accountOwner?: InputMaybe; accountOwnerId?: InputMaybe; address?: InputMaybe; + annualRecurringRevenue?: InputMaybe; createdAt?: InputMaybe; domainName?: InputMaybe; employees?: InputMaybe; id?: InputMaybe; + idealCustomerProfile?: InputMaybe; linkedinUrl?: InputMaybe; name?: InputMaybe; people?: InputMaybe; updatedAt?: InputMaybe; + xUrl?: InputMaybe; }; export type CompanyRelationFilter = { @@ -706,15 +718,18 @@ export type CompanyRelationFilter = { export enum CompanyScalarFieldEnum { AccountOwnerId = 'accountOwnerId', Address = 'address', + AnnualRecurringRevenue = 'annualRecurringRevenue', CreatedAt = 'createdAt', DeletedAt = 'deletedAt', DomainName = 'domainName', Employees = 'employees', Id = 'id', + IdealCustomerProfile = 'idealCustomerProfile', LinkedinUrl = 'linkedinUrl', Name = 'name', UpdatedAt = 'updatedAt', - WorkspaceId = 'workspaceId' + WorkspaceId = 'workspaceId', + XUrl = 'xUrl' } export type CompanyUpdateInput = { @@ -723,14 +738,17 @@ export type CompanyUpdateInput = { PipelineProgress?: InputMaybe; accountOwner?: InputMaybe; address?: InputMaybe; + annualRecurringRevenue?: InputMaybe; createdAt?: InputMaybe; domainName?: InputMaybe; employees?: InputMaybe; id?: InputMaybe; + idealCustomerProfile?: InputMaybe; linkedinUrl?: InputMaybe; name?: InputMaybe; people?: InputMaybe; updatedAt?: InputMaybe; + xUrl?: InputMaybe; }; export type CompanyUpdateManyWithoutAccountOwnerNestedInput = { @@ -765,14 +783,17 @@ export type CompanyWhereInput = { accountOwner?: InputMaybe; accountOwnerId?: InputMaybe; address?: InputMaybe; + annualRecurringRevenue?: InputMaybe; createdAt?: InputMaybe; domainName?: InputMaybe; employees?: InputMaybe; id?: InputMaybe; + idealCustomerProfile?: InputMaybe; linkedinUrl?: InputMaybe; name?: InputMaybe; people?: InputMaybe; updatedAt?: InputMaybe; + xUrl?: InputMaybe; }; export type CompanyWhereUniqueInput = { @@ -3047,7 +3068,7 @@ export type GetClientConfigQueryVariables = Exact<{ [key: string]: never; }>; export type GetClientConfigQuery = { __typename?: 'Query', clientConfig: { __typename?: 'ClientConfig', signInPrefilled: boolean, debugMode: boolean, authProviders: { __typename?: 'AuthProviders', google: boolean, password: boolean }, telemetry: { __typename?: 'Telemetry', enabled: boolean, anonymizationEnabled: boolean }, support: { __typename?: 'Support', supportDriver: string, supportFrontChatId?: string | null } } }; -export type CompanyFieldsFragmentFragment = { __typename?: 'Company', address: string, createdAt: string, domainName: string, employees?: number | null, linkedinUrl?: string | null, id: string, name: string, accountOwner?: { __typename?: 'User', id: string, email: string, displayName: string, avatarUrl?: string | null } | null }; +export type CompanyFieldsFragmentFragment = { __typename?: 'Company', address: string, createdAt: string, domainName: string, employees?: number | null, linkedinUrl?: string | null, xUrl?: string | null, annualRecurringRevenue?: number | null, idealCustomerProfile: boolean, id: string, name: string, accountOwner?: { __typename?: 'User', id: string, email: string, displayName: string, avatarUrl?: string | null } | null }; export type DeleteManyCompaniesMutationVariables = Exact<{ ids?: InputMaybe | Scalars['String']>; @@ -3068,7 +3089,7 @@ export type InsertOneCompanyMutationVariables = Exact<{ }>; -export type InsertOneCompanyMutation = { __typename?: 'Mutation', createOneCompany: { __typename?: 'Company', address: string, createdAt: string, domainName: string, employees?: number | null, linkedinUrl?: string | null, id: string, name: string, accountOwner?: { __typename?: 'User', id: string, email: string, displayName: string, avatarUrl?: string | null } | null } }; +export type InsertOneCompanyMutation = { __typename?: 'Mutation', createOneCompany: { __typename?: 'Company', address: string, createdAt: string, domainName: string, employees?: number | null, linkedinUrl?: string | null, xUrl?: string | null, annualRecurringRevenue?: number | null, idealCustomerProfile: boolean, id: string, name: string, accountOwner?: { __typename?: 'User', id: string, email: string, displayName: string, avatarUrl?: string | null } | null } }; export type UpdateOneCompanyMutationVariables = Exact<{ where: CompanyWhereUniqueInput; @@ -3076,7 +3097,7 @@ export type UpdateOneCompanyMutationVariables = Exact<{ }>; -export type UpdateOneCompanyMutation = { __typename?: 'Mutation', updateOneCompany?: { __typename?: 'Company', address: string, createdAt: string, domainName: string, employees?: number | null, linkedinUrl?: string | null, id: string, name: string, accountOwner?: { __typename?: 'User', id: string, email: string, displayName: string, avatarUrl?: string | null } | null } | null }; +export type UpdateOneCompanyMutation = { __typename?: 'Mutation', updateOneCompany?: { __typename?: 'Company', address: string, createdAt: string, domainName: string, employees?: number | null, linkedinUrl?: string | null, xUrl?: string | null, annualRecurringRevenue?: number | null, idealCustomerProfile: boolean, id: string, name: string, accountOwner?: { __typename?: 'User', id: string, email: string, displayName: string, avatarUrl?: string | null } | null } | null }; export type GetCompaniesQueryVariables = Exact<{ orderBy?: InputMaybe | CompanyOrderByWithRelationInput>; @@ -3084,14 +3105,14 @@ export type GetCompaniesQueryVariables = Exact<{ }>; -export type GetCompaniesQuery = { __typename?: 'Query', companies: Array<{ __typename?: 'Company', id: string, domainName: string, name: string, createdAt: string, address: string, linkedinUrl?: string | null, employees?: number | null, _activityCount: number, accountOwner?: { __typename?: 'User', id: string, email: string, displayName: string, firstName?: string | null, lastName?: string | null, avatarUrl?: string | null } | null }> }; +export type GetCompaniesQuery = { __typename?: 'Query', companies: Array<{ __typename?: 'Company', id: string, domainName: string, name: string, createdAt: string, address: string, linkedinUrl?: string | null, xUrl?: string | null, annualRecurringRevenue?: number | null, idealCustomerProfile: boolean, employees?: number | null, _activityCount: number, accountOwner?: { __typename?: 'User', id: string, email: string, displayName: string, firstName?: string | null, lastName?: string | null, avatarUrl?: string | null } | null }> }; export type GetCompanyQueryVariables = Exact<{ where: CompanyWhereUniqueInput; }>; -export type GetCompanyQuery = { __typename?: 'Query', findUniqueCompany: { __typename?: 'Company', id: string, domainName: string, name: string, createdAt: string, address: string, linkedinUrl?: string | null, employees?: number | null, _activityCount: number, accountOwner?: { __typename?: 'User', id: string, email: string, displayName: string, avatarUrl?: string | null } | null, Favorite?: Array<{ __typename?: 'Favorite', id: string, person?: { __typename?: 'Person', id: string } | null, company?: { __typename?: 'Company', id: string } | null }> | null } }; +export type GetCompanyQuery = { __typename?: 'Query', findUniqueCompany: { __typename?: 'Company', id: string, domainName: string, name: string, createdAt: string, address: string, linkedinUrl?: string | null, xUrl?: string | null, annualRecurringRevenue?: number | null, idealCustomerProfile: boolean, employees?: number | null, _activityCount: number, accountOwner?: { __typename?: 'User', id: string, email: string, displayName: string, avatarUrl?: string | null } | null, Favorite?: Array<{ __typename?: 'Favorite', id: string, person?: { __typename?: 'Person', id: string } | null, company?: { __typename?: 'Company', id: string } | null }> | null } }; export type DeleteFavoriteMutationVariables = Exact<{ where: FavoriteWhereInput; @@ -3302,7 +3323,7 @@ export type SearchCompanyQueryVariables = Exact<{ }>; -export type SearchCompanyQuery = { __typename?: 'Query', searchResults: Array<{ __typename?: 'Company', address: string, createdAt: string, domainName: string, employees?: number | null, linkedinUrl?: string | null, id: string, name: string, accountOwner?: { __typename?: 'User', id: string, email: string, displayName: string, avatarUrl?: string | null } | null }> }; +export type SearchCompanyQuery = { __typename?: 'Query', searchResults: Array<{ __typename?: 'Company', address: string, createdAt: string, domainName: string, employees?: number | null, linkedinUrl?: string | null, xUrl?: string | null, annualRecurringRevenue?: number | null, idealCustomerProfile: boolean, id: string, name: string, accountOwner?: { __typename?: 'User', id: string, email: string, displayName: string, avatarUrl?: string | null } | null }> }; export type SearchPeopleQueryVariables = Exact<{ where?: InputMaybe; @@ -3628,6 +3649,9 @@ export const CompanyFieldsFragmentFragmentDoc = gql` domainName employees linkedinUrl + xUrl + annualRecurringRevenue + idealCustomerProfile id name } @@ -4514,6 +4538,9 @@ export const GetCompaniesDocument = gql` createdAt address linkedinUrl + xUrl + annualRecurringRevenue + idealCustomerProfile employees _activityCount accountOwner { @@ -4565,6 +4592,9 @@ export const GetCompanyDocument = gql` createdAt address linkedinUrl + xUrl + annualRecurringRevenue + idealCustomerProfile employees _activityCount accountOwner { diff --git a/front/src/modules/companies/constants/companyViewFields.tsx b/front/src/modules/companies/constants/companyViewFields.tsx index 57b476160..6036e0e7f 100644 --- a/front/src/modules/companies/constants/companyViewFields.tsx +++ b/front/src/modules/companies/constants/companyViewFields.tsx @@ -1,8 +1,10 @@ import { + ViewFieldBooleanMetadata, ViewFieldChipMetadata, ViewFieldDateMetadata, ViewFieldDefinition, ViewFieldMetadata, + ViewFieldMoneyMetadata, ViewFieldNumberMetadata, ViewFieldRelationMetadata, ViewFieldTextMetadata, @@ -10,10 +12,13 @@ import { } from '@/ui/editable-field/types/ViewField'; import { IconBrandLinkedin, + IconBrandX, IconBuildingSkyscraper, IconCalendarEvent, IconLink, IconMap, + IconMoneybag, + IconTarget, IconUserCircle, IconUsers, } from '@/ui/icon/index'; @@ -111,4 +116,40 @@ export const companyViewFields: ViewFieldDefinition[] = [ }, isVisible: true, } satisfies ViewFieldDefinition, + { + id: 'idealCustomerProfile', + columnLabel: 'ICP', + columnIcon: , + columnSize: 150, + columnOrder: 8, + metadata: { + type: 'boolean', + fieldName: 'idealCustomerProfile', + }, + isVisible: false, + } satisfies ViewFieldDefinition, + { + id: 'annualRecurringRevenue', + columnLabel: 'ARR', + columnIcon: , + columnSize: 150, + columnOrder: 8, + metadata: { + type: 'moneyAmount', + fieldName: 'annualRecurringRevenue', + }, + } satisfies ViewFieldDefinition, + { + id: 'xUrl', + columnLabel: 'Twitter', + columnIcon: , + columnSize: 150, + columnOrder: 8, + metadata: { + type: 'url', + fieldName: 'xUrl', + placeHolder: 'X', + }, + isVisible: false, + } satisfies ViewFieldDefinition, ]; diff --git a/front/src/modules/companies/graphql/fragments/companyFieldsFragment.ts b/front/src/modules/companies/graphql/fragments/companyFieldsFragment.ts index 064b91927..af48e99c3 100644 --- a/front/src/modules/companies/graphql/fragments/companyFieldsFragment.ts +++ b/front/src/modules/companies/graphql/fragments/companyFieldsFragment.ts @@ -13,6 +13,9 @@ export const COMPANY_FIELDS_FRAGMENT = gql` domainName employees linkedinUrl + xUrl + annualRecurringRevenue + idealCustomerProfile id name } diff --git a/front/src/modules/companies/graphql/queries/getCompanies.ts b/front/src/modules/companies/graphql/queries/getCompanies.ts index a8a7c5a17..cec3df517 100644 --- a/front/src/modules/companies/graphql/queries/getCompanies.ts +++ b/front/src/modules/companies/graphql/queries/getCompanies.ts @@ -12,6 +12,9 @@ export const GET_COMPANIES = gql` createdAt address linkedinUrl + xUrl + annualRecurringRevenue + idealCustomerProfile employees _activityCount accountOwner { diff --git a/front/src/modules/companies/graphql/queries/getCompany.ts b/front/src/modules/companies/graphql/queries/getCompany.ts index 6d979eab3..f9a5b34fc 100644 --- a/front/src/modules/companies/graphql/queries/getCompany.ts +++ b/front/src/modules/companies/graphql/queries/getCompany.ts @@ -9,6 +9,9 @@ export const GET_COMPANY = gql` createdAt address linkedinUrl + xUrl + annualRecurringRevenue + idealCustomerProfile employees _activityCount accountOwner { diff --git a/front/src/modules/companies/table/components/companies-mock-data.ts b/front/src/modules/companies/table/components/companies-mock-data.ts index 9455a3920..534f80056 100644 --- a/front/src/modules/companies/table/components/companies-mock-data.ts +++ b/front/src/modules/companies/table/components/companies-mock-data.ts @@ -10,6 +10,9 @@ type MockedCompany = Pick< | 'address' | 'employees' | 'linkedinUrl' + | 'xUrl' + | 'annualRecurringRevenue' + | 'idealCustomerProfile' | '_activityCount' > & { accountOwner: Pick< @@ -30,6 +33,9 @@ export const mockedCompaniesData: Array = [ domainName: 'airbnb.com', name: 'Airbnb', linkedinUrl: 'https://www.linkedin.com/company/airbnb/', + xUrl: 'https://twitter.com/airbnb', + annualRecurringRevenue: 500000, + idealCustomerProfile: true, createdAt: '2023-04-26T10:08:54.724515+00:00', address: 'San Francisco, CA', employees: 5000, @@ -50,6 +56,9 @@ export const mockedCompaniesData: Array = [ domainName: 'qonto.com', name: 'Qonto', linkedinUrl: 'https://www.linkedin.com/company/qonto/', + xUrl: 'https://twitter.com/qonto', + annualRecurringRevenue: 500000, + idealCustomerProfile: false, createdAt: '2023-04-26T10:12:42.33625+00:00', address: 'Paris, France', employees: 800, @@ -62,6 +71,9 @@ export const mockedCompaniesData: Array = [ domainName: 'stripe.com', name: 'Stripe', linkedinUrl: 'https://www.linkedin.com/company/stripe/', + xUrl: 'https://twitter.com/stripe', + annualRecurringRevenue: 5000000, + idealCustomerProfile: false, createdAt: '2023-04-26T10:10:32.530184+00:00', address: 'San Francisco, CA', employees: 8000, @@ -73,6 +85,9 @@ export const mockedCompaniesData: Array = [ id: 'b1cfd51b-a831-455f-ba07-4e30671e1dc3', domainName: 'figma.com', linkedinUrl: 'https://www.linkedin.com/company/figma/', + xUrl: 'https://twitter.com/figma', + annualRecurringRevenue: 50000, + idealCustomerProfile: true, name: 'Figma', createdAt: '2023-03-21T06:30:25.39474+00:00', address: 'San Francisco, CA', @@ -85,6 +100,9 @@ export const mockedCompaniesData: Array = [ id: '5c21e19e-e049-4393-8c09-3e3f8fb09ecb', domainName: 'notion.com', linkedinUrl: 'https://www.linkedin.com/company/notion/', + xUrl: 'https://twitter.com/notion', + annualRecurringRevenue: 500000, + idealCustomerProfile: false, name: 'Notion', createdAt: '2023-04-26T10:13:29.712485+00:00', address: 'San Francisco, CA', diff --git a/front/src/modules/ui/editable-field/types/FieldMetadata.ts b/front/src/modules/ui/editable-field/types/FieldMetadata.ts index b7afa92f9..dc2f4fa66 100644 --- a/front/src/modules/ui/editable-field/types/FieldMetadata.ts +++ b/front/src/modules/ui/editable-field/types/FieldMetadata.ts @@ -9,10 +9,12 @@ export type FieldType = | 'double-text-chip' | 'double-text' | 'number' + | 'boolean' | 'date' | 'phone' | 'url' - | 'probability'; + | 'probability' + | 'moneyAmount'; export type FieldTextMetadata = { placeHolder: string; diff --git a/front/src/modules/ui/editable-field/types/ViewField.ts b/front/src/modules/ui/editable-field/types/ViewField.ts index f1386e15c..db2fea2f0 100644 --- a/front/src/modules/ui/editable-field/types/ViewField.ts +++ b/front/src/modules/ui/editable-field/types/ViewField.ts @@ -11,7 +11,9 @@ export type ViewFieldType = | 'date' | 'phone' | 'url' - | 'probability'; + | 'probability' + | 'boolean' + | 'moneyAmount'; export type ViewFieldTextMetadata = { type: 'text'; @@ -42,6 +44,16 @@ export type ViewFieldNumberMetadata = { isPositive?: boolean; }; +export type ViewFieldMoneyMetadata = { + type: 'moneyAmount'; + fieldName: string; +}; + +export type ViewFieldBooleanMetadata = { + type: 'boolean'; + fieldName: string; +}; + export type ViewFieldRelationMetadata = { type: 'relation'; relationType: Entity; @@ -89,8 +101,10 @@ export type ViewFieldMetadata = { type: ViewFieldType } & ( | ViewFieldPhoneMetadata | ViewFieldURLMetadata | ViewFieldNumberMetadata + | ViewFieldBooleanMetadata | ViewFieldDateMetadata | ViewFieldProbabilityMetadata + | ViewFieldMoneyMetadata ); export type ViewFieldDefinition = { @@ -109,6 +123,8 @@ export type ViewFieldTextValue = string; export type ViewFieldChipValue = string; export type ViewFieldDateValue = string; export type ViewFieldPhoneValue = string; +export type ViewFieldBooleanValue = boolean; +export type ViewFieldMoneyValue = number; export type ViewFieldURLValue = string; export type ViewFieldNumberValue = number | null; export type ViewFieldProbabilityValue = number; diff --git a/front/src/modules/ui/editable-field/types/guards/isViewFieldBoolean.ts b/front/src/modules/ui/editable-field/types/guards/isViewFieldBoolean.ts new file mode 100644 index 000000000..244b7691e --- /dev/null +++ b/front/src/modules/ui/editable-field/types/guards/isViewFieldBoolean.ts @@ -0,0 +1,11 @@ +import { + ViewFieldBooleanMetadata, + ViewFieldDefinition, + ViewFieldMetadata, +} from '../ViewField'; + +export function isViewFieldBoolean( + field: ViewFieldDefinition, +): field is ViewFieldDefinition { + return field.metadata.type === 'boolean'; +} diff --git a/front/src/modules/ui/editable-field/types/guards/isViewFieldBooleanValue.ts b/front/src/modules/ui/editable-field/types/guards/isViewFieldBooleanValue.ts new file mode 100644 index 000000000..bf1516ee0 --- /dev/null +++ b/front/src/modules/ui/editable-field/types/guards/isViewFieldBooleanValue.ts @@ -0,0 +1,7 @@ +import { ViewFieldBooleanValue } from '../ViewField'; + +export function isViewFieldBooleanValue( + fieldValue: unknown, +): fieldValue is ViewFieldBooleanValue { + return typeof fieldValue === 'boolean'; +} diff --git a/front/src/modules/ui/editable-field/types/guards/isViewFieldMoney.ts b/front/src/modules/ui/editable-field/types/guards/isViewFieldMoney.ts new file mode 100644 index 000000000..5e6f8c10a --- /dev/null +++ b/front/src/modules/ui/editable-field/types/guards/isViewFieldMoney.ts @@ -0,0 +1,11 @@ +import { + ViewFieldDefinition, + ViewFieldMetadata, + ViewFieldMoneyMetadata, +} from '../ViewField'; + +export function isViewFieldMoney( + field: ViewFieldDefinition, +): field is ViewFieldDefinition { + return field.metadata.type === 'moneyAmount'; +} diff --git a/front/src/modules/ui/editable-field/types/guards/isViewFieldMoneyValue.ts b/front/src/modules/ui/editable-field/types/guards/isViewFieldMoneyValue.ts new file mode 100644 index 000000000..11983b9e6 --- /dev/null +++ b/front/src/modules/ui/editable-field/types/guards/isViewFieldMoneyValue.ts @@ -0,0 +1,7 @@ +import { ViewFieldMoneyValue } from '../ViewField'; + +export function isViewFieldMoneyValue( + fieldValue: unknown, +): fieldValue is ViewFieldMoneyValue { + return typeof fieldValue === 'number'; +} diff --git a/front/src/modules/ui/icon/index.ts b/front/src/modules/ui/icon/index.ts index 19067fc14..fe324d8a9 100644 --- a/front/src/modules/ui/icon/index.ts +++ b/front/src/modules/ui/icon/index.ts @@ -45,6 +45,7 @@ export { IconMail, IconMap, IconMinus, + IconMoneybag, IconNotes, IconPencil, IconPhone, @@ -53,6 +54,7 @@ export { IconSearch, IconSettings, IconTag, + IconTarget, IconTargetArrow, IconTimelineEvent, IconTrash, diff --git a/front/src/modules/ui/table/editable-cell/components/GenericEditableCell.tsx b/front/src/modules/ui/table/editable-cell/components/GenericEditableCell.tsx index 8e37f77e4..941e96429 100644 --- a/front/src/modules/ui/table/editable-cell/components/GenericEditableCell.tsx +++ b/front/src/modules/ui/table/editable-cell/components/GenericEditableCell.tsx @@ -1,6 +1,8 @@ +import { isViewFieldBoolean } from '@/ui/editable-field/types/guards/isViewFieldBoolean'; import { isViewFieldDate } from '@/ui/editable-field/types/guards/isViewFieldDate'; import { isViewFieldDoubleText } from '@/ui/editable-field/types/guards/isViewFieldDoubleText'; import { isViewFieldDoubleTextChip } from '@/ui/editable-field/types/guards/isViewFieldDoubleTextChip'; +import { isViewFieldMoney } from '@/ui/editable-field/types/guards/isViewFieldMoney'; import { isViewFieldNumber } from '@/ui/editable-field/types/guards/isViewFieldNumber'; import { isViewFieldPhone } from '@/ui/editable-field/types/guards/isViewFieldPhone'; import { isViewFieldRelation } from '@/ui/editable-field/types/guards/isViewFieldRelation'; @@ -12,10 +14,12 @@ import { } from '@/ui/editable-field/types/ViewField'; import { isViewFieldChip } from '../../../editable-field/types/guards/isViewFieldChip'; +import { GenericEditableBooleanCell } from '../type/components/GenericEditableBooleanCell'; import { GenericEditableChipCell } from '../type/components/GenericEditableChipCell'; import { GenericEditableDateCell } from '../type/components/GenericEditableDateCell'; import { GenericEditableDoubleTextCell } from '../type/components/GenericEditableDoubleTextCell'; import { GenericEditableDoubleTextChipCell } from '../type/components/GenericEditableDoubleTextChipCell'; +import { GenericEditableMoneyCell } from '../type/components/GenericEditableMoneyCell'; import { GenericEditableNumberCell } from '../type/components/GenericEditableNumberCell'; import { GenericEditablePhoneCell } from '../type/components/GenericEditablePhoneCell'; import { GenericEditableRelationCell } from '../type/components/GenericEditableRelationCell'; @@ -43,8 +47,12 @@ export function GenericEditableCell({ viewField: fieldDefinition }: OwnProps) { return ; } else if (isViewFieldNumber(fieldDefinition)) { return ; + } else if (isViewFieldBoolean(fieldDefinition)) { + return ; } else if (isViewFieldChip(fieldDefinition)) { return ; + } else if (isViewFieldMoney(fieldDefinition)) { + return ; } else { console.warn( `Unknown field metadata type: ${fieldDefinition.metadata.type} in GenericEditableCell`, diff --git a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableBooleanCell.tsx b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableBooleanCell.tsx new file mode 100644 index 000000000..a6666b5f8 --- /dev/null +++ b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableBooleanCell.tsx @@ -0,0 +1,77 @@ +import styled from '@emotion/styled'; +import { IconCheck, IconX } from '@tabler/icons-react'; +import { useRecoilState } from 'recoil'; + +import { + ViewFieldBooleanMetadata, + ViewFieldDefinition, +} from '@/ui/editable-field/types/ViewField'; +import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId'; +import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField'; +import { tableEntityFieldFamilySelector } from '@/ui/table/states/selectors/tableEntityFieldFamilySelector'; + +import { EditableCellDisplayContainer } from '../../components/EditableCellContainer'; + +type OwnProps = { + viewField: ViewFieldDefinition; + editModeHorizontalAlign?: 'left' | 'right'; +}; + +const StyledCellBaseContainer = styled.div` + align-items: center; + box-sizing: border-box; + cursor: pointer; + display: flex; + height: ${({ theme }) => theme.spacing(8)}; + position: relative; + user-select: none; + width: 100%; +`; + +const StyledCellBooleancontainer = styled.div` + margin-left: 5px; +`; + +function capitalizeFirstLetter(value: string) { + return value.charAt(0).toUpperCase() + value.slice(1); +} + +export function GenericEditableBooleanCell({ viewField }: OwnProps) { + const currentRowEntityId = useCurrentRowEntityId(); + + const [fieldValue, setFieldValue] = useRecoilState( + tableEntityFieldFamilySelector({ + entityId: currentRowEntityId ?? '', + fieldName: viewField.metadata.fieldName, + }), + ); + + const updateField = useUpdateEntityField(); + + function handleClick() { + const newValue = !fieldValue; + try { + setFieldValue(newValue); + + if (currentRowEntityId && updateField) { + updateField(currentRowEntityId, viewField, newValue); + } + } catch (error) { + console.warn( + `In GenericEditableBooleanCellEditMode, Invalid value: ${newValue}, ${error}`, + ); + } + } + + return ( + + + {fieldValue ? : } + + {fieldValue !== undefined && + capitalizeFirstLetter(fieldValue.toString())} + + + + ); +} diff --git a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableMoneyCell.tsx b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableMoneyCell.tsx new file mode 100644 index 000000000..469a627d6 --- /dev/null +++ b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableMoneyCell.tsx @@ -0,0 +1,47 @@ +import { useRecoilValue } from 'recoil'; + +import { + ViewFieldDefinition, + ViewFieldMoneyMetadata, +} from '@/ui/editable-field/types/ViewField'; +import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell'; +import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId'; +import { tableEntityFieldFamilySelector } from '@/ui/table/states/selectors/tableEntityFieldFamilySelector'; + +import { GenericEditableMoneyCellEditMode } from './GenericEditableMoneyCellEditMode'; + +type OwnProps = { + viewField: ViewFieldDefinition; + editModeHorizontalAlign?: 'left' | 'right'; +}; + +function formatNumber(value: number) { + // Formats the value to a string and add commas to it ex: 50,000 | 500,000 + return value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ','); +} + +export function GenericEditableMoneyCell({ + viewField, + editModeHorizontalAlign, +}: OwnProps) { + const currentRowEntityId = useCurrentRowEntityId(); + + const fieldValue = useRecoilValue( + tableEntityFieldFamilySelector({ + entityId: currentRowEntityId ?? '', + fieldName: viewField.metadata.fieldName, + }), + ); + + return ( + + } + nonEditModeContent={ + <>{fieldValue ? `$${formatNumber(fieldValue)}` : ''} + } + > + ); +} diff --git a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableMoneyCellEditMode.tsx b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableMoneyCellEditMode.tsx new file mode 100644 index 000000000..a591b4c48 --- /dev/null +++ b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableMoneyCellEditMode.tsx @@ -0,0 +1,58 @@ +import { useRecoilState } from 'recoil'; + +import { + ViewFieldDefinition, + ViewFieldMoneyMetadata, +} from '@/ui/editable-field/types/ViewField'; +import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId'; +import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField'; +import { tableEntityFieldFamilySelector } from '@/ui/table/states/selectors/tableEntityFieldFamilySelector'; + +import { TextCellEdit } from './TextCellEdit'; + +type OwnProps = { + viewField: ViewFieldDefinition; +}; + +export function GenericEditableMoneyCellEditMode({ viewField }: OwnProps) { + const currentRowEntityId = useCurrentRowEntityId(); + + const [fieldValue, setFieldValue] = useRecoilState( + tableEntityFieldFamilySelector({ + entityId: currentRowEntityId ?? '', + fieldName: viewField.metadata.fieldName, + }), + ); + + const updateField = useUpdateEntityField(); + + function handleSubmit(newText: string) { + if (newText === fieldValue) return; + + try { + const numberValue = parseInt(newText); + + if (isNaN(numberValue)) { + throw new Error('Not a number'); + } + + if (numberValue > 2000000000) { + throw new Error('Number too big'); + } + + setFieldValue(numberValue.toString()); + + if (currentRowEntityId && updateField) { + updateField(currentRowEntityId, viewField, numberValue); + } + } catch (error) { + console.warn( + `In GenericEditableMoneyCellEditMode, Invalid number: ${newText}, ${error}`, + ); + } + } + + return ( + + ); +} diff --git a/front/src/modules/ui/table/hooks/useUpdateEntityField.ts b/front/src/modules/ui/table/hooks/useUpdateEntityField.ts index bc1e59a54..d0c3a03af 100644 --- a/front/src/modules/ui/table/hooks/useUpdateEntityField.ts +++ b/front/src/modules/ui/table/hooks/useUpdateEntityField.ts @@ -1,5 +1,7 @@ import { useContext } from 'react'; +import { isViewFieldBoolean } from '@/ui/editable-field/types/guards/isViewFieldBoolean'; +import { isViewFieldBooleanValue } from '@/ui/editable-field/types/guards/isViewFieldBooleanValue'; import { isViewFieldChip } from '@/ui/editable-field/types/guards/isViewFieldChip'; import { isViewFieldDate } from '@/ui/editable-field/types/guards/isViewFieldDate'; import { isViewFieldDateValue } from '@/ui/editable-field/types/guards/isViewFieldDateValue'; @@ -7,6 +9,8 @@ import { isViewFieldDoubleText } from '@/ui/editable-field/types/guards/isViewFi import { isViewFieldDoubleTextChip } from '@/ui/editable-field/types/guards/isViewFieldDoubleTextChip'; import { isViewFieldDoubleTextChipValue } from '@/ui/editable-field/types/guards/isViewFieldDoubleTextChipValue'; import { isViewFieldDoubleTextValue } from '@/ui/editable-field/types/guards/isViewFieldDoubleTextValue'; +import { isViewFieldMoney } from '@/ui/editable-field/types/guards/isViewFieldMoney'; +import { isViewFieldMoneyValue } from '@/ui/editable-field/types/guards/isViewFieldMoneyValue'; import { isViewFieldNumber } from '@/ui/editable-field/types/guards/isViewFieldNumber'; import { isViewFieldNumberValue } from '@/ui/editable-field/types/guards/isViewFieldNumberValue'; import { isViewFieldPhone } from '@/ui/editable-field/types/guards/isViewFieldPhone'; @@ -202,6 +206,32 @@ export function useUpdateEntityField() { ) { const newContent = newFieldValueUnknown; + updateEntity({ + variables: { + where: { id: currentEntityId }, + data: { [viewField.metadata.fieldName]: newContent }, + }, + }); + // Boolean + } else if ( + isViewFieldBoolean(viewField) && + isViewFieldBooleanValue(newFieldValueUnknown) + ) { + const newContent = newFieldValueUnknown; + + updateEntity({ + variables: { + where: { id: currentEntityId }, + data: { [viewField.metadata.fieldName]: newContent }, + }, + }); + // Money + } else if ( + isViewFieldMoney(viewField) && + isViewFieldMoneyValue(newFieldValueUnknown) + ) { + const newContent = newFieldValueUnknown; + updateEntity({ variables: { where: { id: currentEntityId }, diff --git a/front/src/testing/mock-data/companies.ts b/front/src/testing/mock-data/companies.ts index 9818d07c0..77fa6f764 100644 --- a/front/src/testing/mock-data/companies.ts +++ b/front/src/testing/mock-data/companies.ts @@ -11,6 +11,9 @@ type MockedCompany = Pick< | 'address' | 'employees' | 'linkedinUrl' + | 'xUrl' + | 'annualRecurringRevenue' + | 'idealCustomerProfile' | '_activityCount' > & { accountOwner: Pick< @@ -34,6 +37,9 @@ export const mockedCompaniesData: Array = [ address: '17 rue de clignancourt', employees: 12, linkedinUrl: 'https://www.linkedin.com/company/airbnb/', + xUrl: 'https://twitter.com/airbnb', + annualRecurringRevenue: 500000, + idealCustomerProfile: true, _activityCount: 1, accountOwner: { email: 'charles@test.com', @@ -54,6 +60,9 @@ export const mockedCompaniesData: Array = [ address: '', employees: 1, linkedinUrl: 'https://www.linkedin.com/company/aircall/', + xUrl: 'https://twitter.com/aircall', + annualRecurringRevenue: 50000, + idealCustomerProfile: false, _activityCount: 1, accountOwner: null, __typename: 'Company', @@ -66,6 +75,9 @@ export const mockedCompaniesData: Array = [ address: '', employees: 1, linkedinUrl: 'https://www.linkedin.com/company/algolia/', + xUrl: 'https://twitter.com/algolia', + annualRecurringRevenue: 500000, + idealCustomerProfile: true, _activityCount: 1, accountOwner: null, __typename: 'Company', @@ -78,6 +90,9 @@ export const mockedCompaniesData: Array = [ address: '', employees: 10, linkedinUrl: 'https://www.linkedin.com/company/apple/', + xUrl: 'https://twitter.com/apple', + annualRecurringRevenue: 5000000, + idealCustomerProfile: false, _activityCount: 0, accountOwner: null, __typename: 'Company', @@ -90,6 +105,9 @@ export const mockedCompaniesData: Array = [ address: '10 rue de la Paix', employees: 1, linkedinUrl: 'https://www.linkedin.com/company/qonto/', + xUrl: 'https://twitter.com/qonto', + annualRecurringRevenue: 500000, + idealCustomerProfile: false, _activityCount: 2, accountOwner: null, __typename: 'Company', @@ -102,6 +120,9 @@ export const mockedCompaniesData: Array = [ address: '', employees: 1, linkedinUrl: 'https://www.linkedin.com/company/facebook/', + xUrl: 'https://twitter.com/facebook', + annualRecurringRevenue: 5000000, + idealCustomerProfile: true, _activityCount: 13, accountOwner: null, __typename: 'Company', @@ -114,6 +135,9 @@ export const mockedCompaniesData: Array = [ address: '', employees: 1, linkedinUrl: 'https://www.linkedin.com/company/sequoia/', + xUrl: 'https://twitter.com/sequoia', + annualRecurringRevenue: 500000, + idealCustomerProfile: true, _activityCount: 1, accountOwner: null, __typename: 'Company', diff --git a/server/src/database/migrations/20230817201132_add_new_company_fields/migration.sql b/server/src/database/migrations/20230817201132_add_new_company_fields/migration.sql new file mode 100644 index 000000000..5352bde5d --- /dev/null +++ b/server/src/database/migrations/20230817201132_add_new_company_fields/migration.sql @@ -0,0 +1,4 @@ +-- AlterTable +ALTER TABLE "companies" ADD COLUMN "annualRecurringRevenue" INTEGER, +ADD COLUMN "idealCustomerProfile" BOOLEAN DEFAULT false, +ADD COLUMN "xUrl" TEXT; diff --git a/server/src/database/schema.prisma b/server/src/database/schema.prisma index beab9187e..1efbbdc5a 100644 --- a/server/src/database/schema.prisma +++ b/server/src/database/schema.prisma @@ -226,6 +226,15 @@ model Company { /// @Validator.IsString() /// @Validator.IsOptional() linkedinUrl String? + /// @Validator.IsNumber() + /// @Validator.IsOptional() + annualRecurringRevenue Int? + /// @Validator.IsBoolean() + /// @Validator.IsOptional() + idealCustomerProfile Boolean @default(false) + /// @Validator.IsString() + /// @Validator.IsOptional() + xUrl String? /// @Validator.IsString() /// @Validator.IsOptional() address String