Create and EditableRelation component and make it generic (#107)

* Create and EditableRelation component and make it generic

* Refactor EditableCell component to be more flexible

* Complete Company picker on people page

* Fix lint
This commit is contained in:
Charles Bochet
2023-05-06 16:08:45 +02:00
committed by GitHub
parent 7ac2f8e1a6
commit 41c46c36ed
21 changed files with 637 additions and 198 deletions

View File

@ -31,7 +31,7 @@ const StyledPeopleContainer = styled.div`
function People() {
const [orderBy, setOrderBy] = useState(defaultOrderBy);
const [where, setWhere] = useState<People_Bool_Exp>({});
const [filterSearchResults, setSearhInput, setFilterSearch] = useSearch();
const [filterSearchResults, setSearchInput, setFilterSearch] = useSearch();
const updateSorts = useCallback((sorts: Array<PeopleSelectedSortType>) => {
setOrderBy(sorts.length ? reduceSortsToOrderBy(sorts) : defaultOrderBy);
@ -61,7 +61,7 @@ function People() {
onSortsUpdate={updateSorts}
onFiltersUpdate={updateFilters}
onFilterSearch={(filter, searchValue) => {
setSearhInput(searchValue);
setSearchInput(searchValue);
setFilterSearch(filter);
}}
/>

View File

@ -1,12 +1,85 @@
import { render, waitFor } from '@testing-library/react';
import { fireEvent, render, waitFor } from '@testing-library/react';
import { PeopleDefault } from '../__stories__/People.stories';
import { act } from 'react-dom/test-utils';
import {
GraphqlMutationPerson,
GraphqlQueryPerson,
} from '../../../interfaces/person.interface';
it('Checks the People page render', async () => {
const { getByTestId } = render(<PeopleDefault />);
jest.mock('../../../apollo', () => {
const personInterface = jest.requireActual(
'../../../interfaces/person.interface',
);
return {
apiClient: {
mutate: (arg: {
mutation: unknown;
variables: GraphqlMutationPerson;
}) => {
const gqlPerson = arg.variables as unknown as GraphqlQueryPerson;
return { data: personInterface.mapPerson(gqlPerson) };
},
},
};
});
it('Checks people full name edit is updating data', async () => {
const { getByText, getByDisplayValue } = render(<PeopleDefault />);
await waitFor(() => {
const personChip = getByTestId('row-id-0');
expect(personChip).toBeDefined();
expect(getByText('John Doe')).toBeDefined();
});
act(() => {
fireEvent.click(getByText('John Doe'));
});
await waitFor(() => {
expect(getByDisplayValue('John')).toBeInTheDocument();
});
act(() => {
const nameInput = getByDisplayValue('John');
if (!nameInput) {
throw new Error('firstNameInput is null');
}
fireEvent.change(nameInput, { target: { value: 'Jo' } });
fireEvent.click(getByText('All People')); // Click outside
});
await waitFor(() => {
expect(getByText('Jo Doe')).toBeInTheDocument();
});
});
it('Checks people email edit is updating data', async () => {
const { getByText, getByDisplayValue } = render(<PeopleDefault />);
await waitFor(() => {
expect(getByText('john@linkedin.com')).toBeDefined();
});
act(() => {
fireEvent.click(getByText('john@linkedin.com'));
});
await waitFor(() => {
expect(getByDisplayValue('john@linkedin.com')).toBeInTheDocument();
});
act(() => {
const emailInput = getByDisplayValue('john@linkedin.com');
if (!emailInput) {
throw new Error('emailInput is null');
}
fireEvent.change(emailInput, { target: { value: 'john@linkedin.c' } });
fireEvent.click(getByText('All People')); // Click outside
});
await waitFor(() => {
expect(getByText('john@linkedin.c')).toBeInTheDocument();
});
});

View File

@ -13,11 +13,12 @@ import { createColumnHelper } from '@tanstack/react-table';
import ClickableCell from '../../components/table/ClickableCell';
import ColumnHead from '../../components/table/ColumnHead';
import Checkbox from '../../components/form/Checkbox';
import CompanyChip from '../../components/chips/CompanyChip';
import CompanyChip, {
CompanyChipPropsType,
} from '../../components/chips/CompanyChip';
import { GraphqlQueryPerson, Person } from '../../interfaces/person.interface';
import PipeChip from '../../components/chips/PipeChip';
import EditableText from '../../components/table/editable-cell/EditableText';
import { updatePerson } from '../../services/people';
import {
FilterType,
SortType,
@ -31,10 +32,15 @@ import {
SEARCH_COMPANY_QUERY,
SEARCH_PEOPLE_QUERY,
} from '../../services/search/search';
import { GraphqlQueryCompany } from '../../interfaces/company.interface';
import {
GraphqlQueryCompany,
PartialCompany,
} from '../../interfaces/company.interface';
import EditablePhone from '../../components/table/editable-cell/EditablePhone';
import EditableFullName from '../../components/table/editable-cell/EditableFullName';
import EditableDate from '../../components/table/editable-cell/EditableDate';
import EditableRelation from '../../components/table/editable-cell/EditableRelation';
import { updatePerson } from '../../services/people';
export const availableSorts = [
{
@ -261,7 +267,7 @@ export const peopleColumns = [
const person = props.row.original;
person.firstname = firstName;
person.lastname = lastName;
updatePerson(person).catch((error) => console.error(error)); // TODO: handle error
updatePerson(person);
}}
/>
),
@ -275,7 +281,7 @@ export const peopleColumns = [
changeHandler={(value: string) => {
const person = props.row.original;
person.email = value;
updatePerson(person).catch((error) => console.error(error)); // TODO: handle error
updatePerson(person);
}}
/>
),
@ -285,12 +291,47 @@ export const peopleColumns = [
<ColumnHead viewName="Company" viewIcon={<FaRegBuilding />} />
),
cell: (props) => (
<ClickableCell href="#">
<CompanyChip
name={props.row.original.company.name}
picture={`https://www.google.com/s2/favicons?domain=${props.row.original.company.domain_name}&sz=256`}
/>
</ClickableCell>
<EditableRelation<PartialCompany, CompanyChipPropsType>
relation={props.row.original.company}
searchPlaceholder="Company"
ChipComponent={CompanyChip}
chipComponentPropsMapper={(
company: PartialCompany,
): CompanyChipPropsType => {
return {
name: company.name,
picture: `https://www.google.com/s2/favicons?domain=${company.domain_name}&sz=256`,
};
}}
changeHandler={(relation: PartialCompany) => {
const person = props.row.original;
person.company.id = relation.id;
updatePerson(person);
}}
searchFilter={
{
key: 'company_name',
label: 'Company',
icon: <FaBuilding />,
whereTemplate: () => {
return {};
},
searchQuery: SEARCH_COMPANY_QUERY,
searchTemplate: (searchInput: string) => ({
name: { _ilike: `%${searchInput}%` },
}),
searchResultMapper: (company: GraphqlQueryCompany) => ({
displayValue: company.name,
value: {
id: company.id,
name: company.name,
domain_name: company.domain_name,
},
}),
operands: [],
} satisfies FilterType<People_Bool_Exp>
}
/>
),
}),
columnHelper.accessor('phone', {
@ -302,7 +343,7 @@ export const peopleColumns = [
changeHandler={(value: string) => {
const person = props.row.original;
person.phone = value;
updatePerson(person).catch((error) => console.error(error)); // TODO: handle error
updatePerson(person);
}}
/>
),
@ -315,7 +356,7 @@ export const peopleColumns = [
changeHandler={(value: Date) => {
const person = props.row.original;
person.creationDate = value;
updatePerson(person).catch((error) => console.error(error)); // TODO: handle error
updatePerson(person);
}}
/>
),
@ -332,13 +373,13 @@ export const peopleColumns = [
header: () => <ColumnHead viewName="City" viewIcon={<FaMapPin />} />,
cell: (props) => (
<EditableText
shouldAlignRight={true}
editModeHorizontalAlign="right"
placeholder="City"
content={props.row.original.city}
changeHandler={(value: string) => {
const person = props.row.original;
person.city = value;
updatePerson(person).catch((error) => console.error(error)); // TODO: handle error
updatePerson(person);
}}
/>
),