From 287168f691ddbc48c58200278dc39f7c3be821ae Mon Sep 17 00:00:00 2001 From: Sammy Teillet Date: Wed, 14 Jun 2023 17:06:50 +0200 Subject: [PATCH] 289 on opportunities page i see person and company card layout read only (#293) * feature: create boardCard component * test: add snapshot for BoardCards * feature: fix typename of company * feature: add max width on BoardItem * feature: design CompanyBoardCard * feature: Add People picture and name * feature: design PeopleCard * feature: fix font weight for card header * test: fix storybook for board * test: add unit test for column optimistic renderer --- .../__tests__/company.interface.test.ts | 8 +- .../companies/interfaces/company.interface.ts | 6 +- .../opportunities/components/Board.tsx | 4 +- .../opportunities/components/BoardCard.tsx | 119 +++++++++++++++++- .../__stories__/BoardCard.stories.tsx | 36 ++++++ .../components/__stories__/mock-data.ts | 20 +-- .../__tests__/person.interface.test.ts | 2 +- .../people/services/__tests__/update.test.ts | 2 +- .../src/modules/ui/components/board/Board.tsx | 5 +- .../modules/ui/components/board/BoardItem.tsx | 4 +- .../components/board/__tests__/Board.test.ts | 55 ++++++++ .../ui/components/table/EntityTable.tsx | 4 +- front/src/pages/companies/Companies.tsx | 2 +- .../people/__tests__/people-filter.test.ts | 2 +- front/src/testing/mock-data/companies.ts | 14 +-- 15 files changed, 246 insertions(+), 37 deletions(-) create mode 100644 front/src/modules/opportunities/components/__stories__/BoardCard.stories.tsx create mode 100644 front/src/modules/ui/components/board/__tests__/Board.test.ts diff --git a/front/src/modules/companies/interfaces/__tests__/company.interface.test.ts b/front/src/modules/companies/interfaces/__tests__/company.interface.test.ts index 4704291bb..0010ffdb4 100644 --- a/front/src/modules/companies/interfaces/__tests__/company.interface.test.ts +++ b/front/src/modules/companies/interfaces/__tests__/company.interface.test.ts @@ -33,12 +33,12 @@ describe('Company mappers', () => { __typename: 'Pipe', }, ], - __typename: 'companies', + __typename: 'Company', } satisfies GraphqlQueryCompany; const company = mapToCompany(graphQLCompany); expect(company).toStrictEqual({ - __typename: 'companies', + __typename: 'Company', id: graphQLCompany.id, name: graphQLCompany.name, domainName: graphQLCompany.domainName, @@ -77,7 +77,7 @@ describe('Company mappers', () => { __typename: 'users', }, createdAt: now, - __typename: 'companies', + __typename: 'Company', } satisfies Company; const graphQLCompany = mapToGqlCompany(company); expect(graphQLCompany).toStrictEqual({ @@ -88,7 +88,7 @@ describe('Company mappers', () => { employees: company.employees, address: company.address, accountOwnerId: '522d4ec4-c46b-4360-a0a7-df8df170be81', - __typename: 'companies', + __typename: 'Company', } satisfies GraphqlMutationCompany); }); }); diff --git a/front/src/modules/companies/interfaces/company.interface.ts b/front/src/modules/companies/interfaces/company.interface.ts index 94ade5e4d..e9205aa44 100644 --- a/front/src/modules/companies/interfaces/company.interface.ts +++ b/front/src/modules/companies/interfaces/company.interface.ts @@ -9,7 +9,7 @@ import { } from '../../users/interfaces/user.interface'; export type Company = { - __typename: 'companies'; + __typename: 'Company'; id: string; name?: string; domainName?: string; @@ -54,7 +54,7 @@ export type GraphqlMutationCompany = { }; export const mapToCompany = (company: GraphqlQueryCompany): Company => ({ - __typename: 'companies', + __typename: 'Company', id: company.id, employees: company.employees, name: company.name, @@ -80,5 +80,5 @@ export const mapToGqlCompany = (company: Company): GraphqlMutationCompany => ({ createdAt: company.createdAt ? company.createdAt.toUTCString() : undefined, accountOwnerId: company.accountOwner?.id, - __typename: 'companies', + __typename: 'Company', }); diff --git a/front/src/modules/opportunities/components/Board.tsx b/front/src/modules/opportunities/components/Board.tsx index 2159a8cc5..649e708e7 100644 --- a/front/src/modules/opportunities/components/Board.tsx +++ b/front/src/modules/opportunities/components/Board.tsx @@ -61,9 +61,7 @@ export const Board = ({ initialBoard, items }: BoardProps) => { > {(draggableProvided) => ( - - {items[itemKey]?.id || 'Item not found'} - + )} diff --git a/front/src/modules/opportunities/components/BoardCard.tsx b/front/src/modules/opportunities/components/BoardCard.tsx index c7982172f..b6acc117c 100644 --- a/front/src/modules/opportunities/components/BoardCard.tsx +++ b/front/src/modules/opportunities/components/BoardCard.tsx @@ -1,5 +1,122 @@ import styled from '@emotion/styled'; -export const BoardCard = styled.p` +import { Company, Person } from '../../../generated/graphql'; +import CompanyChip from '../../companies/components/CompanyChip'; +import PersonPlaceholder from '../../people/components/person-placeholder.png'; +import { PersonChip } from '../../people/components/PersonChip'; +import { + IconBuilding, + IconCalendar, + IconMail, + IconPhone, + IconSum, + IconUser, +} from '../../ui/icons'; +import { getLogoUrlFromDomainName, humanReadableDate } from '../../utils/utils'; + +const StyledBoardCard = styled.div` color: ${(props) => props.theme.text80}; `; + +const StyledBoardCardHeader = styled.div` + align-items: center; + display: flex; + flex-direction: row; + font-weight: ${(props) => props.theme.fontWeightBold}; + height: 24px; + padding: ${(props) => props.theme.spacing(2)}; + img { + height: 16px; + margin-right: ${(props) => props.theme.spacing(2)}; + object-fit: cover; + width: 16px; + } +`; +const StyledBoardCardBody = styled.div` + display: flex; + flex-direction: column; + gap: ${(props) => props.theme.spacing(2)}; + padding: ${(props) => props.theme.spacing(2)}; + span { + align-items: center; + display: flex; + flex-direction: row; + svg { + color: ${(props) => props.theme.text40}; + margin-right: ${(props) => props.theme.spacing(2)}; + } + } +`; + +export const BoardCard = ({ item }: { item: Person | Company }) => { + if (item.__typename === 'Person') return ; + if (item.__typename === 'Company') return ; + // @todo return card skeleton + return null; +}; + +const PersonBoardCard = ({ person }: { person: Person }) => { + const fullname = `${person.firstname} ${person.lastname}`; + return ( + + + person + {fullname} + + + + + + + + + {person.email} + + + + {person.phone} + + + + {humanReadableDate(new Date(person.createdAt as string))} + + + + ); +}; + +const CompanyBoardCard = ({ company }: { company: Company }) => { + return ( + + + {`${company.name}-company-logo`} + {company.name} + + + + + + + + {company.employees} + + + + {humanReadableDate(new Date(company.createdAt as string))} + + + + ); +}; diff --git a/front/src/modules/opportunities/components/__stories__/BoardCard.stories.tsx b/front/src/modules/opportunities/components/__stories__/BoardCard.stories.tsx new file mode 100644 index 000000000..cdf05e403 --- /dev/null +++ b/front/src/modules/opportunities/components/__stories__/BoardCard.stories.tsx @@ -0,0 +1,36 @@ +import { StrictMode } from 'react'; +import { Meta, StoryObj } from '@storybook/react'; + +import { Company, Person } from '../../../../generated/graphql'; +import { mockedCompaniesData } from '../../../../testing/mock-data/companies'; +import { mockedPeopleData } from '../../../../testing/mock-data/people'; +import { BoardItem } from '../../../ui/components/board/BoardItem'; +import { BoardCard } from '../BoardCard'; + +const meta: Meta = { + title: 'UI/Board/BoardCard', + component: BoardCard, +}; + +export default meta; +type Story = StoryObj; + +export const CompanyBoardCard: Story = { + render: () => ( + + + + + + ), +}; + +export const PersonBoardCard: Story = { + render: () => ( + + + + + + ), +}; diff --git a/front/src/modules/opportunities/components/__stories__/mock-data.ts b/front/src/modules/opportunities/components/__stories__/mock-data.ts index a382080d3..618115aa0 100644 --- a/front/src/modules/opportunities/components/__stories__/mock-data.ts +++ b/front/src/modules/opportunities/components/__stories__/mock-data.ts @@ -1,16 +1,22 @@ +import { mockedCompaniesData } from '../../../../testing/mock-data/companies'; +import { mockedPeopleData } from '../../../../testing/mock-data/people'; import { Column, Items } from '../../../ui/components/board/Board'; export const items: Items = { - 'item-1': { id: 'item-1', content: 'Item 1' }, - 'item-2': { id: 'item-2', content: 'Item 2' }, - 'item-3': { id: 'item-3', content: 'Item 3' }, - 'item-4': { id: 'item-4', content: 'Item 4' }, - 'item-5': { id: 'item-5', content: 'Item 5' }, - 'item-6': { id: 'item-6', content: 'Item 6' }, + 'item-1': mockedCompaniesData[0], + 'item-2': mockedCompaniesData[1], + 'item-3': mockedCompaniesData[2], + 'item-4': mockedPeopleData[0], + 'item-5': mockedPeopleData[1], + 'item-6': mockedPeopleData[2], }; + for (let i = 7; i <= 20; i++) { const key = `item-${i}`; - items[key] = { id: key, content: `Item ${i}` }; + items[key] = { + ...mockedCompaniesData[i % mockedCompaniesData.length], + id: key, + }; } export const initialBoard = [ diff --git a/front/src/modules/people/interfaces/__tests__/person.interface.test.ts b/front/src/modules/people/interfaces/__tests__/person.interface.test.ts index 3af788fdd..c0fdeae1a 100644 --- a/front/src/modules/people/interfaces/__tests__/person.interface.test.ts +++ b/front/src/modules/people/interfaces/__tests__/person.interface.test.ts @@ -39,7 +39,7 @@ describe('Person mappers', () => { phone: graphQLPerson.phone, _commentCount: 1, company: { - __typename: 'companies', + __typename: 'Company', id: '7af20dea-0412-4c4c-8b13-d6f0e6e09e87', accountOwner: undefined, address: undefined, diff --git a/front/src/modules/people/services/__tests__/update.test.ts b/front/src/modules/people/services/__tests__/update.test.ts index a1c6ba88a..221d95403 100644 --- a/front/src/modules/people/services/__tests__/update.test.ts +++ b/front/src/modules/people/services/__tests__/update.test.ts @@ -31,7 +31,7 @@ it('updates a person', async () => { id: '7dfbc3f7-6e5e-4128-957e-8d86808cdf6b', name: 'ACME', domainName: 'example.com', - __typename: 'companies', + __typename: 'Company', }, phone: '+1 (555) 123-4567', pipes: [ diff --git a/front/src/modules/ui/components/board/Board.tsx b/front/src/modules/ui/components/board/Board.tsx index d99e095e6..5222b5a59 100644 --- a/front/src/modules/ui/components/board/Board.tsx +++ b/front/src/modules/ui/components/board/Board.tsx @@ -8,10 +8,7 @@ export const StyledBoard = styled.div` `; export type BoardItemKey = `item-${number | string}`; -export interface Item { - id: string; - content?: string; -} +export type Item = any & { id: string }; export interface Items { [key: string]: Item; } diff --git a/front/src/modules/ui/components/board/BoardItem.tsx b/front/src/modules/ui/components/board/BoardItem.tsx index eae767140..ec1bf5f3d 100644 --- a/front/src/modules/ui/components/board/BoardItem.tsx +++ b/front/src/modules/ui/components/board/BoardItem.tsx @@ -7,12 +7,12 @@ const StyledCard = styled.div` border-radius: ${({ theme }) => theme.borderRadius}; box-shadow: ${({ theme }) => theme.boxShadow}; margin-bottom: ${({ theme }) => theme.spacing(2)}; - padding: ${({ theme }) => theme.spacing(2)}; + max-width: 300px; `; type BoardCardProps = { children: React.ReactNode; - draggableProvided: DraggableProvided; + draggableProvided?: DraggableProvided; }; export const BoardItem = ({ children, draggableProvided }: BoardCardProps) => { diff --git a/front/src/modules/ui/components/board/__tests__/Board.test.ts b/front/src/modules/ui/components/board/__tests__/Board.test.ts new file mode 100644 index 000000000..dc0171223 --- /dev/null +++ b/front/src/modules/ui/components/board/__tests__/Board.test.ts @@ -0,0 +1,55 @@ +import { DropResult } from '@hello-pangea/dnd'; + +import { BoardItemKey, getOptimisticlyUpdatedBoard } from '../Board'; + +describe('getOptimisticlyUpdatedBoard', () => { + it('should return a new board with the updated cell', () => { + const initialColumn1: BoardItemKey[] = ['item-1', 'item-2', 'item-3']; + const initialColumn2: BoardItemKey[] = ['item-4', 'item-5']; + + const finalColumn1: BoardItemKey[] = ['item-2', 'item-3']; + const finalColumn2: BoardItemKey[] = ['item-4', 'item-1', 'item-5']; + + const dropResult = { + source: { + droppableId: 'column-1', + index: 0, + }, + destination: { + droppableId: 'column-2', + index: 1, + }, + } as DropResult; + + const initialBoard = [ + { + id: 'column-1', + title: 'My Column', + itemKeys: initialColumn1, + }, + { + id: 'column-2', + title: 'My Column', + itemKeys: initialColumn2, + }, + ]; + + const updatedBoard = getOptimisticlyUpdatedBoard(initialBoard, dropResult); + + const finalBoard = [ + { + id: 'column-1', + title: 'My Column', + itemKeys: finalColumn1, + }, + { + id: 'column-2', + title: 'My Column', + itemKeys: finalColumn2, + }, + ]; + + expect(updatedBoard).toEqual(finalBoard); + expect(updatedBoard).not.toBe(initialBoard); + }); +}); diff --git a/front/src/modules/ui/components/table/EntityTable.tsx b/front/src/modules/ui/components/table/EntityTable.tsx index c5811feee..5c6cf9296 100644 --- a/front/src/modules/ui/components/table/EntityTable.tsx +++ b/front/src/modules/ui/components/table/EntityTable.tsx @@ -24,7 +24,7 @@ import { currentRowSelectionState } from '../../tables/states/rowSelectionState' import { TableHeader } from './table-header/TableHeader'; type OwnProps< - TData extends { id: string; __typename: 'companies' | 'people' }, + TData extends { id: string; __typename: 'Company' | 'people' }, SortField, > = { data: Array; @@ -109,7 +109,7 @@ const StyledRow = styled.tr<{ selected: boolean }>` `; export function EntityTable< - TData extends { id: string; __typename: 'companies' | 'people' }, + TData extends { id: string; __typename: 'Company' | 'people' }, SortField, >({ data, diff --git a/front/src/pages/companies/Companies.tsx b/front/src/pages/companies/Companies.tsx index 8f9068e3b..09c16c567 100644 --- a/front/src/pages/companies/Companies.tsx +++ b/front/src/pages/companies/Companies.tsx @@ -66,7 +66,7 @@ export function Companies() { pipes: [], createdAt: new Date(), accountOwner: null, - __typename: 'companies', + __typename: 'Company', }; await insertCompany(newCompany); diff --git a/front/src/pages/people/__tests__/people-filter.test.ts b/front/src/pages/people/__tests__/people-filter.test.ts index 88d16c86d..a51e4a412 100644 --- a/front/src/pages/people/__tests__/people-filter.test.ts +++ b/front/src/pages/people/__tests__/people-filter.test.ts @@ -7,7 +7,7 @@ describe('PeopleFilter', () => { id: 'test-id', name: 'test-name', domainName: 'test-domain-name', - __typename: 'companies', + __typename: 'Company', }), ).toMatchSnapshot(); }); diff --git a/front/src/testing/mock-data/companies.ts b/front/src/testing/mock-data/companies.ts index e2ad27260..0a0503896 100644 --- a/front/src/testing/mock-data/companies.ts +++ b/front/src/testing/mock-data/companies.ts @@ -15,7 +15,7 @@ export const mockedCompaniesData: Array = [ id: '7dfbc3f7-6e5e-4128-957e-8d86808cdf6b', __typename: 'users', }, - __typename: 'companies', + __typename: 'Company', }, { id: 'b396e6b9-dc5c-4643-bcff-61b6cf7523ae', @@ -26,7 +26,7 @@ export const mockedCompaniesData: Array = [ employees: 1, _commentCount: 1, accountOwner: null, - __typename: 'companies', + __typename: 'Company', }, { id: 'a674fa6c-1455-4c57-afaf-dd5dc086361d', @@ -37,7 +37,7 @@ export const mockedCompaniesData: Array = [ employees: 1, _commentCount: 1, accountOwner: null, - __typename: 'companies', + __typename: 'Company', }, { id: 'b1cfd51b-a831-455f-ba07-4e30671e1dc3', @@ -48,7 +48,7 @@ export const mockedCompaniesData: Array = [ employees: 10, _commentCount: 0, accountOwner: null, - __typename: 'companies', + __typename: 'Company', }, { id: '5c21e19e-e049-4393-8c09-3e3f8fb09ecb', @@ -59,7 +59,7 @@ export const mockedCompaniesData: Array = [ employees: 1, _commentCount: 2, accountOwner: null, - __typename: 'companies', + __typename: 'Company', }, { id: '9d162de6-cfbf-4156-a790-e39854dcd4eb', @@ -70,7 +70,7 @@ export const mockedCompaniesData: Array = [ employees: 1, _commentCount: 13, accountOwner: null, - __typename: 'companies', + __typename: 'Company', }, { id: '9d162de6-cfbf-4156-a790-e39854dcd4ef', @@ -81,6 +81,6 @@ export const mockedCompaniesData: Array = [ employees: 1, _commentCount: 1, accountOwner: null, - __typename: 'companies', + __typename: 'Company', }, ];