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

View File

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

View File

@ -9,6 +9,7 @@ import { useState } from 'react';
type OwnProps = {
viewName: string;
viewIcon?: IconProp;
onSortsUpdate?: React.Dispatch<React.SetStateAction<SortType[]>>;
};
const StyledContainer = styled.div`
@ -43,8 +44,9 @@ const StyledFilters = styled.div`
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 onSortItemSelect = (sortId: string) => {
setSorts([
{
@ -53,6 +55,7 @@ function TableHeader({ viewName, viewIcon }: OwnProps) {
id: sortId,
},
]);
onSortsUpdate && onSortsUpdate(sorts);
};
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 { Pipe } from '../../interfaces/pipe.interface';
import { Company } from './company.interface';
import { Pipe } from './pipe.interface';
export type Person = {
fullName: string;
@ -28,3 +28,16 @@ export type GraphqlPerson = {
phone: 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 { peopleColumns } from './people-table';
import { gql, useQuery } from '@apollo/client';
import { GraphqlPerson, Person } from './types';
import { defaultData } from './default-data';
import { mapPerson } from './mapper';
import { GraphqlPerson, mapPerson } from '../../interfaces/person.interface';
import { useState } from 'react';
import { SortType } from '../../components/table/table-header/SortAndFilterBar';
const StyledPeopleContainer = styled.div`
display: flex;
@ -14,8 +14,8 @@ const StyledPeopleContainer = styled.div`
`;
export const GET_PEOPLE = gql`
query GetPeople {
person {
query GetPeople($orderBy: [person_order_by!]) {
person(order_by: $orderBy) {
id
phone
email
@ -31,20 +31,40 @@ export const GET_PEOPLE = gql`
}
`;
function People() {
const { data } = useQuery<{ person: GraphqlPerson[] }>(GET_PEOPLE);
// @TODO get those types from generated-code person-order-by
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 (
<WithTopBarContainer title="People" icon={faUser}>
<StyledPeopleContainer>
{mydata && (
{data && (
<Table
data={mydata}
data={data.person.map(mapPerson)}
columns={peopleColumns}
viewName="All People"
viewIcon={faList}
onSortsUpdate={setSorts}
/>
)}
</StyledPeopleContainer>

View File

@ -16,6 +16,9 @@ const mocks = [
{
request: {
query: GET_PEOPLE,
variables: {
orderBy: [{ created_at: 'desc' }],
},
},
result: {
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';
it('Checks the People page render', () => {
it('Checks the People page render', async () => {
const { getByTestId } = render(<PeopleDefault />);
const title = getByTestId('top-bar-title');
expect(title).toHaveTextContent('People');
await waitFor(() => {
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',
picture: 'http://placekitten.com/256',
id: 1,
__typename: 'Person',
firstname: 'Alexandre',
lastname: 'Prot',
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',
creationDate: new Date('Feb 23, 2018'),
pipe: { id: 1, name: 'Sales Pipeline', icon: 'faUser' },
created_at: '2023-04-20T13:20:09.158312+00:00',
city: 'Paris',
countryCode: 'FR',
},
{
fullName: 'Alexandre Prot',
id: 2,
__typename: 'Person',
firstname: 'Alexandre',
lastname: 'Prot',
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',
creationDate: new Date('Feb 22, 2018'),
pipe: { id: 1, name: 'Sales Pipeline', icon: 'faUser' },
created_at: '2023-04-20T13:20:09.158312+00:00',
city: 'Paris',
countryCode: 'FR',
},
{
fullName: 'Alexandre Prot',
picture: 'http://placekitten.com/256',
id: 3,
__typename: 'Person',
firstname: 'Alexandre',
lastname: 'Prot',
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',
creationDate: new Date('Feb 21, 2018'),
pipe: { id: 1, name: 'Sales Pipeline', icon: 'faUser' },
created_at: '2023-04-20T13:20:09.158312+00:00',
city: 'Paris',
countryCode: 'FR',
},
{
fullName: 'Alexandre Prot',
id: 4,
__typename: 'Person',
firstname: 'Alexandre',
lastname: 'Prot',
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',
creationDate: new Date('Feb 25, 2018'),
pipe: { id: 1, name: 'Sales Pipeline', icon: 'faUser' },
created_at: '2023-04-20T13:20:09.158312+00:00',
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 CompanyChip from '../../components/chips/CompanyChip';
import PersonChip from '../../components/chips/PersonChip';
import { Person } from './types';
import { Person } from '../../interfaces/person.interface';
const columnHelper = createColumnHelper<Person>();
export const peopleColumns = [