Sammy/t 195 aau i see filters and sort on company (#104)

* feature: add company filter by name

* feature: add fitler on URL

* feature: set icons for sorts

* feature: add creation date and address sorts

* Add tests

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Sammy Teillet
2023-05-06 07:59:30 +02:00
committed by GitHub
parent 406e1dc02e
commit 354cdb6ad9
17 changed files with 350 additions and 52 deletions

View File

@ -9,8 +9,21 @@ import {
} from '../../services/companies';
import Table from '../../components/table/Table';
import { mapCompany } from '../../interfaces/company.interface';
import { companiesColumns, sortsAvailable } from './companies-table';
import { reduceSortsToOrderBy } from '../../components/table/table-header/helpers';
import {
companiesColumns,
availableFilters,
availableSorts,
} from './companies-table';
import {
reduceFiltersToWhere,
reduceSortsToOrderBy,
} from '../../components/table/table-header/helpers';
import {
Companies_Bool_Exp,
Companies_Order_By,
} from '../../generated/graphql';
import { SelectedFilterType } from '../../components/table/table-header/interface';
import { useSearch } from '../../services/search/search';
const StyledCompaniesContainer = styled.div`
display: flex;
@ -18,15 +31,23 @@ const StyledCompaniesContainer = styled.div`
`;
function Companies() {
const [, setSorts] = useState([] as Array<CompaniesSelectedSortType>);
const [orderBy, setOrderBy] = useState(defaultOrderBy);
const [orderBy, setOrderBy] = useState<Companies_Order_By[]>(defaultOrderBy);
const [where, setWhere] = useState<Companies_Bool_Exp>({});
const [filterSearchResults, setSearhInput, setFilterSearch] = useSearch();
const updateSorts = useCallback((sorts: Array<CompaniesSelectedSortType>) => {
setSorts(sorts);
setOrderBy(sorts.length ? reduceSortsToOrderBy(sorts) : defaultOrderBy);
}, []);
const { data } = useCompaniesQuery(orderBy);
const updateFilters = useCallback(
(filters: Array<SelectedFilterType<Companies_Bool_Exp>>) => {
setWhere(reduceFiltersToWhere(filters));
},
[],
);
const { data } = useCompaniesQuery(orderBy, where);
return (
<WithTopBarContainer title="Companies" icon={<FaRegBuilding />}>
@ -36,8 +57,15 @@ function Companies() {
columns={companiesColumns}
viewName="All Companies"
viewIcon={<FaList />}
availableSorts={availableSorts}
availableFilters={availableFilters}
filterSearchResults={filterSearchResults}
onSortsUpdate={updateSorts}
availableSorts={sortsAvailable}
onFiltersUpdate={updateFilters}
onFilterSearch={(filter, searchValue) => {
setSearhInput(searchValue);
setFilterSearch(filter);
}}
/>
</StyledCompaniesContainer>
</WithTopBarContainer>

View File

@ -3,7 +3,7 @@ import Companies from '../Companies';
import { ThemeProvider } from '@emotion/react';
import { lightTheme } from '../../../layout/styles/themes';
import { GET_COMPANIES } from '../../../services/companies';
import { mockCompanyData } from './mock-data';
import { mockData } from '../__tests__/__data__/mock-data';
import { MockedProvider } from '@apollo/client/testing';
const component = {
@ -19,11 +19,12 @@ const mocks = [
query: GET_COMPANIES,
variables: {
orderBy: [{ name: 'asc' }],
where: {},
},
},
result: {
data: {
companies: mockCompanyData,
companies: mockData,
},
},
},

View File

@ -1,18 +0,0 @@
import { GraphqlQueryCompany } from '../../../interfaces/company.interface';
export const mockCompanyData: Array<GraphqlQueryCompany> = [
{
id: 'f121ab32-fac4-4b8c-9a3d-150c877319c2',
name: 'ACME',
domain_name: 'example.com',
account_owner: {
id: '91510aa5-ede6-451f-8029-a7fa69e4bad6',
email: 'john@example.com',
displayName: 'John Doe',
__typename: 'User',
},
employees: 10,
address: '1 Infinity Loop, 95014 Cupertino, California',
created_at: new Date().toUTCString(),
},
];

View File

@ -0,0 +1,64 @@
import { GraphqlQueryCompany } from '../../../../interfaces/company.interface';
export const mockData: Array<GraphqlQueryCompany> = [
{
id: '89bb825c-171e-4bcc-9cf7-43448d6fb278',
domain_name: 'airbnb.com',
name: 'Airbnb',
created_at: '2023-04-26T10:08:54.724515+00:00',
address: '17 rue de clignancourt',
employees: 12,
account_owner: null,
__typename: 'companies',
},
{
id: 'b396e6b9-dc5c-4643-bcff-61b6cf7523ae',
domain_name: 'aircall.io',
name: 'Aircall',
created_at: '2023-04-26T10:12:42.33625+00:00',
address: null,
employees: 1,
account_owner: null,
__typename: 'companies',
},
{
id: 'a674fa6c-1455-4c57-afaf-dd5dc086361d',
domain_name: 'algolia.com',
name: 'Algolia',
created_at: '2023-04-26T10:10:32.530184+00:00',
address: null,
employees: 1,
account_owner: null,
__typename: 'companies',
},
{
id: 'b1cfd51b-a831-455f-ba07-4e30671e1dc3',
domain_name: 'apple.com',
name: 'Apple',
created_at: '2023-03-21T06:30:25.39474+00:00',
address: null,
employees: 10,
account_owner: null,
__typename: 'companies',
},
{
id: '5c21e19e-e049-4393-8c09-3e3f8fb09ecb',
domain_name: 'bereal.com',
name: 'BeReal',
created_at: '2023-04-26T10:13:29.712485+00:00',
address: '10 rue de la Paix',
employees: 1,
account_owner: null,
__typename: 'companies',
},
{
id: '9d162de6-cfbf-4156-a790-e39854dcd4eb',
domain_name: 'claap.com',
name: 'Claap',
created_at: '2023-04-26T10:09:25.656555+00:00',
address: null,
employees: 1,
account_owner: null,
__typename: 'companies',
},
];

View File

@ -0,0 +1,53 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CompaniesFilter should render the filter company_name 1`] = `
Object {
"name": Object {
"_eq": "Airbnb",
},
}
`;
exports[`CompaniesFilter should render the filter company_name 2`] = `
Object {
"_not": Object {
"name": Object {
"_eq": "Airbnb",
},
},
}
`;
exports[`CompaniesFilter should render the filter domainName 1`] = `
Object {
"domain_name": Object {
"_eq": "airbnb.com",
},
}
`;
exports[`CompaniesFilter should render the filter domainName 2`] = `
Object {
"_not": Object {
"domain_name": Object {
"_eq": "airbnb.com",
},
},
}
`;
exports[`CompaniesFilter should render the serch company_name with the searchValue 1`] = `
Object {
"name": Object {
"_ilike": "%Search value%",
},
}
`;
exports[`CompaniesFilter should render the serch domainName with the searchValue 1`] = `
Object {
"domain_name": Object {
"_ilike": "%Search value%",
},
}
`;

View File

@ -0,0 +1,62 @@
import { FilterType } from '../../../components/table/table-header/interface';
import { Companies_Bool_Exp } from '../../../generated/graphql';
import { GraphqlQueryCompany } from '../../../interfaces/company.interface';
import { GraphqlQueryPerson } from '../../../interfaces/person.interface';
import {
SEARCH_COMPANY_QUERY,
SEARCH_PEOPLE_QUERY,
} from '../../../services/search/search';
import { mockData } from './__data__/mock-data';
import { availableFilters } from '../companies-table';
function assertFilterUseCompanySearch<FilterValue>(
filter: FilterType<Companies_Bool_Exp>,
): filter is FilterType<Companies_Bool_Exp> & {
searchResultMapper: (data: GraphqlQueryCompany) => {
displayValue: string;
value: FilterValue;
};
} {
return filter.searchQuery === SEARCH_COMPANY_QUERY;
}
function assertFilterUsePeopleSearch<FilterValue>(
filter: FilterType<Companies_Bool_Exp>,
): filter is FilterType<Companies_Bool_Exp> & {
searchResultMapper: (data: GraphqlQueryPerson) => {
displayValue: string;
value: FilterValue;
};
} {
return filter.searchQuery === SEARCH_PEOPLE_QUERY;
}
const AirbnbCompany = mockData.find(
(user) => user.name === 'Airbnb',
) as GraphqlQueryCompany;
describe('CompaniesFilter', () => {
for (const filter of availableFilters) {
it(`should render the filter ${filter.key}`, () => {
if (assertFilterUseCompanySearch(filter)) {
const filterSelectedValue = filter.searchResultMapper(mockData[0]);
for (const operand of filter.operands) {
expect(
filter.whereTemplate(operand, filterSelectedValue.value),
).toMatchSnapshot();
}
}
if (assertFilterUsePeopleSearch(filter)) {
const filterSelectedValue = filter.searchResultMapper(AirbnbCompany);
for (const operand of filter.operands) {
expect(
filter.whereTemplate(operand, filterSelectedValue.value),
).toMatchSnapshot();
}
}
});
it(`should render the serch ${filter.key} with the searchValue`, () => {
expect(filter.searchTemplate('Search value')).toMatchSnapshot();
});
}
});

View File

@ -1,5 +1,8 @@
import { createColumnHelper } from '@tanstack/react-table';
import { Company } from '../../interfaces/company.interface';
import {
Company,
GraphqlQueryCompany,
} from '../../interfaces/company.interface';
import { updateCompany } from '../../services/companies';
import ColumnHead from '../../components/table/ColumnHead';
import Checkbox from '../../components/form/Checkbox';
@ -14,28 +17,117 @@ import {
FaStream,
FaRegUser,
FaUsers,
FaBuilding,
} from 'react-icons/fa';
import ClickableCell from '../../components/table/ClickableCell';
import PersonChip from '../../components/chips/PersonChip';
import EditableChip from '../../components/table/editable-cell/EditableChip';
import { SortType } from '../../components/table/table-header/interface';
import { Companies_Order_By } from '../../generated/graphql';
import {
FilterType,
SortType,
} from '../../components/table/table-header/interface';
import {
Companies_Bool_Exp,
Companies_Order_By,
} from '../../generated/graphql';
import { SEARCH_COMPANY_QUERY } from '../../services/search/search';
export const sortsAvailable = [
export const availableSorts = [
{
key: 'name',
label: 'Name',
icon: undefined,
icon: <FaBuilding />,
_type: 'default_sort',
},
{
key: 'employees',
label: 'Employees',
icon: <FaUsers />,
_type: 'default_sort',
},
{
key: 'domain_name',
label: 'Domain',
icon: undefined,
label: 'Url',
icon: <FaLink />,
_type: 'default_sort',
},
{
key: 'address',
label: 'Address',
icon: <FaMapPin />,
_type: 'default_sort',
},
{
key: 'created_at',
label: 'Creation',
icon: <FaCalendar />,
_type: 'default_sort',
},
] satisfies Array<SortType<Companies_Order_By>>;
export const availableFilters = [
{
key: 'company_name',
label: 'Company',
icon: <FaBuilding />,
whereTemplate: (operand, { companyName }) => {
if (operand.keyWord === 'equal') {
return {
name: { _eq: companyName },
};
}
if (operand.keyWord === 'not_equal') {
return {
_not: { name: { _eq: companyName } },
};
}
},
searchQuery: SEARCH_COMPANY_QUERY,
searchTemplate: (searchInput: string) => ({
name: { _ilike: `%${searchInput}%` },
}),
searchResultMapper: (company: GraphqlQueryCompany) => ({
displayValue: company.name,
value: { companyName: company.name },
}),
operands: [
{ label: 'Equal', id: 'equal', keyWord: 'equal' },
{ label: 'Not equal', id: 'not-equal', keyWord: 'not_equal' },
],
},
{
key: 'domainName',
label: 'Url',
icon: <FaLink />,
whereTemplate: (operand, { domainName }) => {
if (operand.keyWord === 'equal') {
return {
domain_name: { _eq: domainName },
};
}
if (operand.keyWord === 'not_equal') {
return {
_not: { domain_name: { _eq: domainName } },
};
}
},
searchQuery: SEARCH_COMPANY_QUERY,
searchTemplate: (searchInput: string) => ({
domain_name: { _ilike: `%${searchInput}%` },
}),
searchResultMapper: (company: GraphqlQueryCompany) => ({
displayValue: company.domain_name,
value: { domainName: company.domain_name },
}),
operands: [
{ label: 'Equal', id: 'equal', keyWord: 'equal' },
{ label: 'Not equal', id: 'not-equal', keyWord: 'not_equal' },
],
},
] satisfies Array<FilterType<Companies_Bool_Exp>>;
const columnHelper = createColumnHelper<Company>();
export const companiesColumns = [
columnHelper.accessor('id', {

View File

@ -3,7 +3,7 @@ import People from '../People';
import { ThemeProvider } from '@emotion/react';
import { lightTheme } from '../../../layout/styles/themes';
import { MockedProvider } from '@apollo/client/testing';
import { defaultData } from '../default-data';
import { mockData } from '../__tests__/__data__/mock-data';
import { GET_PEOPLE } from '../../../services/people';
import { SEARCH_PEOPLE_QUERY } from '../../../services/search/search';
@ -25,7 +25,7 @@ const mocks = [
},
result: {
data: {
people: defaultData,
people: mockData,
},
},
},

View File

@ -1,6 +1,6 @@
import { GraphqlQueryPerson } from '../../interfaces/person.interface';
import { GraphqlQueryPerson } from '../../../../interfaces/person.interface';
export const defaultData: Array<GraphqlQueryPerson> = [
export const mockData: Array<GraphqlQueryPerson> = [
{
id: '7dfbc3f7-6e5e-4128-957e-8d86808cdf6b',
__typename: 'Person',

View File

@ -22,7 +22,7 @@ exports[`PeopleFilter should render the filter company_name 1`] = `
Object {
"company": Object {
"name": Object {
"_eq": "ACME",
"_eq": "Airbnb",
},
},
}
@ -33,7 +33,7 @@ Object {
"_not": Object {
"company": Object {
"name": Object {
"_eq": "ACME",
"_eq": "Airbnb",
},
},
},

View File

@ -6,8 +6,8 @@ import {
SEARCH_COMPANY_QUERY,
SEARCH_PEOPLE_QUERY,
} from '../../../services/search/search';
import { mockCompanyData } from '../../companies/__stories__/mock-data';
import { defaultData } from '../default-data';
import { mockData as mockCompanyData } from '../../companies/__tests__/__data__/mock-data';
import { mockData as mockPeopleData } from './__data__/mock-data';
import { availableFilters } from '../people-table';
function assertFilterUseCompanySearch<FilterValue>(
@ -32,7 +32,7 @@ function assertFilterUsePeopleSearch<FilterValue>(
return filter.searchQuery === SEARCH_PEOPLE_QUERY;
}
const JohnDoeUser = defaultData.find(
const JohnDoeUser = mockPeopleData.find(
(user) => user.email === 'john@linkedin.com',
) as GraphqlQueryPerson;