From 8c7815af79ac025bfbf5e375709b7fb82b660a03 Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Sat, 6 May 2023 19:08:47 +0200 Subject: [PATCH] Hide Opportunities as nothing is built yet and make company table fully editable (#109) * Hide Opportunities as nothing is built yet and make company table fully editable * Fix tests --- front/src/__tests__/App.test.tsx | 1 - front/src/components/chips/PersonChip.tsx | 4 +- .../table/editable-cell/EditableChip.tsx | 2 +- .../table/editable-cell/EditableRelation.tsx | 2 +- .../table/table-header/TableHeader.tsx | 3 - front/src/interfaces/user.interface.ts | 3 + front/src/layout/navbar/Navbar.tsx | 13 +- .../companies/__tests__/Companies.test.tsx | 116 ++++++++++++++++-- front/src/pages/companies/companies-table.tsx | 107 ++++++++++------ front/src/pages/people/people-table.tsx | 11 -- front/src/services/companies/update.ts | 2 + front/src/services/search/search.ts | 10 ++ 12 files changed, 201 insertions(+), 73 deletions(-) diff --git a/front/src/__tests__/App.test.tsx b/front/src/__tests__/App.test.tsx index d88a1017c..ca2633903 100644 --- a/front/src/__tests__/App.test.tsx +++ b/front/src/__tests__/App.test.tsx @@ -10,7 +10,6 @@ it('Checks the App component renders', async () => { const { getByText } = render(); expect(getByText('Companies')).toBeDefined(); - expect(getByText('Opportunities')).toBeDefined(); await waitFor(() => { expect(getByText('Twenty')).toBeDefined(); }); diff --git a/front/src/components/chips/PersonChip.tsx b/front/src/components/chips/PersonChip.tsx index 963c6db1e..b037315dc 100644 --- a/front/src/components/chips/PersonChip.tsx +++ b/front/src/components/chips/PersonChip.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import styled from '@emotion/styled'; import PersonPlaceholder from './person-placeholder.png'; -type OwnProps = { +export type PersonChipPropsType = { name: string; picture?: string; }; @@ -28,7 +28,7 @@ const StyledContainer = styled.span` } `; -function PersonChip({ name, picture }: OwnProps) { +function PersonChip({ name, picture }: PersonChipPropsType) { return ( } - nonEditModeContent={} + nonEditModeContent={} > ); } diff --git a/front/src/components/table/editable-cell/EditableRelation.tsx b/front/src/components/table/editable-cell/EditableRelation.tsx index e93361189..794efd8f0 100644 --- a/front/src/components/table/editable-cell/EditableRelation.tsx +++ b/front/src/components/table/editable-cell/EditableRelation.tsx @@ -49,7 +49,7 @@ const StyledEditModeResultItem = styled.div` `; export type EditableRelationProps = { - relation: RelationType; + relation?: RelationType; searchPlaceholder: string; searchFilter: FilterType; changeHandler: (relation: RelationType) => void; diff --git a/front/src/components/table/table-header/TableHeader.tsx b/front/src/components/table/table-header/TableHeader.tsx index 6b6766818..9805dbb9d 100644 --- a/front/src/components/table/table-header/TableHeader.tsx +++ b/front/src/components/table/table-header/TableHeader.tsx @@ -1,5 +1,4 @@ import styled from '@emotion/styled'; -import DropdownButton from './DropdownButton'; import { FilterType, SelectedFilterType, @@ -147,8 +146,6 @@ function TableHeader({ availableSorts={availableSorts || []} onSortSelect={sortSelect} /> - - {sorts.length + filters.length > 0 && ( diff --git a/front/src/interfaces/user.interface.ts b/front/src/interfaces/user.interface.ts index e64498cca..aaed906bd 100644 --- a/front/src/interfaces/user.interface.ts +++ b/front/src/interfaces/user.interface.ts @@ -18,6 +18,9 @@ export interface User { workspace_member?: WorkspaceMember; } +export type PartialUser = Partial & + Pick; + export const mapUser = (user: GraphqlQueryUser): User => { const mappedUser = { id: user.id, diff --git a/front/src/layout/navbar/Navbar.tsx b/front/src/layout/navbar/Navbar.tsx index 10939c9b0..fbcf3e7d4 100644 --- a/front/src/layout/navbar/Navbar.tsx +++ b/front/src/layout/navbar/Navbar.tsx @@ -5,7 +5,7 @@ import { Workspace } from '../../interfaces/workspace.interface'; import NavItem from './NavItem'; import NavTitle from './NavTitle'; import WorkspaceContainer from './WorkspaceContainer'; -import { FaRegUser, FaRegBuilding, FaBullseye } from 'react-icons/fa'; +import { FaRegUser, FaRegBuilding } from 'react-icons/fa'; const NavbarContainer = styled.div` display: flex; @@ -55,17 +55,6 @@ function Navbar({ workspace }: OwnProps) { }) } /> - } - active={ - !!useMatch({ - path: useResolvedPath('/opportunities').pathname, - end: true, - }) - } - /> diff --git a/front/src/pages/companies/__tests__/Companies.test.tsx b/front/src/pages/companies/__tests__/Companies.test.tsx index c6f735c24..0a574fa42 100644 --- a/front/src/pages/companies/__tests__/Companies.test.tsx +++ b/front/src/pages/companies/__tests__/Companies.test.tsx @@ -1,15 +1,117 @@ -import { render, waitFor } from '@testing-library/react'; +import { fireEvent, render, waitFor } from '@testing-library/react'; import { CompaniesDefault } from '../__stories__/Companies.stories'; +import { act } from 'react-dom/test-utils'; +import { + GraphqlMutationCompany, + GraphqlQueryCompany, +} from '../../../interfaces/company.interface'; -it('Checks the Companies page render', async () => { - const { getByTestId } = render(); +jest.mock('../../../apollo', () => { + const companyInterface = jest.requireActual( + '../../../interfaces/company.interface', + ); + return { + apiClient: { + mutate: (arg: { + mutation: unknown; + variables: GraphqlMutationCompany; + }) => { + const gqlCompany = arg.variables as unknown as GraphqlQueryCompany; + return { data: companyInterface.mapCompany(gqlCompany) }; + }, + }, + }; +}); - const title = getByTestId('top-bar-title'); - expect(title).toHaveTextContent('Companies'); +it('Checks company name edit is updating data', async () => { + const { getByText, getByDisplayValue } = render(); await waitFor(() => { - const row = getByTestId('row-id-0'); - expect(row).toBeDefined(); + expect(getByText('Airbnb')).toBeDefined(); + }); + + act(() => { + fireEvent.click(getByText('Airbnb')); + }); + + await waitFor(() => { + expect(getByDisplayValue('Airbnb')).toBeInTheDocument(); + }); + + act(() => { + const nameInput = getByDisplayValue('Airbnb'); + + if (!nameInput) { + throw new Error('nameInput is null'); + } + fireEvent.change(nameInput, { target: { value: 'Airbnbb' } }); + fireEvent.click(getByText('All Companies')); // Click outside + }); + + await waitFor(() => { + expect(getByText('Airbnbb')).toBeDefined(); + }); +}); + +it('Checks company url edit is updating data', async () => { + const { getByText, getByDisplayValue } = render(); + + await waitFor(() => { + expect(getByText('airbnb.com')).toBeDefined(); + }); + + act(() => { + fireEvent.click(getByText('airbnb.com')); + }); + + await waitFor(() => { + expect(getByDisplayValue('airbnb.com')).toBeInTheDocument(); + }); + + act(() => { + const urlInput = getByDisplayValue('airbnb.com'); + + if (!urlInput) { + throw new Error('urlInput is null'); + } + fireEvent.change(urlInput, { target: { value: 'airbnb.co' } }); + fireEvent.click(getByText('All Companies')); // Click outside + }); + + await waitFor(() => { + expect(getByText('airbnb.co')).toBeInTheDocument(); + }); +}); + +it('Checks company address edit is updating data', async () => { + const { getByText, getByDisplayValue } = render(); + + await waitFor(() => { + expect(getByText('17 rue de clignancourt')).toBeDefined(); + }); + + act(() => { + fireEvent.click(getByText('17 rue de clignancourt')); + }); + + await waitFor(() => { + expect(getByDisplayValue('17 rue de clignancourt')).toBeInTheDocument(); + }); + + act(() => { + const addressInput = getByDisplayValue('17 rue de clignancourt'); + + if (!addressInput) { + throw new Error('addressInput is null'); + } + fireEvent.change(addressInput, { + target: { value: '21 rue de clignancourt' }, + }); + fireEvent.click(getByText('All Companies')); // Click outside + }); + + await waitFor(() => { + expect(getByText('21 rue de clignancourt')).toBeInTheDocument(); }); }); diff --git a/front/src/pages/companies/companies-table.tsx b/front/src/pages/companies/companies-table.tsx index 2b2771d75..888760320 100644 --- a/front/src/pages/companies/companies-table.tsx +++ b/front/src/pages/companies/companies-table.tsx @@ -8,19 +8,19 @@ import ColumnHead from '../../components/table/ColumnHead'; import Checkbox from '../../components/form/Checkbox'; import CompanyChip from '../../components/chips/CompanyChip'; import EditableText from '../../components/table/editable-cell/EditableText'; -import PipeChip from '../../components/chips/PipeChip'; import { FaRegBuilding, FaCalendar, FaLink, FaMapPin, - FaStream, FaRegUser, FaUsers, FaBuilding, + FaUser, } from 'react-icons/fa'; -import ClickableCell from '../../components/table/ClickableCell'; -import PersonChip from '../../components/chips/PersonChip'; +import PersonChip, { + PersonChipPropsType, +} from '../../components/chips/PersonChip'; import EditableChip from '../../components/table/editable-cell/EditableChip'; import { FilterType, @@ -29,8 +29,15 @@ import { import { Companies_Bool_Exp, Companies_Order_By, + Users_Bool_Exp, } from '../../generated/graphql'; -import { SEARCH_COMPANY_QUERY } from '../../services/search/search'; +import { + SEARCH_COMPANY_QUERY, + SEARCH_USER_QUERY, +} from '../../services/search/search'; +import EditableDate from '../../components/table/editable-cell/EditableDate'; +import EditableRelation from '../../components/table/editable-cell/EditableRelation'; +import { GraphqlQueryUser, PartialUser } from '../../interfaces/user.interface'; export const availableSorts = [ { @@ -151,7 +158,7 @@ export const companiesColumns = [ changeHandler={(value: string) => { const company = props.row.original; company.name = value; - updateCompany(company).catch((error) => console.error(error)); + updateCompany(company); }} ChipComponent={CompanyChip} /> @@ -165,7 +172,7 @@ export const companiesColumns = [ changeHandler={(value) => { const company = props.row.original; company.employees = parseInt(value); - updateCompany(company).catch((error) => console.error(error)); + updateCompany(company); }} /> ), @@ -178,7 +185,7 @@ export const companiesColumns = [ changeHandler={(value) => { const company = props.row.original; company.domain_name = value; - updateCompany(company).catch((error) => console.error(error)); + updateCompany(company); }} /> ), @@ -191,33 +198,22 @@ export const companiesColumns = [ changeHandler={(value) => { const company = props.row.original; company.address = value; - updateCompany(company).catch((error) => console.error(error)); + updateCompany(company); }} /> ), }), - columnHelper.accessor('opportunities', { - header: () => ( - } /> - ), - cell: (props) => ( - - {props.row.original.opportunities.map((opportunity) => ( - - ))} - - ), - }), columnHelper.accessor('creationDate', { header: () => } />, cell: (props) => ( - - {new Intl.DateTimeFormat(undefined, { - month: 'short', - day: 'numeric', - year: 'numeric', - }).format(props.row.original.creationDate)} - + { + const company = props.row.original; + company.creationDate = value; + updateCompany(company); + }} + /> ), }), columnHelper.accessor('accountOwner', { @@ -225,13 +221,54 @@ export const companiesColumns = [ } /> ), cell: (props) => ( - - <> - {props.row.original.accountOwner && ( - - )} - - + + relation={props.row.original.accountOwner} + searchPlaceholder="Account Owner" + ChipComponent={PersonChip} + chipComponentPropsMapper={( + accountOwner: PartialUser, + ): PersonChipPropsType => { + return { + name: accountOwner.displayName, + }; + }} + changeHandler={(relation: PartialUser) => { + const company = props.row.original; + if (company.accountOwner) { + company.accountOwner.id = relation.id; + } else { + company.accountOwner = { + id: relation.id, + email: relation.email, + displayName: relation.displayName, + }; + } + updateCompany(company); + }} + searchFilter={ + { + key: 'account_owner_name', + label: 'Account Owner', + icon: , + whereTemplate: () => { + return {}; + }, + searchQuery: SEARCH_USER_QUERY, + searchTemplate: (searchInput: string) => ({ + displayName: { _ilike: `%${searchInput}%` }, + }), + searchResultMapper: (accountOwner: GraphqlQueryUser) => ({ + displayValue: accountOwner.displayName, + value: { + id: accountOwner.id, + email: accountOwner.email, + displayName: accountOwner.displayName, + }, + }), + operands: [], + } satisfies FilterType + } + /> ), }), ]; diff --git a/front/src/pages/people/people-table.tsx b/front/src/pages/people/people-table.tsx index c55750c8f..b2766b864 100644 --- a/front/src/pages/people/people-table.tsx +++ b/front/src/pages/people/people-table.tsx @@ -5,19 +5,16 @@ import { FaRegUser, FaMapPin, FaPhone, - FaStream, FaUser, FaBuilding, } from 'react-icons/fa'; import { createColumnHelper } from '@tanstack/react-table'; -import ClickableCell from '../../components/table/ClickableCell'; import ColumnHead from '../../components/table/ColumnHead'; import Checkbox from '../../components/form/Checkbox'; import CompanyChip, { CompanyChipPropsType, } from '../../components/chips/CompanyChip'; import { GraphqlQueryPerson, Person } from '../../interfaces/person.interface'; -import PipeChip from '../../components/chips/PipeChip'; import EditableText from '../../components/table/editable-cell/EditableText'; import { FilterType, @@ -361,14 +358,6 @@ export const peopleColumns = [ /> ), }), - columnHelper.accessor('pipe', { - header: () => } />, - cell: (props) => ( - - - - ), - }), columnHelper.accessor('city', { header: () => } />, cell: (props) => ( diff --git a/front/src/services/companies/update.ts b/front/src/services/companies/update.ts index 56b380f0a..604aabc75 100644 --- a/front/src/services/companies/update.ts +++ b/front/src/services/companies/update.ts @@ -8,6 +8,7 @@ export const UPDATE_COMPANY = gql` $name: String $domain_name: String $account_owner_id: uuid + $created_at: timestamptz $address: String $employees: Int ) { @@ -19,6 +20,7 @@ export const UPDATE_COMPANY = gql` domain_name: $domain_name employees: $employees name: $name + created_at: $created_at } ) { affected_rows diff --git a/front/src/services/search/search.ts b/front/src/services/search/search.ts index 4086df5d2..4719f0589 100644 --- a/front/src/services/search/search.ts +++ b/front/src/services/search/search.ts @@ -18,6 +18,16 @@ export const SEARCH_PEOPLE_QUERY = gql` } `; +export const SEARCH_USER_QUERY = gql` + query SearchQuery($where: users_bool_exp, $limit: Int) { + searchResults: users(where: $where, limit: $limit) { + id + email + displayName + } + } +`; + const EMPTY_QUERY = gql` query EmptyQuery { _