Enable add person on People Table (#111)

Add possibility to add Person on People table
This commit is contained in:
Charles Bochet 2023-05-08 00:15:32 +02:00 committed by GitHub
parent 50a4a97145
commit 48a75358b4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 301 additions and 153 deletions

View File

@ -50,7 +50,7 @@ describe('mapCompany', () => {
expect(company.name).toBe('ACME'); expect(company.name).toBe('ACME');
expect(company.domain_name).toBe('exmaple.com'); expect(company.domain_name).toBe('exmaple.com');
expect(company.creationDate).toEqual(now); expect(company.creationDate).toEqual(now);
expect(company.accountOwner).toBeUndefined(); expect(company.accountOwner).toBeNull();
expect(company.employees).toBe(10); expect(company.employees).toBe(10);
expect(company.address).toBe( expect(company.address).toBe(
'1 Infinite Loop, 95014 Cupertino, California, USA', '1 Infinite Loop, 95014 Cupertino, California, USA',

View File

@ -54,7 +54,7 @@ export const mapCompany = (company: GraphqlQueryCompany): Company => ({
email: company.account_owner.email, email: company.account_owner.email,
displayName: company.account_owner.displayName, displayName: company.account_owner.displayName,
} }
: undefined, : null,
creationDate: new Date(company.created_at), creationDate: new Date(company.created_at),
opportunities: [], opportunities: [],
}); });

View File

@ -44,7 +44,6 @@ describe('mapPerson', () => {
name: '', name: '',
icon: '', icon: '',
}, },
countryCode: '',
}); });
expect(person.firstname).toBe('John'); expect(person.firstname).toBe('John');
}); });

View File

@ -7,12 +7,11 @@ export type Person = {
lastname: string; lastname: string;
picture?: string; picture?: string;
email: string; email: string;
company: PartialCompany; company: PartialCompany | null;
phone: string; phone: string;
creationDate: Date; creationDate: Date;
pipe: Pipe; pipe: Pipe | null;
city: string; city: string;
countryCode: string;
}; };
export type GraphqlQueryPerson = { export type GraphqlQueryPerson = {
@ -45,7 +44,10 @@ export type GraphqlMutationPerson = {
}; };
export const mapPerson = (person: GraphqlQueryPerson): Person => ({ export const mapPerson = (person: GraphqlQueryPerson): Person => ({
...person, id: person.id,
email: person.email,
phone: person.phone,
city: person.city,
firstname: person.firstname, firstname: person.firstname,
lastname: person.lastname, lastname: person.lastname,
creationDate: new Date(person.created_at), creationDate: new Date(person.created_at),
@ -54,12 +56,13 @@ export const mapPerson = (person: GraphqlQueryPerson): Person => ({
id: '7dfbc3f7-6e5e-4128-957e-8d86808cdf6b', id: '7dfbc3f7-6e5e-4128-957e-8d86808cdf6b',
icon: '💰', icon: '💰',
}, },
company: { company: person.company
? {
id: person.company.id, id: person.company.id,
name: person.company.name, name: person.company.name,
domain_name: person.company.domain_name, domain_name: person.company.domain_name,
}, }
countryCode: 'FR', : null,
}); });
export const mapGqlPerson = (person: Person): GraphqlMutationPerson => ({ export const mapGqlPerson = (person: Person): GraphqlMutationPerson => ({
@ -67,6 +70,6 @@ export const mapGqlPerson = (person: Person): GraphqlMutationPerson => ({
firstname: person.firstname, firstname: person.firstname,
lastname: person.lastname, lastname: person.lastname,
created_at: person.creationDate.toUTCString(), created_at: person.creationDate.toUTCString(),
company_id: person.company.id, company_id: person.company?.id,
__typename: 'People', __typename: 'People',
}); });

View File

@ -28,6 +28,20 @@ const mocks = [
}, },
}, },
}, },
{
request: {
query: GET_COMPANIES,
variables: {
orderBy: [{ created_at: 'desc' }],
where: {},
},
},
result: {
data: {
companies: mockData,
},
},
},
]; ];
export const CompaniesDefault = () => ( export const CompaniesDefault = () => (

View File

@ -1,17 +1,19 @@
import { FaRegUser, FaList } from 'react-icons/fa'; import { FaRegUser, FaList } from 'react-icons/fa';
import WithTopBarContainer from '../../layout/containers/WithTopBarContainer'; import WithTopBarContainer from '../../layout/containers/WithTopBarContainer';
import Table from '../../components/table/Table'; import Table from '../../components/table/Table';
import { v4 as uuidv4 } from 'uuid';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { import {
availableFilters, availableFilters,
peopleColumns,
availableSorts, availableSorts,
usePeopleColumns,
} from './people-table'; } from './people-table';
import { mapPerson } from '../../interfaces/person.interface'; import { Person, mapPerson } from '../../interfaces/person.interface';
import { useCallback, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
import { import {
PeopleSelectedSortType, PeopleSelectedSortType,
defaultOrderBy, defaultOrderBy,
insertPerson,
usePeopleQuery, usePeopleQuery,
} from '../../services/people'; } from '../../services/people';
import { useSearch } from '../../services/search/search'; import { useSearch } from '../../services/search/search';
@ -32,6 +34,7 @@ function People() {
const [orderBy, setOrderBy] = useState(defaultOrderBy); const [orderBy, setOrderBy] = useState(defaultOrderBy);
const [where, setWhere] = useState<People_Bool_Exp>({}); const [where, setWhere] = useState<People_Bool_Exp>({});
const [filterSearchResults, setSearchInput, setFilterSearch] = useSearch(); const [filterSearchResults, setSearchInput, setFilterSearch] = useSearch();
const [internalData, setInternalData] = useState<Array<Person>>([]);
const updateSorts = useCallback((sorts: Array<PeopleSelectedSortType>) => { const updateSorts = useCallback((sorts: Array<PeopleSelectedSortType>) => {
setOrderBy(sorts.length ? reduceSortsToOrderBy(sorts) : defaultOrderBy); setOrderBy(sorts.length ? reduceSortsToOrderBy(sorts) : defaultOrderBy);
@ -44,11 +47,34 @@ function People() {
[], [],
); );
const addEmptyRow = useCallback(() => { const { data, loading, refetch } = usePeopleQuery(orderBy, where);
console.log('add row');
}, []);
const { data } = usePeopleQuery(orderBy, where); useEffect(() => {
if (!loading) {
if (data) {
setInternalData(data.people.map(mapPerson));
}
}
}, [loading, setInternalData, data]);
const addEmptyRow = useCallback(() => {
const newCompany: Person = {
id: uuidv4(),
firstname: '',
lastname: '',
email: '',
phone: '',
company: null,
pipe: null,
creationDate: new Date(),
city: '',
};
insertPerson(newCompany);
setInternalData([newCompany, ...internalData]);
refetch();
}, [internalData, setInternalData, refetch]);
const peopleColumns = usePeopleColumns();
return ( return (
<WithTopBarContainer <WithTopBarContainer
@ -59,7 +85,7 @@ function People() {
<StyledPeopleContainer> <StyledPeopleContainer>
{ {
<Table <Table
data={data ? data.people.map(mapPerson) : []} data={internalData}
columns={peopleColumns} columns={peopleColumns}
viewName="All People" viewName="All People"
viewIcon={<FaList />} viewIcon={<FaList />}

View File

@ -29,6 +29,20 @@ const mocks = [
}, },
}, },
}, },
{
request: {
query: GET_PEOPLE,
variables: {
orderBy: [{ created_at: 'desc' }],
where: {},
},
},
result: {
data: {
people: mockData,
},
},
},
{ {
request: { request: {
query: SEARCH_PEOPLE_QUERY, // TODO this should not be called for empty filters query: SEARCH_PEOPLE_QUERY, // TODO this should not be called for empty filters

View File

@ -83,3 +83,24 @@ it('Checks people email edit is updating data', async () => {
expect(getByText('john@linkedin.c')).toBeInTheDocument(); expect(getByText('john@linkedin.c')).toBeInTheDocument();
}); });
}); });
it('Checks insert data is appending a new line', async () => {
const { getByText, getByTestId, container } = render(<PeopleDefault />);
await waitFor(() => {
expect(getByText('John Doe')).toBeDefined();
});
const tableRows = container.querySelectorAll<HTMLElement>('table tbody tr');
expect(tableRows.length).toBe(4);
act(() => {
fireEvent.click(getByTestId('add-button'));
});
await waitFor(() => {
const tableRows = container.querySelectorAll<HTMLElement>('table tbody tr');
expect(tableRows.length).toBe(5);
});
});

View File

@ -38,6 +38,7 @@ import EditableFullName from '../../components/table/editable-cell/EditableFullN
import EditableDate from '../../components/table/editable-cell/EditableDate'; import EditableDate from '../../components/table/editable-cell/EditableDate';
import EditableRelation from '../../components/table/editable-cell/EditableRelation'; import EditableRelation from '../../components/table/editable-cell/EditableRelation';
import { updatePerson } from '../../services/people'; import { updatePerson } from '../../services/people';
import { useMemo } from 'react';
export const availableSorts = [ export const availableSorts = [
{ {
@ -242,7 +243,10 @@ export const availableFilters = [
] satisfies FilterType<People_Bool_Exp>[]; ] satisfies FilterType<People_Bool_Exp>[];
const columnHelper = createColumnHelper<Person>(); const columnHelper = createColumnHelper<Person>();
export const peopleColumns = [
export const usePeopleColumns = () => {
return useMemo(() => {
return [
columnHelper.accessor('id', { columnHelper.accessor('id', {
header: () => ( header: () => (
<Checkbox id={`person-select-all`} name={`person-select-all`} /> <Checkbox id={`person-select-all`} name={`person-select-all`} />
@ -302,7 +306,15 @@ export const peopleColumns = [
}} }}
changeHandler={(relation: PartialCompany) => { changeHandler={(relation: PartialCompany) => {
const person = props.row.original; const person = props.row.original;
if (person.company) {
person.company.id = relation.id; person.company.id = relation.id;
} else {
person.company = {
id: relation.id,
name: relation.name,
domain_name: relation.domain_name,
};
}
updatePerson(person); updatePerson(person);
}} }}
searchFilter={ searchFilter={
@ -346,7 +358,9 @@ export const peopleColumns = [
), ),
}), }),
columnHelper.accessor('creationDate', { columnHelper.accessor('creationDate', {
header: () => <ColumnHead viewName="Creation" viewIcon={<FaCalendar />} />, header: () => (
<ColumnHead viewName="Creation" viewIcon={<FaCalendar />} />
),
cell: (props) => ( cell: (props) => (
<EditableDate <EditableDate
value={props.row.original.creationDate} value={props.row.original.creationDate}
@ -373,4 +387,6 @@ export const peopleColumns = [
/> />
), ),
}), }),
]; ];
}, []);
};

View File

@ -44,6 +44,48 @@ export const UPDATE_PERSON = gql`
} }
`; `;
export const INSERT_PERSON = gql`
mutation InsertPerson(
$id: uuid
$firstname: String
$lastname: String
$phone: String
$city: String
$company_id: uuid
$email: String
$created_at: timestamptz
) {
insert_people(
objects: {
id: $id
firstname: $firstname
lastname: $lastname
phone: $phone
city: $city
company_id: $company_id
email: $email
created_at: $created_at
}
) {
affected_rows
returning {
city
company {
domain_name
name
id
}
email
firstname
id
lastname
phone
created_at
}
}
}
`;
export async function updatePerson( export async function updatePerson(
person: Person, person: Person,
): Promise<FetchResult<Person>> { ): Promise<FetchResult<Person>> {
@ -53,3 +95,14 @@ export async function updatePerson(
}); });
return result; return result;
} }
export async function insertPerson(
person: Person,
): Promise<FetchResult<Person>> {
const result = await apiClient.mutate({
mutation: INSERT_PERSON,
variables: mapGqlPerson(person),
});
return result;
}

View File

@ -14,6 +14,8 @@ insert_permissions:
check: check:
workspace_id: workspace_id:
_eq: x-hasura-workspace-id _eq: x-hasura-workspace-id
set:
workspace_id: x-hasura-Workspace-Id
columns: columns:
- city - city
- email - email