mirror of
https://github.com/twentyhq/twenty.git
synced 2024-12-25 21:13:01 +03:00
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:
commit
007debcee2
@ -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}>
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
|
@ -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) => {
|
||||||
|
22
front/src/interfaces/person.interface.test.ts
Normal file
22
front/src/interfaces/person.interface.test.ts
Normal 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');
|
||||||
|
});
|
||||||
|
});
|
@ -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',
|
||||||
|
});
|
@ -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>
|
||||||
|
@ -16,6 +16,9 @@ const mocks = [
|
|||||||
{
|
{
|
||||||
request: {
|
request: {
|
||||||
query: GET_PEOPLE,
|
query: GET_PEOPLE,
|
||||||
|
variables: {
|
||||||
|
orderBy: [{ created_at: 'desc' }],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
result: {
|
result: {
|
||||||
data: {
|
data: {
|
||||||
|
@ -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();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -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',
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@ -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',
|
|
||||||
});
|
|
@ -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 = [
|
||||||
|
Loading…
Reference in New Issue
Block a user