mirror of
https://github.com/twentyhq/twenty.git
synced 2024-12-25 04:55:30 +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 { 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}>
|
||||
|
@ -10,7 +10,7 @@ type OwnProps = {
|
||||
|
||||
export type SortType = {
|
||||
label: string;
|
||||
order: string;
|
||||
order: 'asc' | 'desc';
|
||||
id: string;
|
||||
icon?: IconProp;
|
||||
};
|
||||
|
@ -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) => {
|
||||
|
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 { 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',
|
||||
});
|
@ -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>
|
||||
|
@ -16,6 +16,9 @@ const mocks = [
|
||||
{
|
||||
request: {
|
||||
query: GET_PEOPLE,
|
||||
variables: {
|
||||
orderBy: [{ created_at: 'desc' }],
|
||||
},
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
@ -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',
|
||||
},
|
||||
];
|
||||
|
@ -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 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 = [
|
||||
|
Loading…
Reference in New Issue
Block a user