From 73e9104b169bdeee9a3b8e2234a1a017156e0969 Mon Sep 17 00:00:00 2001 From: Emilien Chauvet Date: Fri, 21 Jul 2023 15:18:19 -0700 Subject: [PATCH] Add linkedinUrl and job titles to table views (#809) * Add linedinUrl and job titles to table views * Keep address in the end * Add mock data --- front/src/generated/graphql.tsx | 46 +++++++++++++++---- front/src/modules/companies/queries/select.ts | 1 + front/src/modules/companies/queries/show.ts | 1 + front/src/modules/companies/queries/update.ts | 2 + .../states/companyLinkedinUrlFamilyState.ts | 6 +++ .../EditableCompanyLinkedinUrlCell.tsx | 42 +++++++++++++++++ .../table/components/companies-mock-data.ts | 6 +++ .../table/components/companyColumns.tsx | 9 ++++ .../table/hooks/useSetCompanyEntityTable.ts | 12 +++++ .../people/hooks/useSetPeopleEntityTable.ts | 21 +++++++++ front/src/modules/people/queries/select.ts | 2 + front/src/modules/people/queries/show.ts | 2 + front/src/modules/people/queries/update.ts | 4 ++ .../states/peopleJobTitleFamilyState.ts | 6 +++ .../states/peopleLinkedinUrlFamilyState.ts | 6 +++ .../components/EditablePeopleJobTitleCell.tsx | 43 +++++++++++++++++ .../EditablePeopleLinkedinUrlCell.tsx | 44 ++++++++++++++++++ .../people/table/components/peopleColumns.tsx | 18 ++++++++ front/src/modules/ui/icon/index.ts | 2 + front/src/testing/mock-data/people.ts | 10 ++++ .../migration.sql | 6 +++ server/src/database/schema.prisma | 31 ++++++++----- 22 files changed, 301 insertions(+), 19 deletions(-) create mode 100644 front/src/modules/companies/states/companyLinkedinUrlFamilyState.ts create mode 100644 front/src/modules/companies/table/components/EditableCompanyLinkedinUrlCell.tsx create mode 100644 front/src/modules/people/states/peopleJobTitleFamilyState.ts create mode 100644 front/src/modules/people/states/peopleLinkedinUrlFamilyState.ts create mode 100644 front/src/modules/people/table/components/EditablePeopleJobTitleCell.tsx create mode 100644 front/src/modules/people/table/components/EditablePeopleLinkedinUrlCell.tsx create mode 100644 server/src/database/migrations/20230721210543_add_linkedin_url_and_job_title_fields/migration.sql diff --git a/front/src/generated/graphql.tsx b/front/src/generated/graphql.tsx index d190e0081..0b8457e94 100644 --- a/front/src/generated/graphql.tsx +++ b/front/src/generated/graphql.tsx @@ -481,6 +481,7 @@ export type Company = { domainName: Scalars['String']; employees?: Maybe; id: Scalars['ID']; + linkedinUrl?: Maybe; name: Scalars['String']; people?: Maybe>; updatedAt: Scalars['DateTime']; @@ -493,6 +494,7 @@ export type CompanyCreateInput = { domainName: Scalars['String']; employees?: InputMaybe; id?: InputMaybe; + linkedinUrl?: InputMaybe; name: Scalars['String']; people?: InputMaybe; updatedAt?: InputMaybe; @@ -520,6 +522,7 @@ export type CompanyOrderByWithRelationInput = { domainName?: InputMaybe; employees?: InputMaybe; id?: InputMaybe; + linkedinUrl?: InputMaybe; name?: InputMaybe; people?: InputMaybe; updatedAt?: InputMaybe; @@ -538,6 +541,7 @@ export enum CompanyScalarFieldEnum { DomainName = 'domainName', Employees = 'employees', Id = 'id', + LinkedinUrl = 'linkedinUrl', Name = 'name', UpdatedAt = 'updatedAt', WorkspaceId = 'workspaceId' @@ -550,6 +554,7 @@ export type CompanyUpdateInput = { domainName?: InputMaybe; employees?: InputMaybe; id?: InputMaybe; + linkedinUrl?: InputMaybe; name?: InputMaybe; people?: InputMaybe; updatedAt?: InputMaybe; @@ -583,6 +588,7 @@ export type CompanyWhereInput = { domainName?: InputMaybe; employees?: InputMaybe; id?: InputMaybe; + linkedinUrl?: InputMaybe; name?: InputMaybe; people?: InputMaybe; updatedAt?: InputMaybe; @@ -980,7 +986,9 @@ export type Person = { email?: Maybe; firstName?: Maybe; id: Scalars['ID']; + jobTitle?: Maybe; lastName?: Maybe; + linkedinUrl?: Maybe; phone?: Maybe; pipelineProgresses?: Maybe>; updatedAt: Scalars['DateTime']; @@ -993,7 +1001,9 @@ export type PersonCreateInput = { email?: InputMaybe; firstName?: InputMaybe; id?: InputMaybe; + jobTitle?: InputMaybe; lastName?: InputMaybe; + linkedinUrl?: InputMaybe; phone?: InputMaybe; pipelineProgresses?: InputMaybe; updatedAt?: InputMaybe; @@ -1025,7 +1035,9 @@ export type PersonOrderByWithRelationInput = { email?: InputMaybe; firstName?: InputMaybe; id?: InputMaybe; + jobTitle?: InputMaybe; lastName?: InputMaybe; + linkedinUrl?: InputMaybe; phone?: InputMaybe; pipelineProgresses?: InputMaybe; updatedAt?: InputMaybe; @@ -1044,7 +1056,9 @@ export enum PersonScalarFieldEnum { Email = 'email', FirstName = 'firstName', Id = 'id', + JobTitle = 'jobTitle', LastName = 'lastName', + LinkedinUrl = 'linkedinUrl', Phone = 'phone', UpdatedAt = 'updatedAt', WorkspaceId = 'workspaceId' @@ -1057,7 +1071,9 @@ export type PersonUpdateInput = { email?: InputMaybe; firstName?: InputMaybe; id?: InputMaybe; + jobTitle?: InputMaybe; lastName?: InputMaybe; + linkedinUrl?: InputMaybe; phone?: InputMaybe; pipelineProgresses?: InputMaybe; updatedAt?: InputMaybe; @@ -1091,7 +1107,9 @@ export type PersonWhereInput = { email?: InputMaybe; firstName?: InputMaybe; id?: InputMaybe; + jobTitle?: InputMaybe; lastName?: InputMaybe; + linkedinUrl?: InputMaybe; phone?: InputMaybe; pipelineProgresses?: InputMaybe; updatedAt?: InputMaybe; @@ -2026,14 +2044,14 @@ export type GetCompaniesQueryVariables = Exact<{ }>; -export type GetCompaniesQuery = { __typename?: 'Query', companies: Array<{ __typename?: 'Company', id: string, domainName: string, name: string, createdAt: string, address: string, employees?: number | null, _commentThreadCount: 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, employees?: number | null, _commentThreadCount: number, accountOwner?: { __typename?: 'User', id: string, email: string, displayName: string, firstName?: string | null, lastName?: string | null, avatarUrl?: string | null } | null }> }; export type GetCompanyQueryVariables = Exact<{ id: Scalars['String']; }>; -export type GetCompanyQuery = { __typename?: 'Query', findUniqueCompany: { __typename?: 'Company', id: string, domainName: string, name: string, createdAt: string, address: string, employees?: number | null, _commentThreadCount: number, accountOwner?: { __typename?: 'User', id: string, email: string, displayName: string, avatarUrl?: string | null } | null } }; +export type GetCompanyQuery = { __typename?: 'Query', findUniqueCompany: { __typename?: 'Company', id: string, domainName: string, name: string, createdAt: string, address: string, linkedinUrl?: string | null, employees?: number | null, _commentThreadCount: number, accountOwner?: { __typename?: 'User', id: string, email: string, displayName: string, avatarUrl?: string | null } | null } }; export type UpdateOneCompanyMutationVariables = Exact<{ where: CompanyWhereUniqueInput; @@ -2041,14 +2059,14 @@ export type UpdateOneCompanyMutationVariables = Exact<{ }>; -export type UpdateOneCompanyMutation = { __typename?: 'Mutation', updateOneCompany?: { __typename?: 'Company', address: string, createdAt: string, domainName: string, employees?: number | null, id: string, name: string, accountOwner?: { __typename?: 'User', id: string, email: string, displayName: string, firstName?: string | null, lastName?: string | null } | null } | null }; +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, firstName?: string | null, lastName?: string | null } | null } | null }; export type InsertOneCompanyMutationVariables = Exact<{ data: CompanyCreateInput; }>; -export type InsertOneCompanyMutation = { __typename?: 'Mutation', createOneCompany: { __typename?: 'Company', address: string, createdAt: string, domainName: string, employees?: number | null, id: string, name: string } }; +export type InsertOneCompanyMutation = { __typename?: 'Mutation', createOneCompany: { __typename?: 'Company', address: string, createdAt: string, domainName: string, linkedinUrl?: string | null, employees?: number | null, id: string, name: string } }; export type DeleteManyCompaniesMutationVariables = Exact<{ ids?: InputMaybe | Scalars['String']>; @@ -2064,7 +2082,7 @@ export type GetPeopleQueryVariables = Exact<{ }>; -export type GetPeopleQuery = { __typename?: 'Query', people: Array<{ __typename?: 'Person', id: string, phone?: string | null, email?: string | null, city?: string | null, firstName?: string | null, lastName?: string | null, displayName: string, createdAt: string, _commentThreadCount: number, company?: { __typename?: 'Company', id: string, name: string, domainName: string } | null }> }; +export type GetPeopleQuery = { __typename?: 'Query', people: Array<{ __typename?: 'Person', id: string, phone?: string | null, email?: string | null, city?: string | null, firstName?: string | null, lastName?: string | null, displayName: string, jobTitle?: string | null, linkedinUrl?: string | null, createdAt: string, _commentThreadCount: number, company?: { __typename?: 'Company', id: string, name: string, domainName: string } | null }> }; export type GetPersonPhoneByIdQueryVariables = Exact<{ id: Scalars['String']; @@ -2120,7 +2138,7 @@ export type GetPersonQueryVariables = Exact<{ }>; -export type GetPersonQuery = { __typename?: 'Query', findUniquePerson: { __typename?: 'Person', id: string, firstName?: string | null, lastName?: string | null, displayName: string, email?: string | null, createdAt: string, city?: string | null, phone?: string | null, _commentThreadCount: number, company?: { __typename?: 'Company', id: string, name: string, domainName: string } | null } }; +export type GetPersonQuery = { __typename?: 'Query', findUniquePerson: { __typename?: 'Person', id: string, firstName?: string | null, lastName?: string | null, displayName: string, email?: string | null, createdAt: string, city?: string | null, jobTitle?: string | null, linkedinUrl?: string | null, phone?: string | null, _commentThreadCount: number, company?: { __typename?: 'Company', id: string, name: string, domainName: string } | null } }; export type UpdateOnePersonMutationVariables = Exact<{ where: PersonWhereUniqueInput; @@ -2128,14 +2146,14 @@ export type UpdateOnePersonMutationVariables = Exact<{ }>; -export type UpdateOnePersonMutation = { __typename?: 'Mutation', updateOnePerson?: { __typename?: 'Person', id: string, city?: string | null, email?: string | null, firstName?: string | null, lastName?: string | null, displayName: string, phone?: string | null, createdAt: string, company?: { __typename?: 'Company', domainName: string, name: string, id: string } | null } | null }; +export type UpdateOnePersonMutation = { __typename?: 'Mutation', updateOnePerson?: { __typename?: 'Person', id: string, city?: string | null, email?: string | null, jobTitle?: string | null, linkedinUrl?: string | null, firstName?: string | null, lastName?: string | null, displayName: string, phone?: string | null, createdAt: string, company?: { __typename?: 'Company', domainName: string, name: string, id: string } | null } | null }; export type InsertOnePersonMutationVariables = Exact<{ data: PersonCreateInput; }>; -export type InsertOnePersonMutation = { __typename?: 'Mutation', createOnePerson: { __typename?: 'Person', id: string, city?: string | null, email?: string | null, firstName?: string | null, lastName?: string | null, displayName: string, phone?: string | null, createdAt: string, company?: { __typename?: 'Company', domainName: string, name: string, id: string } | null } }; +export type InsertOnePersonMutation = { __typename?: 'Mutation', createOnePerson: { __typename?: 'Person', id: string, city?: string | null, email?: string | null, firstName?: string | null, lastName?: string | null, jobTitle?: string | null, linkedinUrl?: string | null, displayName: string, phone?: string | null, createdAt: string, company?: { __typename?: 'Company', domainName: string, name: string, id: string } | null } }; export type DeleteManyPersonMutationVariables = Exact<{ ids?: InputMaybe | Scalars['String']>; @@ -3039,6 +3057,7 @@ export const GetCompaniesDocument = gql` name createdAt address + linkedinUrl employees _commentThreadCount accountOwner { @@ -3089,6 +3108,7 @@ export const GetCompanyDocument = gql` name createdAt address + linkedinUrl employees _commentThreadCount accountOwner { @@ -3142,6 +3162,7 @@ export const UpdateOneCompanyDocument = gql` createdAt domainName employees + linkedinUrl id name } @@ -3180,6 +3201,7 @@ export const InsertOneCompanyDocument = gql` address createdAt domainName + linkedinUrl employees id name @@ -3255,6 +3277,8 @@ export const GetPeopleDocument = gql` firstName lastName displayName + jobTitle + linkedinUrl createdAt _commentThreadCount company { @@ -3564,6 +3588,8 @@ export const GetPersonDocument = gql` email createdAt city + jobTitle + linkedinUrl phone _commentThreadCount company { @@ -3613,6 +3639,8 @@ export const UpdateOnePersonDocument = gql` id } email + jobTitle + linkedinUrl firstName lastName displayName @@ -3661,6 +3689,8 @@ export const InsertOnePersonDocument = gql` email firstName lastName + jobTitle + linkedinUrl displayName phone createdAt diff --git a/front/src/modules/companies/queries/select.ts b/front/src/modules/companies/queries/select.ts index 8ee85fe7e..163f59116 100644 --- a/front/src/modules/companies/queries/select.ts +++ b/front/src/modules/companies/queries/select.ts @@ -26,6 +26,7 @@ export const GET_COMPANIES = gql` name createdAt address + linkedinUrl employees _commentThreadCount accountOwner { diff --git a/front/src/modules/companies/queries/show.ts b/front/src/modules/companies/queries/show.ts index 57f610c90..4babcbe55 100644 --- a/front/src/modules/companies/queries/show.ts +++ b/front/src/modules/companies/queries/show.ts @@ -10,6 +10,7 @@ export const GET_COMPANY = gql` name createdAt address + linkedinUrl employees _commentThreadCount accountOwner { diff --git a/front/src/modules/companies/queries/update.ts b/front/src/modules/companies/queries/update.ts index 2d43e5ad3..483baf33c 100644 --- a/front/src/modules/companies/queries/update.ts +++ b/front/src/modules/companies/queries/update.ts @@ -17,6 +17,7 @@ export const UPDATE_ONE_COMPANY = gql` createdAt domainName employees + linkedinUrl id name } @@ -29,6 +30,7 @@ export const INSERT_ONE_COMPANY = gql` address createdAt domainName + linkedinUrl employees id name diff --git a/front/src/modules/companies/states/companyLinkedinUrlFamilyState.ts b/front/src/modules/companies/states/companyLinkedinUrlFamilyState.ts new file mode 100644 index 000000000..7a6cba9ba --- /dev/null +++ b/front/src/modules/companies/states/companyLinkedinUrlFamilyState.ts @@ -0,0 +1,6 @@ +import { atomFamily } from 'recoil'; + +export const companyLinkedinUrlFamilyState = atomFamily({ + key: 'companyLinkedinUrlFamilyState', + default: null, +}); diff --git a/front/src/modules/companies/table/components/EditableCompanyLinkedinUrlCell.tsx b/front/src/modules/companies/table/components/EditableCompanyLinkedinUrlCell.tsx new file mode 100644 index 000000000..258c92a87 --- /dev/null +++ b/front/src/modules/companies/table/components/EditableCompanyLinkedinUrlCell.tsx @@ -0,0 +1,42 @@ +import { useEffect, useState } from 'react'; +import { useRecoilValue } from 'recoil'; + +import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId'; +import { useUpdateOneCompanyMutation } from '~/generated/graphql'; + +import { EditableCellURL } from '../../../ui/table/editable-cell/types/EditableCellURL'; +import { companyLinkedinUrlFamilyState } from '../../states/companyLinkedinUrlFamilyState'; + +export function EditableCompanyLinkedinUrlCell() { + const currentRowEntityId = useCurrentRowEntityId(); + + const [updateCompany] = useUpdateOneCompanyMutation(); + + const linkedinUrl = useRecoilValue( + companyLinkedinUrlFamilyState(currentRowEntityId ?? ''), + ); + const [internalValue, setInternalValue] = useState(linkedinUrl ?? ''); + useEffect(() => { + setInternalValue(linkedinUrl ?? ''); + }, [linkedinUrl]); + + return ( + + updateCompany({ + variables: { + where: { + id: currentRowEntityId, + }, + data: { + linkedinUrl: internalValue, + }, + }, + }) + } + onCancel={() => setInternalValue(linkedinUrl ?? '')} + /> + ); +} 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 ab6674532..e69bbcb03 100644 --- a/front/src/modules/companies/table/components/companies-mock-data.ts +++ b/front/src/modules/companies/table/components/companies-mock-data.ts @@ -9,6 +9,7 @@ type MockedCompany = Pick< | 'createdAt' | 'address' | 'employees' + | 'linkedinUrl' | '_commentThreadCount' > & { accountOwner: Pick< @@ -28,6 +29,7 @@ export const mockedCompaniesData: Array = [ id: '89bb825c-171e-4bcc-9cf7-43448d6fb278', domainName: 'airbnb.com', name: 'Airbnb', + linkedinUrl: 'https://www.linkedin.com/company/airbnb/', createdAt: '2023-04-26T10:08:54.724515+00:00', address: 'San Francisco, CA', employees: 5000, @@ -47,6 +49,7 @@ export const mockedCompaniesData: Array = [ id: 'b396e6b9-dc5c-4643-bcff-61b6cf7523ae', domainName: 'qonto.com', name: 'Qonto', + linkedinUrl: 'https://www.linkedin.com/company/qonto/', createdAt: '2023-04-26T10:12:42.33625+00:00', address: 'Paris, France', employees: 800, @@ -58,6 +61,7 @@ export const mockedCompaniesData: Array = [ id: 'a674fa6c-1455-4c57-afaf-dd5dc086361d', domainName: 'stripe.com', name: 'Stripe', + linkedinUrl: 'https://www.linkedin.com/company/stripe/', createdAt: '2023-04-26T10:10:32.530184+00:00', address: 'San Francisco, CA', employees: 8000, @@ -68,6 +72,7 @@ export const mockedCompaniesData: Array = [ { id: 'b1cfd51b-a831-455f-ba07-4e30671e1dc3', domainName: 'figma.com', + linkedinUrl: 'https://www.linkedin.com/company/figma/', name: 'Figma', createdAt: '2023-03-21T06:30:25.39474+00:00', address: 'San Francisco, CA', @@ -79,6 +84,7 @@ export const mockedCompaniesData: Array = [ { id: '5c21e19e-e049-4393-8c09-3e3f8fb09ecb', domainName: 'notion.com', + linkedinUrl: 'https://www.linkedin.com/company/notion/', name: 'Notion', createdAt: '2023-04-26T10:13:29.712485+00:00', address: 'San Francisco, CA', diff --git a/front/src/modules/companies/table/components/companyColumns.tsx b/front/src/modules/companies/table/components/companyColumns.tsx index d5d52f7cc..b431cc9ae 100644 --- a/front/src/modules/companies/table/components/companyColumns.tsx +++ b/front/src/modules/companies/table/components/companyColumns.tsx @@ -1,5 +1,6 @@ import { TableColumn } from '@/people/table/components/peopleColumns'; import { + IconBrandLinkedin, IconBuildingSkyscraper, IconCalendarEvent, IconLink, @@ -13,6 +14,7 @@ import { EditableCompanyAddressCell } from './EditableCompanyAddressCell'; import { EditableCompanyCreatedAtCell } from './EditableCompanyCreatedAtCell'; import { EditableCompanyDomainNameCell } from './EditableCompanyDomainNameCell'; import { EditableCompanyEmployeesCell } from './EditableCompanyEmployeesCell'; +import { EditableCompanyLinkedinUrlCell } from './EditableCompanyLinkedinUrlCell'; import { EditableCompanyNameCell } from './EditableCompanyNameCell'; export const companyColumns: TableColumn[] = [ @@ -51,6 +53,13 @@ export const companyColumns: TableColumn[] = [ size: 150, cellComponent: , }, + { + id: 'linkedinUrl', + title: 'Linkedin', + icon: , + size: 170, + cellComponent: , + }, { id: 'address', title: 'Address', diff --git a/front/src/modules/companies/table/hooks/useSetCompanyEntityTable.ts b/front/src/modules/companies/table/hooks/useSetCompanyEntityTable.ts index d7030c373..b75e3921a 100644 --- a/front/src/modules/companies/table/hooks/useSetCompanyEntityTable.ts +++ b/front/src/modules/companies/table/hooks/useSetCompanyEntityTable.ts @@ -6,6 +6,7 @@ import { companyCommentCountFamilyState } from '@/companies/states/companyCommen import { companyCreatedAtFamilyState } from '@/companies/states/companyCreatedAtFamilyState'; import { companyDomainNameFamilyState } from '@/companies/states/companyDomainNameFamilyState'; import { companyEmployeesFamilyState } from '@/companies/states/companyEmployeesFamilyState'; +import { companyLinkedinUrlFamilyState } from '@/companies/states/companyLinkedinUrlFamilyState'; import { companyNameFamilyState } from '@/companies/states/companyNameFamilyState'; import { GetCompaniesQuery } from '~/generated/graphql'; @@ -30,6 +31,17 @@ export function useSetCompanyEntityTable() { set(companyDomainNameFamilyState(company.id), company.domainName); } + const currentLinkedinUrl = snapshot + .getLoadable(companyLinkedinUrlFamilyState(company.id)) + .valueOrThrow(); + + if (currentLinkedinUrl !== company.linkedinUrl) { + set( + companyLinkedinUrlFamilyState(company.id), + company.linkedinUrl ?? '', + ); + } + const currentEmployees = snapshot .getLoadable(companyEmployeesFamilyState(company.id)) .valueOrThrow(); diff --git a/front/src/modules/people/hooks/useSetPeopleEntityTable.ts b/front/src/modules/people/hooks/useSetPeopleEntityTable.ts index e13652349..2d22069b7 100644 --- a/front/src/modules/people/hooks/useSetPeopleEntityTable.ts +++ b/front/src/modules/people/hooks/useSetPeopleEntityTable.ts @@ -6,6 +6,8 @@ import { peopleCityFamilyState } from '../states/peopleCityFamilyState'; import { peopleCompanyFamilyState } from '../states/peopleCompanyFamilyState'; import { peopleCreatedAtFamilyState } from '../states/peopleCreatedAtFamilyState'; import { peopleEmailFamilyState } from '../states/peopleEmailFamilyState'; +import { peopleJobTitleFamilyState } from '../states/peopleJobTitleFamilyState'; +import { peopleLinkedinUrlFamilyState } from '../states/peopleLinkedinUrlFamilyState'; import { peopleNameCellFamilyState } from '../states/peopleNamesFamilyState'; import { peoplePhoneFamilyState } from '../states/peoplePhoneFamilyState'; @@ -56,6 +58,25 @@ export function useSetPeopleEntityTable() { set(peopleCreatedAtFamilyState(person.id), person.createdAt); } + const currentJobTitle = snapshot + .getLoadable(peopleJobTitleFamilyState(person.id)) + .valueOrThrow(); + + if (currentJobTitle !== person.jobTitle) { + set(peopleJobTitleFamilyState(person.id), person.jobTitle ?? null); + } + + const currentLinkedinUrl = snapshot + .getLoadable(peopleLinkedinUrlFamilyState(person.id)) + .valueOrThrow(); + + if (currentLinkedinUrl !== person.linkedinUrl) { + set( + peopleLinkedinUrlFamilyState(person.id), + person.linkedinUrl ?? null, + ); + } + const currentNameCell = snapshot .getLoadable(peopleNameCellFamilyState(person.id)) .valueOrThrow(); diff --git a/front/src/modules/people/queries/select.ts b/front/src/modules/people/queries/select.ts index 50f2a5651..c4aa5e4c9 100644 --- a/front/src/modules/people/queries/select.ts +++ b/front/src/modules/people/queries/select.ts @@ -28,6 +28,8 @@ export const GET_PEOPLE = gql` firstName lastName displayName + jobTitle + linkedinUrl createdAt _commentThreadCount company { diff --git a/front/src/modules/people/queries/show.ts b/front/src/modules/people/queries/show.ts index e81fd8995..271d4a43b 100644 --- a/front/src/modules/people/queries/show.ts +++ b/front/src/modules/people/queries/show.ts @@ -12,6 +12,8 @@ export const GET_PERSON = gql` email createdAt city + jobTitle + linkedinUrl phone _commentThreadCount company { diff --git a/front/src/modules/people/queries/update.ts b/front/src/modules/people/queries/update.ts index d108cd9ce..8f1203a9a 100644 --- a/front/src/modules/people/queries/update.ts +++ b/front/src/modules/people/queries/update.ts @@ -14,6 +14,8 @@ export const UPDATE_ONE_PERSON = gql` id } email + jobTitle + linkedinUrl firstName lastName displayName @@ -36,6 +38,8 @@ export const INSERT_ONE_PERSON = gql` email firstName lastName + jobTitle + linkedinUrl displayName phone createdAt diff --git a/front/src/modules/people/states/peopleJobTitleFamilyState.ts b/front/src/modules/people/states/peopleJobTitleFamilyState.ts new file mode 100644 index 000000000..36eaaaa61 --- /dev/null +++ b/front/src/modules/people/states/peopleJobTitleFamilyState.ts @@ -0,0 +1,6 @@ +import { atomFamily } from 'recoil'; + +export const peopleJobTitleFamilyState = atomFamily({ + key: 'peopleJobTitleFamilyState', + default: null, +}); diff --git a/front/src/modules/people/states/peopleLinkedinUrlFamilyState.ts b/front/src/modules/people/states/peopleLinkedinUrlFamilyState.ts new file mode 100644 index 000000000..c2f343024 --- /dev/null +++ b/front/src/modules/people/states/peopleLinkedinUrlFamilyState.ts @@ -0,0 +1,6 @@ +import { atomFamily } from 'recoil'; + +export const peopleLinkedinUrlFamilyState = atomFamily({ + key: 'peopleLinkedinUrlFamilyState', + default: null, +}); diff --git a/front/src/modules/people/table/components/EditablePeopleJobTitleCell.tsx b/front/src/modules/people/table/components/EditablePeopleJobTitleCell.tsx new file mode 100644 index 000000000..16ab33086 --- /dev/null +++ b/front/src/modules/people/table/components/EditablePeopleJobTitleCell.tsx @@ -0,0 +1,43 @@ +import { useEffect, useState } from 'react'; +import { useRecoilValue } from 'recoil'; + +import { peopleJobTitleFamilyState } from '@/people/states/peopleJobTitleFamilyState'; +import { EditableCellText } from '@/ui/table/editable-cell/types/EditableCellText'; +import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId'; +import { useUpdateOnePersonMutation } from '~/generated/graphql'; + +export function EditablePeopleJobTitleCell() { + const currentRowEntityId = useCurrentRowEntityId(); + + const [updatePerson] = useUpdateOnePersonMutation(); + + const jobTitle = useRecoilValue( + peopleJobTitleFamilyState(currentRowEntityId ?? ''), + ); + + const [internalValue, setInternalValue] = useState(jobTitle ?? ''); + + useEffect(() => { + setInternalValue(jobTitle ?? ''); + }, [jobTitle]); + + return ( + + updatePerson({ + variables: { + where: { + id: currentRowEntityId, + }, + data: { + jobTitle: internalValue, + }, + }, + }) + } + onCancel={() => setInternalValue(jobTitle ?? '')} + /> + ); +} diff --git a/front/src/modules/people/table/components/EditablePeopleLinkedinUrlCell.tsx b/front/src/modules/people/table/components/EditablePeopleLinkedinUrlCell.tsx new file mode 100644 index 000000000..eee873bc9 --- /dev/null +++ b/front/src/modules/people/table/components/EditablePeopleLinkedinUrlCell.tsx @@ -0,0 +1,44 @@ +import { useEffect, useState } from 'react'; +import { useRecoilValue } from 'recoil'; + +import { peopleLinkedinUrlFamilyState } from '@/people/states/peopleLinkedinUrlFamilyState'; +import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId'; +import { useUpdateOnePersonMutation } from '~/generated/graphql'; + +import { EditableCellURL } from '../../../ui/table/editable-cell/types/EditableCellURL'; + +export function EditablePeopleLinkedinUrlCell() { + const currentRowEntityId = useCurrentRowEntityId(); + + const [updatePerson] = useUpdateOnePersonMutation(); + + const linkedinUrl = useRecoilValue( + peopleLinkedinUrlFamilyState(currentRowEntityId ?? ''), + ); + + const [internalValue, setInternalValue] = useState(linkedinUrl ?? ''); + + useEffect(() => { + setInternalValue(linkedinUrl ?? ''); + }, [linkedinUrl]); + + return ( + + updatePerson({ + variables: { + where: { + id: currentRowEntityId, + }, + data: { + linkedinUrl: internalValue, + }, + }, + }) + } + onCancel={() => setInternalValue(linkedinUrl ?? '')} + /> + ); +} diff --git a/front/src/modules/people/table/components/peopleColumns.tsx b/front/src/modules/people/table/components/peopleColumns.tsx index d2c070da7..3aaec0dc0 100644 --- a/front/src/modules/people/table/components/peopleColumns.tsx +++ b/front/src/modules/people/table/components/peopleColumns.tsx @@ -1,4 +1,6 @@ import { + IconBrandLinkedin, + IconBriefcase, IconBuildingSkyscraper, IconCalendarEvent, IconMail, @@ -12,6 +14,8 @@ import { EditablePeopleCompanyCell } from './EditablePeopleCompanyCell'; import { EditablePeopleCreatedAtCell } from './EditablePeopleCreatedAtCell'; import { EditablePeopleEmailCell } from './EditablePeopleEmailCell'; import { EditablePeopleFullNameCell } from './EditablePeopleFullNameCell'; +import { EditablePeopleJobTitleCell } from './EditablePeopleJobTitleCell'; +import { EditablePeopleLinkedinUrlCell } from './EditablePeopleLinkedinUrlCell'; import { EditablePeoplePhoneCell } from './EditablePeoplePhoneCell'; export type TableColumn = { @@ -65,4 +69,18 @@ export const peopleColumns: TableColumn[] = [ size: 150, cellComponent: , }, + { + id: 'jobTitle', + title: 'Job title', + icon: , + size: 150, + cellComponent: , + }, + { + id: 'linkedinUrl', + title: 'Linkedin', + icon: , + size: 150, + cellComponent: , + }, ]; diff --git a/front/src/modules/ui/icon/index.ts b/front/src/modules/ui/icon/index.ts index b9bf68033..2c93b8c85 100644 --- a/front/src/modules/ui/icon/index.ts +++ b/front/src/modules/ui/icon/index.ts @@ -15,9 +15,11 @@ export { IconColorSwatch } from '@tabler/icons-react'; export { IconProgressCheck } from '@tabler/icons-react'; export { IconX } from '@tabler/icons-react'; export { IconChevronLeft } from '@tabler/icons-react'; +export { IconBriefcase } from '@tabler/icons-react'; export { IconPlus } from '@tabler/icons-react'; export { IconMinus } from '@tabler/icons-react'; export { IconLink } from '@tabler/icons-react'; +export { IconBrandLinkedin } from '@tabler/icons-react'; export { IconUsers } from '@tabler/icons-react'; export { IconCalendarEvent } from '@tabler/icons-react'; export { IconMap } from '@tabler/icons-react'; diff --git a/front/src/testing/mock-data/people.ts b/front/src/testing/mock-data/people.ts index 89135ed91..ba1b96984 100644 --- a/front/src/testing/mock-data/people.ts +++ b/front/src/testing/mock-data/people.ts @@ -11,6 +11,8 @@ type MockedPerson = RequiredAndNotNull< | 'firstName' | 'lastName' | 'displayName' + | 'linkedinUrl' + | 'jobTitle' | 'email' | '__typename' | 'phone' @@ -30,6 +32,8 @@ export const mockedPeopleData: MockedPerson[] = [ lastName: 'Prot', displayName: 'Alexandre Prot', email: 'alexandre@qonto.com', + linkedinUrl: 'https://www.linkedin.com/in/alexandreprot/', + jobTitle: 'CEO', company: { id: '5c21e19e-e049-4393-8c09-3e3f8fb09ecb', name: 'Qonto', @@ -48,6 +52,8 @@ export const mockedPeopleData: MockedPerson[] = [ firstName: 'John', lastName: 'Doe', displayName: 'John Doe', + linkedinUrl: 'https://www.linkedin.com/in/johndoe/', + jobTitle: 'CTO', email: 'john@linkedin.com', company: { id: '7dfbc3f7-6e5e-4128-957e-8d86808cdf6e', @@ -67,6 +73,8 @@ export const mockedPeopleData: MockedPerson[] = [ firstName: 'Jane', lastName: 'Doe', displayName: 'Jane Doe', + linkedinUrl: 'https://www.linkedin.com/in/janedoe/', + jobTitle: 'Investor', email: 'jane@sequoiacap.com', company: { id: '7dfbc3f7-6e5e-4128-957e-8d86808cdf6g', @@ -87,6 +95,8 @@ export const mockedPeopleData: MockedPerson[] = [ lastName: 'Dane', displayName: 'Janice Dane', email: 'janice@facebook.com', + linkedinUrl: 'https://www.linkedin.com/in/janicedane/', + jobTitle: 'CEO', company: { id: '7dfbc3f7-6e5e-4128-957e-8d86808cdf6i', name: 'Facebook', diff --git a/server/src/database/migrations/20230721210543_add_linkedin_url_and_job_title_fields/migration.sql b/server/src/database/migrations/20230721210543_add_linkedin_url_and_job_title_fields/migration.sql new file mode 100644 index 000000000..ec91a0a4a --- /dev/null +++ b/server/src/database/migrations/20230721210543_add_linkedin_url_and_job_title_fields/migration.sql @@ -0,0 +1,6 @@ +-- AlterTable +ALTER TABLE "companies" ADD COLUMN "linkedinUrl" TEXT; + +-- AlterTable +ALTER TABLE "people" ADD COLUMN "jobTitle" TEXT, +ADD COLUMN "linkedinUrl" TEXT; diff --git a/server/src/database/schema.prisma b/server/src/database/schema.prisma index 5bf392f1c..c102a65be 100644 --- a/server/src/database/schema.prisma +++ b/server/src/database/schema.prisma @@ -203,19 +203,22 @@ model WorkspaceMember { model Company { /// @Validator.IsString() /// @Validator.IsOptional() - id String @id @default(uuid()) + id String @id @default(uuid()) /// @Validator.IsString() /// @Validator.IsOptional() - name String + name String /// @Validator.IsString() /// @Validator.IsOptional() - domainName String + domainName String /// @Validator.IsString() /// @Validator.IsOptional() - address String + linkedinUrl String? + /// @Validator.IsString() + /// @Validator.IsOptional() + address String /// @Validator.IsNumber() /// @Validator.IsOptional() - employees Int? + employees Int? people Person[] accountOwner User? @relation(fields: [accountOwnerId], references: [id]) @@ -237,22 +240,28 @@ model Company { model Person { /// @Validator.IsString() /// @Validator.IsOptional() - id String @id @default(uuid()) + id String @id @default(uuid()) /// @Validator.IsString() /// @Validator.IsOptional() - firstName String? + firstName String? /// @Validator.IsString() /// @Validator.IsOptional() - lastName String? + lastName String? /// @Validator.IsString() /// @Validator.IsOptional() - email String? + email String? /// @Validator.IsString() /// @Validator.IsOptional() - phone String? + linkedinUrl String? /// @Validator.IsString() /// @Validator.IsOptional() - city String? + jobTitle String? + /// @Validator.IsString() + /// @Validator.IsOptional() + phone String? + /// @Validator.IsString() + /// @Validator.IsOptional() + city String? company Company? @relation(fields: [companyId], references: [id]) companyId String?