Add Filters on Table views (#95)

* Add filter search logic

WIP Filter search

Implement filters

test: fix sorts tests

test: fix filter test

feature: search person and display firstname in results

feature: fix test for filter component

test: mock search filters

refactor: create a useSearch hook

refactor: move debounce in useSearch and reset status of filter selection

feature: debounce set filters

refactor: remove useless setSorts

feature: add where variable to people query

feature: strongly type Filters

feature: update WhereTemplate method

feature: implement filtering on full name

feature: type the useSearch hook

feature: use where reducer

refactor: create a type for readability

feature: use query and mapper from filters

feature: implement filter by company

feature: search filter results on filter select

feature: add loading and results to search results in filters

refactor: move render search results in a function

feature: display a LOADING when it loads

feature: split search input and search filter for different debounce

refactor: remove some warnings

refactor: remove some warnings

* Write test 1

* Write test 2

* test: useSearch is tested

* test: update names of default people data

* test: add a filter search

* Test 3

* Fix tests

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Sammy Teillet
2023-05-04 13:54:46 +02:00
committed by GitHub
parent 27d5edc031
commit 6a8a8f0728
33 changed files with 913 additions and 316 deletions
@@ -3,15 +3,15 @@ import { lightTheme } from '../../../../layout/styles/themes';
import { FilterDropdownButton } from '../FilterDropdownButton';
import styled from '@emotion/styled';
import { FilterType, SelectedFilterType } from '../interface';
import {
FaRegUser,
FaRegBuilding,
FaEnvelope,
FaPhone,
FaCalendar,
FaMapPin,
} from 'react-icons/fa';
import { useCallback, useState } from 'react';
import { People_Bool_Exp } from '../../../../generated/graphql';
import { FaUsers } from 'react-icons/fa';
import {
SEARCH_PEOPLE_QUERY,
useSearch,
} from '../../../../services/search/search';
import { MockedProvider } from '@apollo/client/testing';
import { defaultData } from '../../../../pages/people/default-data';
const component = {
title: 'FilterDropdownButton',
@@ -20,58 +20,129 @@ const component = {
export default component;
type OwnProps = {
setFilters: (filters: SelectedFilterType[]) => void;
type OwnProps<FilterProperties> = {
setFilter: (filters: SelectedFilterType<FilterProperties>) => void;
};
const mocks = [
{
request: {
query: SEARCH_PEOPLE_QUERY, // TODO this should not be called for empty filters
variables: {
where: undefined,
},
},
result: {
data: {
searchResults: defaultData,
},
},
},
{
request: {
query: SEARCH_PEOPLE_QUERY, // TODO this should not be called for empty filters
variables: {
where: {
_or: [
{ firstname: { _ilike: '%%' } },
{ lastname: { _ilike: '%%' } },
],
},
},
},
result: {
data: {
searchResults: defaultData,
},
},
},
{
request: {
query: SEARCH_PEOPLE_QUERY, // TODO this should not be called for empty filters
variables: {
where: {
_or: [
{ firstname: { _ilike: '%Jane%' } },
{ lastname: { _ilike: '%Jane%' } },
],
},
},
},
result: {
data: {
searchResults: [defaultData.find((p) => p.firstname === 'Jane')],
},
},
},
];
const availableFilters = [
{
key: 'fullname',
label: 'People',
icon: <FaRegUser />,
icon: <FaUsers />,
searchQuery: SEARCH_PEOPLE_QUERY,
searchTemplate: (searchInput: string) => ({
_or: [
{ firstname: { _ilike: `%${searchInput}%` } },
{ lastname: { _ilike: `%${searchInput}%` } },
],
}),
whereTemplate: () => ({
_or: [
{ firstname: { _ilike: 'value' } },
{ lastname: { _ilike: 'value' } },
],
}),
searchResultMapper: (data) => ({
displayValue: data.firstname + ' ' + data.lastname,
value: data.firstname,
}),
},
{
key: 'company_name',
label: 'Company',
icon: <FaRegBuilding />,
},
{
key: 'email',
label: 'Email',
icon: <FaEnvelope />,
},
{ key: 'phone', label: 'Phone', icon: <FaPhone /> },
{
key: 'created_at',
label: 'Created at',
icon: <FaCalendar />,
},
{ key: 'city', label: 'City', icon: <FaMapPin /> },
] satisfies FilterType[];
] satisfies FilterType<People_Bool_Exp>[];
const StyleDiv = styled.div`
height: 200px;
width: 200px;
`;
export const RegularFilterDropdownButton = ({ setFilters }: OwnProps) => {
const [filters, innerSetFilters] = useState<SelectedFilterType[]>([]);
const InnerRegularFilterDropdownButton = ({
setFilter: setFilters,
}: OwnProps<People_Bool_Exp>) => {
const [, innerSetFilters] = useState<SelectedFilterType<People_Bool_Exp>>();
const [filterSearchResults, setSearhInput, setFilterSearch] = useSearch();
const outerSetFilters = useCallback(
(filters: SelectedFilterType[]) => {
innerSetFilters(filters);
setFilters(filters);
(filter: SelectedFilterType<People_Bool_Exp>) => {
innerSetFilters(filter);
setFilters(filter);
},
[setFilters],
);
return (
<ThemeProvider theme={lightTheme}>
<StyleDiv>
<FilterDropdownButton
availableFilters={availableFilters}
filters={filters}
setFilters={outerSetFilters}
/>
</StyleDiv>
</ThemeProvider>
<StyleDiv>
<FilterDropdownButton
availableFilters={availableFilters}
isFilterSelected={true}
onFilterSelect={outerSetFilters}
filterSearchResults={filterSearchResults}
onFilterSearch={(filter, searchValue) => {
setSearhInput(searchValue);
setFilterSearch(filter);
}}
/>
</StyleDiv>
);
};
export const RegularFilterDropdownButton = ({
setFilter: setFilters,
}: OwnProps<People_Bool_Exp>) => {
return (
<MockedProvider mocks={mocks}>
<ThemeProvider theme={lightTheme}>
<InnerRegularFilterDropdownButton setFilter={setFilters} />
</ThemeProvider>
</MockedProvider>
);
};
@@ -1,6 +1,7 @@
import SortAndFilterBar from '../SortAndFilterBar';
import { ThemeProvider } from '@emotion/react';
import { lightTheme } from '../../../../layout/styles/themes';
import { GET_PEOPLE } from '../../../../services/people';
import { FaArrowDown } from 'react-icons/fa';
const component = {
@@ -37,10 +38,24 @@ export const RegularSortAndFilterBar = ({ removeFunction }: OwnProps) => {
filters={[
{
label: 'People',
operand: { id: 'include', label: 'Include' },
id: 'test_filter',
operand: { label: 'Include', id: 'include', keyWord: 'ilike' },
key: 'test_filter',
icon: <FaArrowDown />,
value: 'John Doe',
where: {
firstname: { _ilike: 'John Doe' },
},
searchQuery: GET_PEOPLE,
searchTemplate: () => ({
firstname: { _ilike: 'John Doe' },
}),
whereTemplate: () => {
return { firstname: { _ilike: 'John Doe' } };
},
searchResultMapper: (data) => ({
displayValue: 'John Doe',
value: data.firstname,
}),
},
]}
/>
@@ -1,4 +1,4 @@
import { SelectedSortType, SortType } from '../interface';
import { SortType } from '../interface';
import { ThemeProvider } from '@emotion/react';
import { lightTheme } from '../../../../layout/styles/themes';
import {
@@ -23,8 +23,6 @@ type OwnProps = {
setSorts: () => void;
};
const sorts = [] satisfies SelectedSortType[];
const availableSorts = [
{
key: 'fullname',
@@ -60,9 +58,9 @@ export const RegularSortDropdownButton = ({ setSorts }: OwnProps) => {
<ThemeProvider theme={lightTheme}>
<StyleDiv>
<SortDropdownButton
sorts={sorts}
isSortSelected={true}
availableSorts={availableSorts}
setSorts={setSorts}
onSortSelect={setSorts}
/>
</StyleDiv>
</ThemeProvider>