Refactor Filter type to accept Is, Is Not, Contains, Does not Contain (#128)
* Refactor Filter type to accept Is, Is Not, Contains, Does not Contain * Remove any and add tests
This commit is contained in:
@ -2,10 +2,8 @@ import { ChangeEvent, ComponentType, useState } from 'react';
|
|||||||
import EditableCellWrapper from './EditableCellWrapper';
|
import EditableCellWrapper from './EditableCellWrapper';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { useSearch } from '../../services/api/search/search';
|
import { useSearch } from '../../services/api/search/search';
|
||||||
import {
|
import { SearchConfigType } from '../../interfaces/search/interface';
|
||||||
SearchConfigType,
|
import { AnyEntity } from '../../interfaces/entities/generic.interface';
|
||||||
SearchableType,
|
|
||||||
} from '../../interfaces/search/interface';
|
|
||||||
|
|
||||||
const StyledEditModeContainer = styled.div`
|
const StyledEditModeContainer = styled.div`
|
||||||
width: 200px;
|
width: 200px;
|
||||||
@ -51,7 +49,7 @@ const StyledEditModeResultItem = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export type EditableRelationProps<
|
export type EditableRelationProps<
|
||||||
RelationType extends SearchableType,
|
RelationType extends AnyEntity,
|
||||||
ChipComponentPropsType,
|
ChipComponentPropsType,
|
||||||
> = {
|
> = {
|
||||||
relation?: RelationType | null;
|
relation?: RelationType | null;
|
||||||
@ -66,7 +64,7 @@ export type EditableRelationProps<
|
|||||||
};
|
};
|
||||||
|
|
||||||
function EditableRelation<
|
function EditableRelation<
|
||||||
RelationType extends SearchableType,
|
RelationType extends AnyEntity,
|
||||||
ChipComponentPropsType,
|
ChipComponentPropsType,
|
||||||
>({
|
>({
|
||||||
relation,
|
relation,
|
||||||
|
|||||||
@ -31,7 +31,7 @@ type OwnProps<
|
|||||||
availableSorts?: Array<SortType<SortField>>;
|
availableSorts?: Array<SortType<SortField>>;
|
||||||
availableFilters?: FilterConfigType<TData>[];
|
availableFilters?: FilterConfigType<TData>[];
|
||||||
onSortsUpdate?: (sorts: Array<SelectedSortType<SortField>>) => void;
|
onSortsUpdate?: (sorts: Array<SelectedSortType<SortField>>) => void;
|
||||||
onFiltersUpdate?: (sorts: Array<SelectedFilterType<TData>>) => void;
|
onFiltersUpdate?: (filters: Array<SelectedFilterType<TData>>) => void;
|
||||||
onRowSelectionChange?: (rowSelection: string[]) => void;
|
onRowSelectionChange?: (rowSelection: string[]) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -138,7 +138,7 @@ const Table = <
|
|||||||
viewName={viewName}
|
viewName={viewName}
|
||||||
viewIcon={viewIcon}
|
viewIcon={viewIcon}
|
||||||
availableSorts={availableSorts}
|
availableSorts={availableSorts}
|
||||||
availableFilters={availableFilters as FilterConfigType<any>[]}
|
availableFilters={availableFilters}
|
||||||
onSortsUpdate={onSortsUpdate}
|
onSortsUpdate={onSortsUpdate}
|
||||||
onFiltersUpdate={onFiltersUpdate}
|
onFiltersUpdate={onFiltersUpdate}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -10,18 +10,19 @@ import {
|
|||||||
SearchResultsType,
|
SearchResultsType,
|
||||||
useSearch,
|
useSearch,
|
||||||
} from '../../../services/api/search/search';
|
} from '../../../services/api/search/search';
|
||||||
import { SearchableType } from '../../../interfaces/search/interface';
|
|
||||||
|
|
||||||
type OwnProps<TData extends FilterableFieldsType> = {
|
type OwnProps<TData extends FilterableFieldsType> = {
|
||||||
isFilterSelected: boolean;
|
isFilterSelected: boolean;
|
||||||
availableFilters: FilterConfigType<TData>[];
|
availableFilters: FilterConfigType<TData>[];
|
||||||
onFilterSelect: (filter: SelectedFilterType<TData>) => void;
|
onFilterSelect: (filter: SelectedFilterType<TData>) => void;
|
||||||
|
onFilterRemove: (filterId: SelectedFilterType<TData>['key']) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const FilterDropdownButton = <TData extends FilterableFieldsType>({
|
export const FilterDropdownButton = <TData extends FilterableFieldsType>({
|
||||||
availableFilters,
|
availableFilters,
|
||||||
onFilterSelect,
|
onFilterSelect,
|
||||||
isFilterSelected,
|
isFilterSelected,
|
||||||
|
onFilterRemove,
|
||||||
}: OwnProps<TData>) => {
|
}: OwnProps<TData>) => {
|
||||||
const [isUnfolded, setIsUnfolded] = useState(false);
|
const [isUnfolded, setIsUnfolded] = useState(false);
|
||||||
|
|
||||||
@ -59,7 +60,7 @@ export const FilterDropdownButton = <TData extends FilterableFieldsType>({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const renderSearchResults = (
|
const renderSearchResults = (
|
||||||
filterSearchResults: SearchResultsType<SearchableType>,
|
filterSearchResults: SearchResultsType,
|
||||||
selectedFilter: FilterConfigType<TData>,
|
selectedFilter: FilterConfigType<TData>,
|
||||||
selectedFilterOperand: FilterOperandType<TData>,
|
selectedFilterOperand: FilterOperandType<TData>,
|
||||||
) => {
|
) => {
|
||||||
@ -98,7 +99,7 @@ export const FilterDropdownButton = <TData extends FilterableFieldsType>({
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSelectedFilter(filter);
|
setSelectedFilter(filter);
|
||||||
setSelectedFilterOperand(filter.operands[0]);
|
setSelectedFilterOperand(filter.operands[0]);
|
||||||
setFilterSearch(filter.searchConfig);
|
filter.searchConfig && setFilterSearch(filter.searchConfig);
|
||||||
setSearchInput('');
|
setSearchInput('');
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -126,8 +127,23 @@ export const FilterDropdownButton = <TData extends FilterableFieldsType>({
|
|||||||
type="text"
|
type="text"
|
||||||
placeholder={selectedFilter.label}
|
placeholder={selectedFilter.label}
|
||||||
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
||||||
setFilterSearch(selectedFilter.searchConfig);
|
if (selectedFilter.searchConfig) {
|
||||||
setSearchInput(event.target.value);
|
setFilterSearch(selectedFilter.searchConfig);
|
||||||
|
setSearchInput(event.target.value);
|
||||||
|
} else {
|
||||||
|
if (event.target.value === '') {
|
||||||
|
onFilterRemove(selectedFilter.key);
|
||||||
|
} else {
|
||||||
|
onFilterSelect({
|
||||||
|
key: selectedFilter.key,
|
||||||
|
label: selectedFilter.label,
|
||||||
|
value: event.target.value,
|
||||||
|
displayValue: event.target.value,
|
||||||
|
icon: selectedFilter.icon,
|
||||||
|
operand: selectedFilterOperand,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</DropdownButton.StyledSearchField>
|
</DropdownButton.StyledSearchField>
|
||||||
|
|||||||
@ -123,6 +123,7 @@ function TableHeader<SortField, TData extends FilterableFieldsType>({
|
|||||||
isFilterSelected={filters.length > 0}
|
isFilterSelected={filters.length > 0}
|
||||||
availableFilters={availableFilters || []}
|
availableFilters={availableFilters || []}
|
||||||
onFilterSelect={filterSelect}
|
onFilterSelect={filterSelect}
|
||||||
|
onFilterRemove={filterUnselect}
|
||||||
/>
|
/>
|
||||||
<SortDropdownButton<SortField>
|
<SortDropdownButton<SortField>
|
||||||
isSortSelected={sorts.length > 0}
|
isSortSelected={sorts.length > 0}
|
||||||
|
|||||||
@ -3,15 +3,15 @@ import { lightTheme } from '../../../../layout/styles/themes';
|
|||||||
import { FilterDropdownButton } from '../FilterDropdownButton';
|
import { FilterDropdownButton } from '../FilterDropdownButton';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
import { SEARCH_PEOPLE_QUERY } from '../../../../services/api/search/search';
|
import { SEARCH_COMPANY_QUERY } from '../../../../services/api/search/search';
|
||||||
import { MockedProvider } from '@apollo/client/testing';
|
import { MockedProvider } from '@apollo/client/testing';
|
||||||
import { mockData } from '../../../../pages/people/__tests__/__data__/mock-data';
|
|
||||||
import { availableFilters } from '../../../../pages/people/people-filters';
|
import { availableFilters } from '../../../../pages/people/people-filters';
|
||||||
import { Person } from '../../../../interfaces/entities/person.interface';
|
import { Person } from '../../../../interfaces/entities/person.interface';
|
||||||
import {
|
import {
|
||||||
FilterableFieldsType,
|
FilterableFieldsType,
|
||||||
SelectedFilterType,
|
SelectedFilterType,
|
||||||
} from '../../../../interfaces/filters/interface';
|
} from '../../../../interfaces/filters/interface';
|
||||||
|
import { mockCompaniesData } from '../../../../pages/companies/__tests__/__data__/mock-data';
|
||||||
|
|
||||||
const component = {
|
const component = {
|
||||||
title: 'FilterDropdownButton',
|
title: 'FilterDropdownButton',
|
||||||
@ -27,52 +27,25 @@ type OwnProps<FilterProperties extends FilterableFieldsType> = {
|
|||||||
const mocks = [
|
const mocks = [
|
||||||
{
|
{
|
||||||
request: {
|
request: {
|
||||||
query: SEARCH_PEOPLE_QUERY, // TODO this should not be called for empty filters
|
query: SEARCH_COMPANY_QUERY,
|
||||||
variables: {
|
variables: { where: { name: { _ilike: '%%' } }, limit: 5 },
|
||||||
where: undefined,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
result: {
|
result: {
|
||||||
data: {
|
data: {
|
||||||
searchResults: mockData,
|
searchResults: mockCompaniesData,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
request: {
|
request: {
|
||||||
query: SEARCH_PEOPLE_QUERY, // TODO this should not be called for empty filters
|
query: SEARCH_COMPANY_QUERY,
|
||||||
variables: {
|
variables: { where: { name: { _ilike: '%Airc%' } }, limit: 5 },
|
||||||
where: {
|
|
||||||
_or: [
|
|
||||||
{ firstname: { _ilike: '%%' } },
|
|
||||||
{ lastname: { _ilike: '%%' } },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
limit: 5,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
result: {
|
result: {
|
||||||
data: {
|
data: {
|
||||||
searchResults: mockData,
|
searchResults: mockCompaniesData.filter(
|
||||||
},
|
(company) => company.name === 'Aircall',
|
||||||
},
|
),
|
||||||
},
|
|
||||||
{
|
|
||||||
request: {
|
|
||||||
query: SEARCH_PEOPLE_QUERY, // TODO this should not be called for empty filters
|
|
||||||
variables: {
|
|
||||||
where: {
|
|
||||||
_or: [
|
|
||||||
{ firstname: { _ilike: '%Jane%' } },
|
|
||||||
{ lastname: { _ilike: '%Jane%' } },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
limit: 5,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
result: {
|
|
||||||
data: {
|
|
||||||
searchResults: [mockData.find((p) => p.firstname === 'Jane')],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -101,6 +74,9 @@ const InnerRegularFilterDropdownButton = ({
|
|||||||
availableFilters={availableFilters}
|
availableFilters={availableFilters}
|
||||||
isFilterSelected={true}
|
isFilterSelected={true}
|
||||||
onFilterSelect={outerSetFilters}
|
onFilterSelect={outerSetFilters}
|
||||||
|
onFilterRemove={(filterId) => {
|
||||||
|
console.log(filterId);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</StyleDiv>
|
</StyleDiv>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -24,8 +24,8 @@ export const RegularSortAndFilterBar = ({
|
|||||||
const personFilter = {
|
const personFilter = {
|
||||||
label: 'People',
|
label: 'People',
|
||||||
operand: {
|
operand: {
|
||||||
label: 'Include',
|
label: 'Is',
|
||||||
id: 'include',
|
id: 'is',
|
||||||
whereTemplate: (person: Person) => {
|
whereTemplate: (person: Person) => {
|
||||||
return { email: { _eq: person.email } };
|
return { email: { _eq: person.email } };
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { fireEvent, render, waitFor } from '@testing-library/react';
|
import { fireEvent, render, waitFor } from '@testing-library/react';
|
||||||
import { RegularFilterDropdownButton } from '../__stories__/FilterDropdownButton.stories';
|
import { RegularFilterDropdownButton } from '../__stories__/FilterDropdownButton.stories';
|
||||||
|
|
||||||
it('Checks the default top option is Include', async () => {
|
it('Checks the default top option is Is', async () => {
|
||||||
const setFilters = jest.fn();
|
const setFilters = jest.fn();
|
||||||
const { getByText } = render(
|
const { getByText } = render(
|
||||||
<RegularFilterDropdownButton setFilter={setFilters} />,
|
<RegularFilterDropdownButton setFilter={setFilters} />,
|
||||||
@ -10,27 +10,27 @@ it('Checks the default top option is Include', async () => {
|
|||||||
const sortDropdownButton = getByText('Filter');
|
const sortDropdownButton = getByText('Filter');
|
||||||
fireEvent.click(sortDropdownButton);
|
fireEvent.click(sortDropdownButton);
|
||||||
|
|
||||||
const filterByPeople = getByText('People');
|
const filterByCompany = getByText('Company');
|
||||||
fireEvent.click(filterByPeople);
|
fireEvent.click(filterByCompany);
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
const firstSearchResult = getByText('Alexandre Prot');
|
const firstSearchResult = getByText('Airbnb');
|
||||||
expect(firstSearchResult).toBeDefined();
|
expect(firstSearchResult).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
const filterByJohn = getByText('Alexandre Prot');
|
const filterByAirbnb = getByText('Airbnb');
|
||||||
fireEvent.click(filterByJohn);
|
fireEvent.click(filterByAirbnb);
|
||||||
|
|
||||||
expect(setFilters).toHaveBeenCalledWith(
|
expect(setFilters).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
displayValue: 'Alexandre Prot',
|
displayValue: 'Airbnb',
|
||||||
key: 'fullname',
|
key: 'company_name',
|
||||||
label: 'People',
|
label: 'Company',
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Checks the selection of top option for Not Equal', async () => {
|
it('Checks the selection of top option for Is Not', async () => {
|
||||||
const setFilters = jest.fn();
|
const setFilters = jest.fn();
|
||||||
const { getByText } = render(
|
const { getByText } = render(
|
||||||
<RegularFilterDropdownButton setFilter={setFilters} />,
|
<RegularFilterDropdownButton setFilter={setFilters} />,
|
||||||
@ -39,28 +39,28 @@ it('Checks the selection of top option for Not Equal', async () => {
|
|||||||
const sortDropdownButton = getByText('Filter');
|
const sortDropdownButton = getByText('Filter');
|
||||||
fireEvent.click(sortDropdownButton);
|
fireEvent.click(sortDropdownButton);
|
||||||
|
|
||||||
const filterByPeople = getByText('People');
|
const filterByCompany = getByText('Company');
|
||||||
fireEvent.click(filterByPeople);
|
fireEvent.click(filterByCompany);
|
||||||
|
|
||||||
const openOperandOptions = getByText('Equal');
|
const openOperandOptions = getByText('Is');
|
||||||
fireEvent.click(openOperandOptions);
|
fireEvent.click(openOperandOptions);
|
||||||
|
|
||||||
const selectOperand = getByText('Not equal');
|
const selectOperand = getByText('Is not');
|
||||||
fireEvent.click(selectOperand);
|
fireEvent.click(selectOperand);
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
const firstSearchResult = getByText('Alexandre Prot');
|
const firstSearchResult = getByText('Airbnb');
|
||||||
expect(firstSearchResult).toBeDefined();
|
expect(firstSearchResult).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
const filterByJohn = getByText('Alexandre Prot');
|
const filterByAirbnb = getByText('Airbnb');
|
||||||
fireEvent.click(filterByJohn);
|
fireEvent.click(filterByAirbnb);
|
||||||
|
|
||||||
expect(setFilters).toHaveBeenCalledWith(
|
expect(setFilters).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
key: 'fullname',
|
displayValue: 'Airbnb',
|
||||||
displayValue: 'Alexandre Prot',
|
key: 'company_name',
|
||||||
label: 'People',
|
label: 'Company',
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
const blueSortDropdownButton = getByText('Filter');
|
const blueSortDropdownButton = getByText('Filter');
|
||||||
@ -78,13 +78,13 @@ it('Calls the filters when typing a new name', async () => {
|
|||||||
const sortDropdownButton = getByText('Filter');
|
const sortDropdownButton = getByText('Filter');
|
||||||
fireEvent.click(sortDropdownButton);
|
fireEvent.click(sortDropdownButton);
|
||||||
|
|
||||||
const filterByPeople = getByText('People');
|
const filterByCompany = getByText('Company');
|
||||||
fireEvent.click(filterByPeople);
|
fireEvent.click(filterByCompany);
|
||||||
|
|
||||||
const filterSearch = getByPlaceholderText('People');
|
const filterSearch = getByPlaceholderText('Company');
|
||||||
fireEvent.click(filterSearch);
|
fireEvent.click(filterSearch);
|
||||||
|
|
||||||
fireEvent.change(filterSearch, { target: { value: 'Jane' } });
|
fireEvent.change(filterSearch, { target: { value: 'Airc' } });
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
const loadingDiv = getByTestId('loading-search-results');
|
const loadingDiv = getByTestId('loading-search-results');
|
||||||
@ -92,22 +92,22 @@ it('Calls the filters when typing a new name', async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
const firstSearchResult = getByText('Jane Doe');
|
const firstSearchResult = getByText('Aircall');
|
||||||
expect(firstSearchResult).toBeDefined();
|
expect(firstSearchResult).toBeDefined();
|
||||||
|
|
||||||
const alexandreSearchResult = queryByText('Alexandre Prot');
|
const airbnbResult = queryByText('Airbnb');
|
||||||
expect(alexandreSearchResult).not.toBeInTheDocument();
|
expect(airbnbResult).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
const filterByJane = getByText('Jane Doe');
|
const filterByAircall = getByText('Aircall');
|
||||||
|
|
||||||
fireEvent.click(filterByJane);
|
fireEvent.click(filterByAircall);
|
||||||
|
|
||||||
expect(setFilters).toHaveBeenCalledWith(
|
expect(setFilters).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
key: 'fullname',
|
key: 'company_name',
|
||||||
displayValue: 'Jane Doe',
|
displayValue: 'Aircall',
|
||||||
label: 'People',
|
label: 'Company',
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
const blueSortDropdownButton = getByText('Filter');
|
const blueSortDropdownButton = getByText('Filter');
|
||||||
|
|||||||
@ -1,58 +1,67 @@
|
|||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
import { SearchConfigType, SearchableType } from '../search/interface';
|
import { SearchConfigType } from '../search/interface';
|
||||||
import { Person } from '../entities/person.interface';
|
|
||||||
import { Company } from '../entities/company.interface';
|
|
||||||
import { User } from '../entities/user.interface';
|
|
||||||
import { AnyEntity, BoolExpType } from '../entities/generic.interface';
|
import { AnyEntity, BoolExpType } from '../entities/generic.interface';
|
||||||
|
|
||||||
export type FilterableFieldsType = Person | Company;
|
export type FilterableFieldsType = AnyEntity;
|
||||||
export type FilterWhereType = Person | Company | User | AnyEntity;
|
export type FilterWhereRelationType = AnyEntity;
|
||||||
|
type UnknownType = void;
|
||||||
|
export type FilterWhereType = FilterWhereRelationType | string | UnknownType;
|
||||||
|
|
||||||
export type FilterConfigType<
|
export type FilterConfigType<
|
||||||
FilteredType extends FilterableFieldsType,
|
FilteredType extends FilterableFieldsType,
|
||||||
WhereType extends FilterWhereType = any,
|
WhereType extends FilterWhereType = UnknownType,
|
||||||
> = {
|
> = {
|
||||||
key: string;
|
key: string;
|
||||||
label: string;
|
label: string;
|
||||||
icon: ReactNode;
|
icon: ReactNode;
|
||||||
operands: FilterOperandType<FilteredType, WhereType>[];
|
operands: FilterOperandType<FilteredType, WhereType>[];
|
||||||
searchConfig: WhereType extends SearchableType
|
} & (WhereType extends UnknownType
|
||||||
? SearchConfigType<WhereType>
|
? { searchConfig?: SearchConfigType<UnknownType> }
|
||||||
: null;
|
: WhereType extends AnyEntity
|
||||||
selectedValueRender: (selected: WhereType) => string;
|
? { searchConfig: SearchConfigType<WhereType> }
|
||||||
};
|
: WhereType extends string
|
||||||
|
? object
|
||||||
|
: never) &
|
||||||
|
(WhereType extends UnknownType
|
||||||
|
? { selectedValueRender?: (selected: any) => string }
|
||||||
|
: WhereType extends AnyEntity
|
||||||
|
? { selectedValueRender: (selected: WhereType) => string }
|
||||||
|
: WhereType extends string
|
||||||
|
? object
|
||||||
|
: never);
|
||||||
|
|
||||||
export type FilterOperandType<
|
export type FilterOperandType<
|
||||||
FilteredType extends FilterableFieldsType,
|
FilteredType extends FilterableFieldsType,
|
||||||
WhereType extends FilterWhereType = AnyEntity,
|
WhereType extends FilterWhereType = UnknownType,
|
||||||
> =
|
> = WhereType extends UnknownType
|
||||||
| FilterOperandExactMatchType<FilteredType, WhereType>
|
? any
|
||||||
| FilterOperandComparativeType<FilteredType, WhereType>;
|
: WhereType extends FilterWhereRelationType
|
||||||
|
? FilterOperandRelationType<FilteredType, WhereType>
|
||||||
|
: WhereType extends string
|
||||||
|
? FilterOperandFieldType<FilteredType>
|
||||||
|
: never;
|
||||||
|
|
||||||
type FilterOperandExactMatchType<
|
type FilterOperandRelationType<
|
||||||
FilteredType extends FilterableFieldsType,
|
FilteredType extends FilterableFieldsType,
|
||||||
WhereType extends FilterWhereType,
|
WhereType extends FilterWhereType,
|
||||||
> = {
|
> = {
|
||||||
label: 'Equal' | 'Not equal';
|
label: 'Is' | 'Is not';
|
||||||
id: 'equal' | 'not-equal';
|
id: 'is' | 'is_not';
|
||||||
whereTemplate: (value: WhereType) => BoolExpType<FilteredType>;
|
whereTemplate: (value: WhereType) => BoolExpType<FilteredType>;
|
||||||
};
|
};
|
||||||
|
|
||||||
type FilterOperandComparativeType<
|
type FilterOperandFieldType<FilteredType extends FilterableFieldsType> = {
|
||||||
FilteredType extends FilterableFieldsType,
|
label: 'Contains' | 'Does not contain';
|
||||||
WhereType extends FilterWhereType,
|
id: 'like' | 'not_like';
|
||||||
> = {
|
whereTemplate: (value: string) => BoolExpType<FilteredType>;
|
||||||
label: 'Like' | 'Not like' | 'Include';
|
|
||||||
id: 'like' | 'not_like' | 'include';
|
|
||||||
whereTemplate: (value: WhereType) => BoolExpType<FilteredType>;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SelectedFilterType<
|
export type SelectedFilterType<
|
||||||
FilteredType extends FilterableFieldsType,
|
FilteredType extends FilterableFieldsType,
|
||||||
WhereType extends FilterWhereType = AnyEntity,
|
WhereType extends FilterWhereType = UnknownType,
|
||||||
> = {
|
> = {
|
||||||
key: string;
|
key: string;
|
||||||
value: WhereType;
|
value: WhereType extends UnknownType ? any : WhereType;
|
||||||
displayValue: string;
|
displayValue: string;
|
||||||
label: string;
|
label: string;
|
||||||
icon: ReactNode;
|
icon: ReactNode;
|
||||||
|
|||||||
@ -5,20 +5,25 @@ import {
|
|||||||
People_Bool_Exp,
|
People_Bool_Exp,
|
||||||
Users_Bool_Exp,
|
Users_Bool_Exp,
|
||||||
} from '../../generated/graphql';
|
} from '../../generated/graphql';
|
||||||
import { Person } from '../entities/person.interface';
|
import { AnyEntity, GqlType } from '../entities/generic.interface';
|
||||||
import { Company } from '../entities/company.interface';
|
|
||||||
import { User } from '../entities/user.interface';
|
|
||||||
import { GqlType } from '../entities/generic.interface';
|
|
||||||
|
|
||||||
export type SearchableType = Person | Company | User;
|
type UnknownType = void;
|
||||||
|
|
||||||
export type SearchConfigType<SearchType extends SearchableType> = {
|
export type SearchConfigType<
|
||||||
query: DocumentNode;
|
SearchType extends AnyEntity | UnknownType = AnyEntity,
|
||||||
template: (
|
> = SearchType extends AnyEntity
|
||||||
searchInput: string,
|
? {
|
||||||
) => People_Bool_Exp | Companies_Bool_Exp | Users_Bool_Exp;
|
query: DocumentNode;
|
||||||
resultMapper: (data: GqlType<SearchType>) => {
|
template: (
|
||||||
value: SearchType;
|
searchInput: string,
|
||||||
render: (value: SearchType) => ReactNode;
|
) => People_Bool_Exp | Companies_Bool_Exp | Users_Bool_Exp;
|
||||||
};
|
resultMapper: (data: GqlType<SearchType>) => {
|
||||||
};
|
value: SearchType;
|
||||||
|
render: (value: SearchType) => ReactNode;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
query: DocumentNode;
|
||||||
|
template: (searchInput: string) => any;
|
||||||
|
resultMapper: (data: any) => any;
|
||||||
|
};
|
||||||
|
|||||||
@ -3,10 +3,8 @@ import Companies from '../Companies';
|
|||||||
import { ThemeProvider } from '@emotion/react';
|
import { ThemeProvider } from '@emotion/react';
|
||||||
import { lightTheme } from '../../../layout/styles/themes';
|
import { lightTheme } from '../../../layout/styles/themes';
|
||||||
import { GET_COMPANIES } from '../../../services/api/companies';
|
import { GET_COMPANIES } from '../../../services/api/companies';
|
||||||
import { mockData } from '../__tests__/__data__/mock-data';
|
import { mockCompaniesData } from '../__tests__/__data__/mock-data';
|
||||||
import { MockedProvider } from '@apollo/client/testing';
|
import { MockedProvider } from '@apollo/client/testing';
|
||||||
import { SEARCH_COMPANY_QUERY } from '../../../services/api/search/search';
|
|
||||||
import { mockCompanySearchData } from '../../../services/api/search/__data__/mock-search-data';
|
|
||||||
|
|
||||||
const component = {
|
const component = {
|
||||||
title: 'Companies',
|
title: 'Companies',
|
||||||
@ -26,7 +24,7 @@ const mocks = [
|
|||||||
},
|
},
|
||||||
result: {
|
result: {
|
||||||
data: {
|
data: {
|
||||||
companies: mockData,
|
companies: mockCompaniesData,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -40,28 +38,24 @@ const mocks = [
|
|||||||
},
|
},
|
||||||
result: {
|
result: {
|
||||||
data: {
|
data: {
|
||||||
companies: mockData,
|
companies: mockCompaniesData,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
request: {
|
|
||||||
query: SEARCH_COMPANY_QUERY,
|
|
||||||
variables: { where: { name: { _ilike: '%%' } }, limit: 5 },
|
|
||||||
},
|
|
||||||
result: mockCompanySearchData,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
request: {
|
request: {
|
||||||
query: GET_COMPANIES,
|
query: GET_COMPANIES,
|
||||||
variables: {
|
variables: {
|
||||||
orderBy: [{ created_at: 'desc' }],
|
orderBy: [{ created_at: 'desc' }],
|
||||||
where: { domain_name: { _eq: 'linkedin-searched.com' } },
|
where: { domain_name: { _ilike: '%aircal%' } },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
result: {
|
result: {
|
||||||
data: {
|
data: {
|
||||||
companies: mockData,
|
companies: mockCompaniesData.filter(
|
||||||
|
(company) =>
|
||||||
|
company.domain_name && company.domain_name.includes('aircal'),
|
||||||
|
),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -138,7 +138,9 @@ it('Checks insert data is appending a new line', async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('Checks filters are working', async () => {
|
it('Checks filters are working', async () => {
|
||||||
const { getByText } = render(<CompaniesDefault />);
|
const { getByText, queryByText, getByPlaceholderText } = render(
|
||||||
|
<CompaniesDefault />,
|
||||||
|
);
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(getByText('Airbnb')).toBeDefined();
|
expect(getByText('Airbnb')).toBeDefined();
|
||||||
@ -154,10 +156,12 @@ it('Checks filters are working', async () => {
|
|||||||
const urlFilter = getByText('Url');
|
const urlFilter = getByText('Url');
|
||||||
fireEvent.click(urlFilter);
|
fireEvent.click(urlFilter);
|
||||||
|
|
||||||
await waitFor(() => {
|
const filterSearch = getByPlaceholderText('Url');
|
||||||
expect(getByText('linkedin-searched.com')).toBeDefined();
|
fireEvent.change(filterSearch, { target: { value: 'aircal' } });
|
||||||
});
|
|
||||||
|
|
||||||
const filterByLinkedinOption = getByText('linkedin-searched.com');
|
await waitFor(() => {
|
||||||
fireEvent.click(filterByLinkedinOption);
|
expect(getByText('aircall.io')).toBeDefined();
|
||||||
|
const airbnbResult = queryByText('Airbnb');
|
||||||
|
expect(airbnbResult).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { GraphqlQueryCompany } from '../../../../interfaces/company.interface';
|
import { GraphqlQueryCompany } from '../../../../interfaces/entities/company.interface';
|
||||||
|
|
||||||
export const mockData: Array<GraphqlQueryCompany> = [
|
export const mockCompaniesData: Array<GraphqlQueryCompany> = [
|
||||||
{
|
{
|
||||||
id: '89bb825c-171e-4bcc-9cf7-43448d6fb278',
|
id: '89bb825c-171e-4bcc-9cf7-43448d6fb278',
|
||||||
domain_name: 'airbnb.com',
|
domain_name: 'airbnb.com',
|
||||||
|
|||||||
@ -1,9 +1,5 @@
|
|||||||
import {
|
import { Company } from '../../interfaces/entities/company.interface';
|
||||||
Company,
|
|
||||||
mapToCompany,
|
|
||||||
} from '../../interfaces/entities/company.interface';
|
|
||||||
import { FaLink, FaBuilding } from 'react-icons/fa';
|
import { FaLink, FaBuilding } from 'react-icons/fa';
|
||||||
import { SEARCH_COMPANY_QUERY } from '../../services/api/search/search';
|
|
||||||
import { FilterConfigType } from '../../interfaces/filters/interface';
|
import { FilterConfigType } from '../../interfaces/filters/interface';
|
||||||
|
|
||||||
export const availableFilters = [
|
export const availableFilters = [
|
||||||
@ -11,64 +7,42 @@ export const availableFilters = [
|
|||||||
key: 'company_name',
|
key: 'company_name',
|
||||||
label: 'Company',
|
label: 'Company',
|
||||||
icon: <FaBuilding />,
|
icon: <FaBuilding />,
|
||||||
searchConfig: {
|
|
||||||
query: SEARCH_COMPANY_QUERY,
|
|
||||||
template: (searchInput) => ({
|
|
||||||
name: { _ilike: `%${searchInput}%` },
|
|
||||||
}),
|
|
||||||
resultMapper: (company) => ({
|
|
||||||
render: (company) => company.name,
|
|
||||||
value: mapToCompany(company),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
selectedValueRender: (company) => company.name || '',
|
|
||||||
operands: [
|
operands: [
|
||||||
{
|
{
|
||||||
label: 'Equal',
|
label: 'Contains',
|
||||||
id: 'equal',
|
id: 'like',
|
||||||
whereTemplate: (company) => ({
|
whereTemplate: (searchString) => ({
|
||||||
name: { _eq: company.name },
|
name: { _ilike: `%${searchString}%` },
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Not equal',
|
label: 'Does not contain',
|
||||||
id: 'not-equal',
|
id: 'not_like',
|
||||||
whereTemplate: (company) => ({
|
whereTemplate: (searchString) => ({
|
||||||
_not: { name: { _eq: company.name } },
|
_not: { name: { _ilike: `%${searchString}%` } },
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
} satisfies FilterConfigType<Company, Company>,
|
} satisfies FilterConfigType<Company, string>,
|
||||||
{
|
{
|
||||||
key: 'company_domain_name',
|
key: 'company_domain_name',
|
||||||
label: 'Url',
|
label: 'Url',
|
||||||
icon: <FaLink />,
|
icon: <FaLink />,
|
||||||
searchConfig: {
|
|
||||||
query: SEARCH_COMPANY_QUERY,
|
|
||||||
template: (searchInput) => ({
|
|
||||||
name: { _ilike: `%${searchInput}%` },
|
|
||||||
}),
|
|
||||||
resultMapper: (company) => ({
|
|
||||||
render: (company) => company.domainName,
|
|
||||||
value: mapToCompany(company),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
selectedValueRender: (company) => company.domainName || '',
|
|
||||||
operands: [
|
operands: [
|
||||||
{
|
{
|
||||||
label: 'Equal',
|
label: 'Contains',
|
||||||
id: 'equal',
|
id: 'like',
|
||||||
whereTemplate: (company) => ({
|
whereTemplate: (searchString) => ({
|
||||||
domain_name: { _eq: company.domainName },
|
domain_name: { _ilike: `%${searchString}%` },
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Not equal',
|
label: 'Does not contain',
|
||||||
id: 'not-equal',
|
id: 'not_like',
|
||||||
whereTemplate: (company) => ({
|
whereTemplate: (searchString) => ({
|
||||||
_not: { domain_name: { _eq: company.domainName } },
|
_not: { domain_name: { _ilike: `%${searchString}%` } },
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
} satisfies FilterConfigType<Company, Company>,
|
} satisfies FilterConfigType<Company, string>,
|
||||||
];
|
];
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import People from '../People';
|
|||||||
import { ThemeProvider } from '@emotion/react';
|
import { ThemeProvider } from '@emotion/react';
|
||||||
import { lightTheme } from '../../../layout/styles/themes';
|
import { lightTheme } from '../../../layout/styles/themes';
|
||||||
import { MockedProvider } from '@apollo/client/testing';
|
import { MockedProvider } from '@apollo/client/testing';
|
||||||
import { mockData } from '../__tests__/__data__/mock-data';
|
import { mockPeopleData } from '../__tests__/__data__/mock-data';
|
||||||
import { GET_PEOPLE } from '../../../services/api/people';
|
import { GET_PEOPLE } from '../../../services/api/people';
|
||||||
import { SEARCH_PEOPLE_QUERY } from '../../../services/api/search/search';
|
import { SEARCH_PEOPLE_QUERY } from '../../../services/api/search/search';
|
||||||
|
|
||||||
@ -25,7 +25,7 @@ const mocks = [
|
|||||||
},
|
},
|
||||||
result: {
|
result: {
|
||||||
data: {
|
data: {
|
||||||
people: mockData,
|
people: mockPeopleData,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -39,7 +39,7 @@ const mocks = [
|
|||||||
},
|
},
|
||||||
result: {
|
result: {
|
||||||
data: {
|
data: {
|
||||||
people: mockData,
|
people: mockPeopleData,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { GraphqlQueryPerson } from '../../../../interfaces/person.interface';
|
import { GraphqlQueryPerson } from '../../../../interfaces/entities/person.interface';
|
||||||
|
|
||||||
export const mockData: Array<GraphqlQueryPerson> = [
|
export const mockPeopleData: Array<GraphqlQueryPerson> = [
|
||||||
{
|
{
|
||||||
id: '7dfbc3f7-6e5e-4128-957e-8d86808cdf6b',
|
id: '7dfbc3f7-6e5e-4128-957e-8d86808cdf6b',
|
||||||
__typename: 'Person',
|
__typename: 'Person',
|
||||||
|
|||||||
@ -1,9 +1,19 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`PeopleFilter should render the filter city 1`] = `
|
exports[`PeopleFilter should render the filter city which is text search 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"city": Object {
|
"city": Object {
|
||||||
"_eq": "Paris",
|
"_ilike": "%Paris%",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`PeopleFilter should render the filter company_name which relation search 1`] = `
|
||||||
|
Object {
|
||||||
|
"company": Object {
|
||||||
|
"name": Object {
|
||||||
|
"_eq": "test-name",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@ -1,20 +1,18 @@
|
|||||||
import { cityFilter } from '../people-filters';
|
import { cityFilter, companyFilter } from '../people-filters';
|
||||||
|
|
||||||
describe('PeopleFilter', () => {
|
describe('PeopleFilter', () => {
|
||||||
it(`should render the filter ${cityFilter.key}`, () => {
|
it(`should render the filter ${companyFilter.key} which relation search`, () => {
|
||||||
expect(
|
expect(
|
||||||
cityFilter.operands[0].whereTemplate({
|
companyFilter.operands[0].whereTemplate({
|
||||||
id: 'test-id',
|
id: 'test-id',
|
||||||
city: 'Paris',
|
name: 'test-name',
|
||||||
email: 'john@doe.com',
|
domainName: 'test-domain-name',
|
||||||
firstname: 'John',
|
__typename: 'companies',
|
||||||
lastname: 'Doe',
|
|
||||||
phone: '0123456789',
|
|
||||||
creationDate: new Date(),
|
|
||||||
pipes: [],
|
|
||||||
company: null,
|
|
||||||
__typename: 'people',
|
|
||||||
}),
|
}),
|
||||||
).toMatchSnapshot();
|
).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it(`should render the filter ${cityFilter.key} which is text search`, () => {
|
||||||
|
expect(cityFilter.operands[0].whereTemplate('Paris')).toMatchSnapshot();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,12 +1,6 @@
|
|||||||
import { FaEnvelope, FaMapPin, FaUser, FaBuilding } from 'react-icons/fa';
|
import { FaEnvelope, FaMapPin, FaUser, FaBuilding } from 'react-icons/fa';
|
||||||
import {
|
import { Person } from '../../interfaces/entities/person.interface';
|
||||||
Person,
|
import { SEARCH_COMPANY_QUERY } from '../../services/api/search/search';
|
||||||
mapToPerson,
|
|
||||||
} from '../../interfaces/entities/person.interface';
|
|
||||||
import {
|
|
||||||
SEARCH_COMPANY_QUERY,
|
|
||||||
SEARCH_PEOPLE_QUERY,
|
|
||||||
} from '../../services/api/search/search';
|
|
||||||
import {
|
import {
|
||||||
Company,
|
Company,
|
||||||
mapToCompany,
|
mapToCompany,
|
||||||
@ -17,45 +11,31 @@ export const fullnameFilter = {
|
|||||||
key: 'fullname',
|
key: 'fullname',
|
||||||
label: 'People',
|
label: 'People',
|
||||||
icon: <FaUser />,
|
icon: <FaUser />,
|
||||||
searchConfig: {
|
|
||||||
query: SEARCH_PEOPLE_QUERY,
|
|
||||||
template: (searchInput: string) => ({
|
|
||||||
_or: [
|
|
||||||
{ firstname: { _ilike: `%${searchInput}%` } },
|
|
||||||
{ lastname: { _ilike: `%${searchInput}%` } },
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
resultMapper: (person) => ({
|
|
||||||
render: (person) => `${person.firstname} ${person.lastname}`,
|
|
||||||
value: mapToPerson(person),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
selectedValueRender: (person) => `${person.firstname} ${person.lastname}`,
|
|
||||||
operands: [
|
operands: [
|
||||||
{
|
{
|
||||||
label: 'Equal',
|
label: 'Contains',
|
||||||
id: 'equal',
|
id: 'like',
|
||||||
whereTemplate: (person) => ({
|
whereTemplate: (searchString) => ({
|
||||||
_and: [
|
_or: [
|
||||||
{ firstname: { _eq: `${person.firstname}` } },
|
{ firstname: { _ilike: `%${searchString}%` } },
|
||||||
{ lastname: { _eq: `${person.lastname}` } },
|
{ lastname: { _ilike: `%${searchString}%` } },
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Not equal',
|
label: 'Does not contain',
|
||||||
id: 'not-equal',
|
id: 'not_like',
|
||||||
whereTemplate: (person) => ({
|
whereTemplate: (searchString) => ({
|
||||||
_not: {
|
_not: {
|
||||||
_and: [
|
_and: [
|
||||||
{ firstname: { _eq: `${person.firstname}` } },
|
{ firstname: { _ilike: `%${searchString}%` } },
|
||||||
{ lastname: { _eq: `${person.lastname}` } },
|
{ lastname: { _ilike: `%${searchString}%` } },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
} satisfies FilterConfigType<Person, Person>;
|
} satisfies FilterConfigType<Person, string>;
|
||||||
|
|
||||||
export const companyFilter = {
|
export const companyFilter = {
|
||||||
key: 'company_name',
|
key: 'company_name',
|
||||||
@ -63,8 +43,8 @@ export const companyFilter = {
|
|||||||
icon: <FaBuilding />,
|
icon: <FaBuilding />,
|
||||||
searchConfig: {
|
searchConfig: {
|
||||||
query: SEARCH_COMPANY_QUERY,
|
query: SEARCH_COMPANY_QUERY,
|
||||||
template: (searchInput: string) => ({
|
template: (searchString: string) => ({
|
||||||
name: { _ilike: `%${searchInput}%` },
|
name: { _ilike: `%${searchString}%` },
|
||||||
}),
|
}),
|
||||||
resultMapper: (data) => ({
|
resultMapper: (data) => ({
|
||||||
value: mapToCompany(data),
|
value: mapToCompany(data),
|
||||||
@ -74,15 +54,15 @@ export const companyFilter = {
|
|||||||
selectedValueRender: (company) => company.name || '',
|
selectedValueRender: (company) => company.name || '',
|
||||||
operands: [
|
operands: [
|
||||||
{
|
{
|
||||||
label: 'Equal',
|
label: 'Is',
|
||||||
id: 'equal',
|
id: 'is',
|
||||||
whereTemplate: (company) => ({
|
whereTemplate: (company) => ({
|
||||||
company: { name: { _eq: company.name } },
|
company: { name: { _eq: company.name } },
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Not equal',
|
label: 'Is not',
|
||||||
id: 'not-equal',
|
id: 'is_not',
|
||||||
whereTemplate: (company) => ({
|
whereTemplate: (company) => ({
|
||||||
_not: { company: { name: { _eq: company.name } } },
|
_not: { company: { name: { _eq: company.name } } },
|
||||||
}),
|
}),
|
||||||
@ -94,71 +74,49 @@ export const emailFilter = {
|
|||||||
key: 'email',
|
key: 'email',
|
||||||
label: 'Email',
|
label: 'Email',
|
||||||
icon: <FaEnvelope />,
|
icon: <FaEnvelope />,
|
||||||
searchConfig: {
|
|
||||||
query: SEARCH_PEOPLE_QUERY,
|
|
||||||
template: (searchInput: string) => ({
|
|
||||||
email: { _ilike: `%${searchInput}%` },
|
|
||||||
}),
|
|
||||||
resultMapper: (person) => ({
|
|
||||||
render: (person) => person.email,
|
|
||||||
value: mapToPerson(person),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
operands: [
|
operands: [
|
||||||
{
|
{
|
||||||
label: 'Equal',
|
label: 'Contains',
|
||||||
id: 'equal',
|
id: 'like',
|
||||||
whereTemplate: (person) => ({
|
whereTemplate: (searchString) => ({
|
||||||
email: { _eq: person.email },
|
email: { _ilike: `%${searchString}%` },
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Not equal',
|
label: 'Does not contain',
|
||||||
id: 'not-equal',
|
id: 'not_like',
|
||||||
whereTemplate: (person) => ({
|
whereTemplate: (searchString) => ({
|
||||||
_not: { email: { _eq: person.email } },
|
_not: { email: { _ilike: `%${searchString}%` } },
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
selectedValueRender: (person) => person.email || '',
|
} satisfies FilterConfigType<Person, string>;
|
||||||
} satisfies FilterConfigType<Person, Person>;
|
|
||||||
|
|
||||||
export const cityFilter = {
|
export const cityFilter = {
|
||||||
key: 'city',
|
key: 'city',
|
||||||
label: 'City',
|
label: 'City',
|
||||||
icon: <FaMapPin />,
|
icon: <FaMapPin />,
|
||||||
searchConfig: {
|
|
||||||
query: SEARCH_PEOPLE_QUERY,
|
|
||||||
template: (searchInput: string) => ({
|
|
||||||
city: { _ilike: `%${searchInput}%` },
|
|
||||||
}),
|
|
||||||
resultMapper: (person) => ({
|
|
||||||
render: (person) => person.city,
|
|
||||||
value: mapToPerson(person),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
operands: [
|
operands: [
|
||||||
{
|
{
|
||||||
label: 'Equal',
|
label: 'Contains',
|
||||||
id: 'equal',
|
id: 'like',
|
||||||
whereTemplate: (person) => ({
|
whereTemplate: (searchString) => ({
|
||||||
city: { _eq: person.city },
|
city: { _ilike: `%${searchString}%` },
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Not equal',
|
label: 'Does not contain',
|
||||||
id: 'not-equal',
|
id: 'not_like',
|
||||||
whereTemplate: (person) => ({
|
whereTemplate: (searchString) => ({
|
||||||
_not: { city: { _eq: person.city } },
|
_not: { city: { _ilike: `%${searchString}%` } },
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
selectedValueRender: (person) => person.email || '',
|
} satisfies FilterConfigType<Person, string>;
|
||||||
} satisfies FilterConfigType<Person, Person>;
|
|
||||||
|
|
||||||
export const availableFilters = [
|
export const availableFilters = [
|
||||||
fullnameFilter,
|
fullnameFilter,
|
||||||
companyFilter,
|
companyFilter,
|
||||||
emailFilter,
|
emailFilter,
|
||||||
cityFilter,
|
cityFilter,
|
||||||
] satisfies FilterConfigType<Person, any>[];
|
] satisfies FilterConfigType<Person>[];
|
||||||
|
|||||||
@ -1,36 +0,0 @@
|
|||||||
export const mockCompanySearchData = {
|
|
||||||
data: {
|
|
||||||
searchResults: [
|
|
||||||
{
|
|
||||||
id: 'fe256b39-3ec3-4fe3-8997-b76aa0bfa408',
|
|
||||||
name: 'Linkedin',
|
|
||||||
domain_name: 'linkedin-searched.com',
|
|
||||||
__typename: 'companies',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '118995f3-5d81-46d6-bf83-f7fd33ea6102',
|
|
||||||
name: 'Facebook',
|
|
||||||
domain_name: 'facebook-searched.com',
|
|
||||||
__typename: 'companies',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '04b2e9f5-0713-40a5-8216-82802401d33e',
|
|
||||||
name: 'Qonto',
|
|
||||||
domain_name: 'qonto-searched.com',
|
|
||||||
__typename: 'companies',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '460b6fb1-ed89-413a-b31a-962986e67bb4',
|
|
||||||
name: 'Microsoft',
|
|
||||||
domain_name: 'microsoft-searched.com',
|
|
||||||
__typename: 'companies',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '0d940997-c21e-4ec2-873b-de4264d89025',
|
|
||||||
name: 'Google',
|
|
||||||
domain_name: 'google-searched.com',
|
|
||||||
__typename: 'companies',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@ -1,9 +1,7 @@
|
|||||||
import { gql, useQuery } from '@apollo/client';
|
import { gql, useQuery } from '@apollo/client';
|
||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import {
|
import { SearchConfigType } from '../../../interfaces/search/interface';
|
||||||
SearchConfigType,
|
import { AnyEntity } from '../../../interfaces/entities/generic.interface';
|
||||||
SearchableType,
|
|
||||||
} from '../../../interfaces/search/interface';
|
|
||||||
|
|
||||||
export const SEARCH_PEOPLE_QUERY = gql`
|
export const SEARCH_PEOPLE_QUERY = gql`
|
||||||
query SearchQuery($where: people_bool_exp, $limit: Int) {
|
query SearchQuery($where: people_bool_exp, $limit: Int) {
|
||||||
@ -58,7 +56,7 @@ const debounce = <FuncArgs extends any[]>(
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SearchResultsType<T extends SearchableType> = {
|
export type SearchResultsType<T extends AnyEntity = AnyEntity> = {
|
||||||
results: {
|
results: {
|
||||||
render: (value: T) => string;
|
render: (value: T) => string;
|
||||||
value: T;
|
value: T;
|
||||||
@ -66,7 +64,7 @@ export type SearchResultsType<T extends SearchableType> = {
|
|||||||
loading: boolean;
|
loading: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useSearch = <T extends SearchableType>(): [
|
export const useSearch = <T extends AnyEntity = AnyEntity>(): [
|
||||||
SearchResultsType<T>,
|
SearchResultsType<T>,
|
||||||
React.Dispatch<React.SetStateAction<string>>,
|
React.Dispatch<React.SetStateAction<string>>,
|
||||||
React.Dispatch<React.SetStateAction<SearchConfigType<T> | null>>,
|
React.Dispatch<React.SetStateAction<SearchConfigType<T> | null>>,
|
||||||
|
|||||||
Reference in New Issue
Block a user