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:
+114
-43
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user