Merge pull request #56 from twentyhq/sammy/t-111-when-created-at-sort-is-applied-table

Sammy/t 111 when created at sort is applied table
This commit is contained in:
Charles Bochet 2023-04-20 18:47:50 +02:00 committed by GitHub
commit 007debcee2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 144 additions and 61 deletions

View File

@ -9,12 +9,14 @@ import {
import TableHeader from './table-header/TableHeader'; import TableHeader from './table-header/TableHeader';
import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { IconProp } from '@fortawesome/fontawesome-svg-core';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { SortType } from './table-header/SortAndFilterBar';
type OwnProps<TData> = { type OwnProps<TData> = {
data: Array<TData>; data: Array<TData>;
columns: Array<ColumnDef<TData, any>>; columns: Array<ColumnDef<TData, any>>;
viewName: string; viewName: string;
viewIcon?: IconProp; viewIcon?: IconProp;
onSortsUpdate?: React.Dispatch<React.SetStateAction<SortType[]>>;
}; };
const StyledTable = styled.table` const StyledTable = styled.table`
@ -60,7 +62,13 @@ const StyledTableWithHeader = styled.div`
flex: 1; flex: 1;
`; `;
function Table<TData>({ data, columns, viewName, viewIcon }: OwnProps<TData>) { function Table<TData>({
data,
columns,
viewName,
viewIcon,
onSortsUpdate,
}: OwnProps<TData>) {
const table = useReactTable({ const table = useReactTable({
data, data,
columns, columns,
@ -69,7 +77,11 @@ function Table<TData>({ data, columns, viewName, viewIcon }: OwnProps<TData>) {
return ( return (
<StyledTableWithHeader> <StyledTableWithHeader>
<TableHeader viewName={viewName} viewIcon={viewIcon} /> <TableHeader
viewName={viewName}
viewIcon={viewIcon}
onSortsUpdate={onSortsUpdate}
/>
<StyledTable> <StyledTable>
<thead> <thead>
{table.getHeaderGroups().map((headerGroup) => ( {table.getHeaderGroups().map((headerGroup) => (
@ -88,8 +100,8 @@ function Table<TData>({ data, columns, viewName, viewIcon }: OwnProps<TData>) {
))} ))}
</thead> </thead>
<tbody> <tbody>
{table.getRowModel().rows.map((row) => ( {table.getRowModel().rows.map((row, index) => (
<tr key={row.id}> <tr key={row.id} data-testid={`row-id-${row.index}`}>
{row.getVisibleCells().map((cell) => { {row.getVisibleCells().map((cell) => {
return ( return (
<td key={cell.id}> <td key={cell.id}>

View File

@ -10,7 +10,7 @@ type OwnProps = {
export type SortType = { export type SortType = {
label: string; label: string;
order: string; order: 'asc' | 'desc';
id: string; id: string;
icon?: IconProp; icon?: IconProp;
}; };

View File

@ -9,6 +9,7 @@ import { useState } from 'react';
type OwnProps = { type OwnProps = {
viewName: string; viewName: string;
viewIcon?: IconProp; viewIcon?: IconProp;
onSortsUpdate?: React.Dispatch<React.SetStateAction<SortType[]>>;
}; };
const StyledContainer = styled.div` const StyledContainer = styled.div`
@ -43,8 +44,9 @@ const StyledFilters = styled.div`
margin-right: ${(props) => props.theme.spacing(2)}; margin-right: ${(props) => props.theme.spacing(2)};
`; `;
function TableHeader({ viewName, viewIcon }: OwnProps) { function TableHeader({ viewName, viewIcon, onSortsUpdate }: OwnProps) {
const [sorts, setSorts] = useState([] as Array<SortType>); const [sorts, setSorts] = useState([] as Array<SortType>);
const onSortItemSelect = (sortId: string) => { const onSortItemSelect = (sortId: string) => {
setSorts([ setSorts([
{ {
@ -53,6 +55,7 @@ function TableHeader({ viewName, viewIcon }: OwnProps) {
id: sortId, id: sortId,
}, },
]); ]);
onSortsUpdate && onSortsUpdate(sorts);
}; };
const onSortItemUnSelect = (sortId: string) => { const onSortItemUnSelect = (sortId: string) => {

View File

@ -0,0 +1,22 @@
import { mapPerson } from './person.interface';
describe('mapPerson', () => {
it('should map person', () => {
const person = mapPerson({
id: 1,
firstname: 'John',
lastname: 'Doe',
email: '',
phone: '',
city: '',
created_at: '',
company: {
__typename: '',
company_name: '',
company_domain: '',
},
__typename: '',
});
expect(person.fullName).toBe('John Doe');
});
});

View File

@ -1,5 +1,5 @@
import { Company } from '../../interfaces/company.interface'; import { Company } from './company.interface';
import { Pipe } from '../../interfaces/pipe.interface'; import { Pipe } from './pipe.interface';
export type Person = { export type Person = {
fullName: string; fullName: string;
@ -28,3 +28,16 @@ export type GraphqlPerson = {
phone: string; phone: string;
__typename: string; __typename: string;
}; };
export const mapPerson = (person: GraphqlPerson): Person => ({
fullName: `${person.firstname} ${person.lastname}`,
creationDate: new Date(person.created_at),
pipe: { name: 'coucou', id: 1, icon: 'faUser' },
...person,
company: {
id: 1,
name: person.company.company_name,
domain: person.company.company_domain,
},
countryCode: 'FR',
});

View File

@ -4,9 +4,9 @@ import Table from '../../components/table/Table';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { peopleColumns } from './people-table'; import { peopleColumns } from './people-table';
import { gql, useQuery } from '@apollo/client'; import { gql, useQuery } from '@apollo/client';
import { GraphqlPerson, Person } from './types'; import { GraphqlPerson, mapPerson } from '../../interfaces/person.interface';
import { defaultData } from './default-data'; import { useState } from 'react';
import { mapPerson } from './mapper'; import { SortType } from '../../components/table/table-header/SortAndFilterBar';
const StyledPeopleContainer = styled.div` const StyledPeopleContainer = styled.div`
display: flex; display: flex;
@ -14,8 +14,8 @@ const StyledPeopleContainer = styled.div`
`; `;
export const GET_PEOPLE = gql` export const GET_PEOPLE = gql`
query GetPeople { query GetPeople($orderBy: [person_order_by!]) {
person { person(order_by: $orderBy) {
id id
phone phone
email email
@ -31,20 +31,40 @@ export const GET_PEOPLE = gql`
} }
`; `;
function People() { // @TODO get those types from generated-code person-order-by
const { data } = useQuery<{ person: GraphqlPerson[] }>(GET_PEOPLE); type OrderBy = Record<string, 'asc' | 'desc'>;
const mydata: Person[] = data ? data.person.map(mapPerson) : defaultData; const defaultOrderBy = [
{
created_at: 'desc',
},
];
const reduceSortsToOrderBy = (sorts: Array<SortType>): OrderBy[] => {
const mappedSorts = sorts.reduce((acc, sort) => {
acc[sort.id] = sort.order;
return acc;
}, {} as OrderBy);
return [mappedSorts];
};
function People() {
const [sorts, setSorts] = useState([] as Array<SortType>);
const orderBy = sorts.length ? reduceSortsToOrderBy(sorts) : defaultOrderBy;
const { data } = useQuery<{ person: GraphqlPerson[] }>(GET_PEOPLE, {
variables: { orderBy: orderBy },
});
return ( return (
<WithTopBarContainer title="People" icon={faUser}> <WithTopBarContainer title="People" icon={faUser}>
<StyledPeopleContainer> <StyledPeopleContainer>
{mydata && ( {data && (
<Table <Table
data={mydata} data={data.person.map(mapPerson)}
columns={peopleColumns} columns={peopleColumns}
viewName="All People" viewName="All People"
viewIcon={faList} viewIcon={faList}
onSortsUpdate={setSorts}
/> />
)} )}
</StyledPeopleContainer> </StyledPeopleContainer>

View File

@ -16,6 +16,9 @@ const mocks = [
{ {
request: { request: {
query: GET_PEOPLE, query: GET_PEOPLE,
variables: {
orderBy: [{ created_at: 'desc' }],
},
}, },
result: { result: {
data: { data: {

View File

@ -1,10 +1,12 @@
import { render } from '@testing-library/react'; import { render, waitFor } from '@testing-library/react';
import { PeopleDefault } from '../__stories__/People.stories'; import { PeopleDefault } from '../__stories__/People.stories';
it('Checks the People page render', () => { it('Checks the People page render', async () => {
const { getByTestId } = render(<PeopleDefault />); const { getByTestId } = render(<PeopleDefault />);
const title = getByTestId('top-bar-title'); await waitFor(() => {
expect(title).toHaveTextContent('People'); const personChip = getByTestId('row-id-0');
expect(personChip).toBeDefined();
});
}); });

View File

@ -1,47 +1,69 @@
import { Person } from './types'; import { GraphqlPerson } from '../../interfaces/person.interface';
export const defaultData: Array<Person> = [ export const defaultData: Array<GraphqlPerson> = [
{ {
fullName: 'Alexandre Prot', id: 1,
picture: 'http://placekitten.com/256', __typename: 'Person',
firstname: 'Alexandre',
lastname: 'Prot',
email: 'alexandre@qonto.com', email: 'alexandre@qonto.com',
company: { id: 1, name: 'Qonto', domain: 'qonto.com' }, company: {
company_name: 'Qonto',
company_domain: 'qonto.com',
__typename: 'Company',
},
phone: '06 12 34 56 78', phone: '06 12 34 56 78',
creationDate: new Date('Feb 23, 2018'), created_at: '2023-04-20T13:20:09.158312+00:00',
pipe: { id: 1, name: 'Sales Pipeline', icon: 'faUser' },
city: 'Paris', city: 'Paris',
countryCode: 'FR',
}, },
{ {
fullName: 'Alexandre Prot', id: 2,
__typename: 'Person',
firstname: 'Alexandre',
lastname: 'Prot',
email: 'alexandre@qonto.com', email: 'alexandre@qonto.com',
company: { id: 2, name: 'LinkedIn', domain: 'linkedin.com' }, company: {
company_name: 'LinkedIn',
company_domain: 'linkedin.com',
__typename: 'Company',
},
phone: '06 12 34 56 78', phone: '06 12 34 56 78',
creationDate: new Date('Feb 22, 2018'), created_at: '2023-04-20T13:20:09.158312+00:00',
pipe: { id: 1, name: 'Sales Pipeline', icon: 'faUser' },
city: 'Paris', city: 'Paris',
countryCode: 'FR',
}, },
{ {
fullName: 'Alexandre Prot', id: 3,
picture: 'http://placekitten.com/256', __typename: 'Person',
firstname: 'Alexandre',
lastname: 'Prot',
email: 'alexandre@qonto.com', email: 'alexandre@qonto.com',
company: { id: 5, name: 'Sequoia', domain: 'sequoiacap.com' }, company: {
company_name: 'Sequoia',
company_domain: 'sequoiacap.com',
__typename: 'Company',
},
phone: '06 12 34 56 78', phone: '06 12 34 56 78',
creationDate: new Date('Feb 21, 2018'), created_at: '2023-04-20T13:20:09.158312+00:00',
pipe: { id: 1, name: 'Sales Pipeline', icon: 'faUser' },
city: 'Paris', city: 'Paris',
countryCode: 'FR',
}, },
{ {
fullName: 'Alexandre Prot', id: 4,
__typename: 'Person',
firstname: 'Alexandre',
lastname: 'Prot',
email: 'alexandre@qonto.com', email: 'alexandre@qonto.com',
company: { id: 2, name: 'Facebook', domain: 'facebook.com' }, company: {
company_name: 'Facebook',
company_domain: 'facebook.com',
__typename: 'Company',
},
phone: '06 12 34 56 78', phone: '06 12 34 56 78',
creationDate: new Date('Feb 25, 2018'), created_at: '2023-04-20T13:20:09.158312+00:00',
pipe: { id: 1, name: 'Sales Pipeline', icon: 'faUser' },
city: 'Paris', city: 'Paris',
countryCode: 'FR',
}, },
]; ];

View File

@ -1,14 +0,0 @@
import { GraphqlPerson, Person } from './types';
export const mapPerson = (person: GraphqlPerson): Person => ({
fullName: `${person.firstname} ${person.lastname}`,
creationDate: new Date(person.created_at),
pipe: { name: 'coucou', id: 1, icon: 'faUser' },
...person,
company: {
id: 1,
name: person.company.company_name,
domain: person.company.company_domain,
},
countryCode: 'FR',
});

View File

@ -15,7 +15,7 @@ import Checkbox from '../../components/form/Checkbox';
import HorizontalyAlignedContainer from '../../layout/containers/HorizontalyAlignedContainer'; import HorizontalyAlignedContainer from '../../layout/containers/HorizontalyAlignedContainer';
import CompanyChip from '../../components/chips/CompanyChip'; import CompanyChip from '../../components/chips/CompanyChip';
import PersonChip from '../../components/chips/PersonChip'; import PersonChip from '../../components/chips/PersonChip';
import { Person } from './types'; import { Person } from '../../interfaces/person.interface';
const columnHelper = createColumnHelper<Person>(); const columnHelper = createColumnHelper<Person>();
export const peopleColumns = [ export const peopleColumns = [