diff --git a/front/src/components/editable-cell/EditableRelation.tsx b/front/src/components/editable-cell/EditableRelation.tsx index 95448d7269..49c7fa5d44 100644 --- a/front/src/components/editable-cell/EditableRelation.tsx +++ b/front/src/components/editable-cell/EditableRelation.tsx @@ -2,10 +2,8 @@ import { ChangeEvent, ComponentType, useState } from 'react'; import EditableCellWrapper from './EditableCellWrapper'; import styled from '@emotion/styled'; import { useSearch } from '../../services/api/search/search'; -import { - SearchConfigType, - SearchableType, -} from '../../interfaces/search/interface'; +import { SearchConfigType } from '../../interfaces/search/interface'; +import { AnyEntity } from '../../interfaces/entities/generic.interface'; const StyledEditModeContainer = styled.div` width: 200px; @@ -51,7 +49,7 @@ const StyledEditModeResultItem = styled.div` `; export type EditableRelationProps< - RelationType extends SearchableType, + RelationType extends AnyEntity, ChipComponentPropsType, > = { relation?: RelationType | null; @@ -66,7 +64,7 @@ export type EditableRelationProps< }; function EditableRelation< - RelationType extends SearchableType, + RelationType extends AnyEntity, ChipComponentPropsType, >({ relation, diff --git a/front/src/components/table/Table.tsx b/front/src/components/table/Table.tsx index 73e606b085..c2a6ea020c 100644 --- a/front/src/components/table/Table.tsx +++ b/front/src/components/table/Table.tsx @@ -31,7 +31,7 @@ type OwnProps< availableSorts?: Array>; availableFilters?: FilterConfigType[]; onSortsUpdate?: (sorts: Array>) => void; - onFiltersUpdate?: (sorts: Array>) => void; + onFiltersUpdate?: (filters: Array>) => void; onRowSelectionChange?: (rowSelection: string[]) => void; }; @@ -138,7 +138,7 @@ const Table = < viewName={viewName} viewIcon={viewIcon} availableSorts={availableSorts} - availableFilters={availableFilters as FilterConfigType[]} + availableFilters={availableFilters} onSortsUpdate={onSortsUpdate} onFiltersUpdate={onFiltersUpdate} /> diff --git a/front/src/components/table/table-header/FilterDropdownButton.tsx b/front/src/components/table/table-header/FilterDropdownButton.tsx index ba4a2a990b..f276cbd934 100644 --- a/front/src/components/table/table-header/FilterDropdownButton.tsx +++ b/front/src/components/table/table-header/FilterDropdownButton.tsx @@ -10,18 +10,19 @@ import { SearchResultsType, useSearch, } from '../../../services/api/search/search'; -import { SearchableType } from '../../../interfaces/search/interface'; type OwnProps = { isFilterSelected: boolean; availableFilters: FilterConfigType[]; onFilterSelect: (filter: SelectedFilterType) => void; + onFilterRemove: (filterId: SelectedFilterType['key']) => void; }; export const FilterDropdownButton = ({ availableFilters, onFilterSelect, isFilterSelected, + onFilterRemove, }: OwnProps) => { const [isUnfolded, setIsUnfolded] = useState(false); @@ -59,7 +60,7 @@ export const FilterDropdownButton = ({ ); const renderSearchResults = ( - filterSearchResults: SearchResultsType, + filterSearchResults: SearchResultsType, selectedFilter: FilterConfigType, selectedFilterOperand: FilterOperandType, ) => { @@ -98,7 +99,7 @@ export const FilterDropdownButton = ({ onClick={() => { setSelectedFilter(filter); setSelectedFilterOperand(filter.operands[0]); - setFilterSearch(filter.searchConfig); + filter.searchConfig && setFilterSearch(filter.searchConfig); setSearchInput(''); }} > @@ -126,8 +127,23 @@ export const FilterDropdownButton = ({ type="text" placeholder={selectedFilter.label} onChange={(event: ChangeEvent) => { - setFilterSearch(selectedFilter.searchConfig); - setSearchInput(event.target.value); + if (selectedFilter.searchConfig) { + 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, + }); + } + } }} /> diff --git a/front/src/components/table/table-header/TableHeader.tsx b/front/src/components/table/table-header/TableHeader.tsx index d1d2e1bd8f..db2bbbce09 100644 --- a/front/src/components/table/table-header/TableHeader.tsx +++ b/front/src/components/table/table-header/TableHeader.tsx @@ -123,6 +123,7 @@ function TableHeader({ isFilterSelected={filters.length > 0} availableFilters={availableFilters || []} onFilterSelect={filterSelect} + onFilterRemove={filterUnselect} /> isSortSelected={sorts.length > 0} diff --git a/front/src/components/table/table-header/__stories__/FilterDropdownButton.stories.tsx b/front/src/components/table/table-header/__stories__/FilterDropdownButton.stories.tsx index f5a5fdb939..28f8251dc7 100644 --- a/front/src/components/table/table-header/__stories__/FilterDropdownButton.stories.tsx +++ b/front/src/components/table/table-header/__stories__/FilterDropdownButton.stories.tsx @@ -3,15 +3,15 @@ import { lightTheme } from '../../../../layout/styles/themes'; import { FilterDropdownButton } from '../FilterDropdownButton'; import styled from '@emotion/styled'; 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 { mockData } from '../../../../pages/people/__tests__/__data__/mock-data'; import { availableFilters } from '../../../../pages/people/people-filters'; import { Person } from '../../../../interfaces/entities/person.interface'; import { FilterableFieldsType, SelectedFilterType, } from '../../../../interfaces/filters/interface'; +import { mockCompaniesData } from '../../../../pages/companies/__tests__/__data__/mock-data'; const component = { title: 'FilterDropdownButton', @@ -27,52 +27,25 @@ type OwnProps = { const mocks = [ { request: { - query: SEARCH_PEOPLE_QUERY, // TODO this should not be called for empty filters - variables: { - where: undefined, - }, + query: SEARCH_COMPANY_QUERY, + variables: { where: { name: { _ilike: '%%' } }, limit: 5 }, }, result: { data: { - searchResults: mockData, + searchResults: mockCompaniesData, }, }, }, { request: { - query: SEARCH_PEOPLE_QUERY, // TODO this should not be called for empty filters - variables: { - where: { - _or: [ - { firstname: { _ilike: '%%' } }, - { lastname: { _ilike: '%%' } }, - ], - }, - limit: 5, - }, + query: SEARCH_COMPANY_QUERY, + variables: { where: { name: { _ilike: '%Airc%' } }, limit: 5 }, }, result: { data: { - searchResults: mockData, - }, - }, - }, - { - 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')], + searchResults: mockCompaniesData.filter( + (company) => company.name === 'Aircall', + ), }, }, }, @@ -101,6 +74,9 @@ const InnerRegularFilterDropdownButton = ({ availableFilters={availableFilters} isFilterSelected={true} onFilterSelect={outerSetFilters} + onFilterRemove={(filterId) => { + console.log(filterId); + }} /> ); diff --git a/front/src/components/table/table-header/__stories__/SortAndFilterBar.stories.tsx b/front/src/components/table/table-header/__stories__/SortAndFilterBar.stories.tsx index 017597a053..522f517773 100644 --- a/front/src/components/table/table-header/__stories__/SortAndFilterBar.stories.tsx +++ b/front/src/components/table/table-header/__stories__/SortAndFilterBar.stories.tsx @@ -24,8 +24,8 @@ export const RegularSortAndFilterBar = ({ const personFilter = { label: 'People', operand: { - label: 'Include', - id: 'include', + label: 'Is', + id: 'is', whereTemplate: (person: Person) => { return { email: { _eq: person.email } }; }, diff --git a/front/src/components/table/table-header/__tests__/FilterDropdownButton.test.tsx b/front/src/components/table/table-header/__tests__/FilterDropdownButton.test.tsx index 4213cabe42..63c2bec6d9 100644 --- a/front/src/components/table/table-header/__tests__/FilterDropdownButton.test.tsx +++ b/front/src/components/table/table-header/__tests__/FilterDropdownButton.test.tsx @@ -1,7 +1,7 @@ import { fireEvent, render, waitFor } from '@testing-library/react'; 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 { getByText } = render( , @@ -10,27 +10,27 @@ it('Checks the default top option is Include', async () => { const sortDropdownButton = getByText('Filter'); fireEvent.click(sortDropdownButton); - const filterByPeople = getByText('People'); - fireEvent.click(filterByPeople); + const filterByCompany = getByText('Company'); + fireEvent.click(filterByCompany); await waitFor(() => { - const firstSearchResult = getByText('Alexandre Prot'); + const firstSearchResult = getByText('Airbnb'); expect(firstSearchResult).toBeDefined(); }); - const filterByJohn = getByText('Alexandre Prot'); - fireEvent.click(filterByJohn); + const filterByAirbnb = getByText('Airbnb'); + fireEvent.click(filterByAirbnb); expect(setFilters).toHaveBeenCalledWith( expect.objectContaining({ - displayValue: 'Alexandre Prot', - key: 'fullname', - label: 'People', + displayValue: 'Airbnb', + key: 'company_name', + 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 { getByText } = render( , @@ -39,28 +39,28 @@ it('Checks the selection of top option for Not Equal', async () => { const sortDropdownButton = getByText('Filter'); fireEvent.click(sortDropdownButton); - const filterByPeople = getByText('People'); - fireEvent.click(filterByPeople); + const filterByCompany = getByText('Company'); + fireEvent.click(filterByCompany); - const openOperandOptions = getByText('Equal'); + const openOperandOptions = getByText('Is'); fireEvent.click(openOperandOptions); - const selectOperand = getByText('Not equal'); + const selectOperand = getByText('Is not'); fireEvent.click(selectOperand); await waitFor(() => { - const firstSearchResult = getByText('Alexandre Prot'); + const firstSearchResult = getByText('Airbnb'); expect(firstSearchResult).toBeDefined(); }); - const filterByJohn = getByText('Alexandre Prot'); - fireEvent.click(filterByJohn); + const filterByAirbnb = getByText('Airbnb'); + fireEvent.click(filterByAirbnb); expect(setFilters).toHaveBeenCalledWith( expect.objectContaining({ - key: 'fullname', - displayValue: 'Alexandre Prot', - label: 'People', + displayValue: 'Airbnb', + key: 'company_name', + label: 'Company', }), ); const blueSortDropdownButton = getByText('Filter'); @@ -78,13 +78,13 @@ it('Calls the filters when typing a new name', async () => { const sortDropdownButton = getByText('Filter'); fireEvent.click(sortDropdownButton); - const filterByPeople = getByText('People'); - fireEvent.click(filterByPeople); + const filterByCompany = getByText('Company'); + fireEvent.click(filterByCompany); - const filterSearch = getByPlaceholderText('People'); + const filterSearch = getByPlaceholderText('Company'); fireEvent.click(filterSearch); - fireEvent.change(filterSearch, { target: { value: 'Jane' } }); + fireEvent.change(filterSearch, { target: { value: 'Airc' } }); await waitFor(() => { const loadingDiv = getByTestId('loading-search-results'); @@ -92,22 +92,22 @@ it('Calls the filters when typing a new name', async () => { }); await waitFor(() => { - const firstSearchResult = getByText('Jane Doe'); + const firstSearchResult = getByText('Aircall'); expect(firstSearchResult).toBeDefined(); - const alexandreSearchResult = queryByText('Alexandre Prot'); - expect(alexandreSearchResult).not.toBeInTheDocument(); + const airbnbResult = queryByText('Airbnb'); + expect(airbnbResult).not.toBeInTheDocument(); }); - const filterByJane = getByText('Jane Doe'); + const filterByAircall = getByText('Aircall'); - fireEvent.click(filterByJane); + fireEvent.click(filterByAircall); expect(setFilters).toHaveBeenCalledWith( expect.objectContaining({ - key: 'fullname', - displayValue: 'Jane Doe', - label: 'People', + key: 'company_name', + displayValue: 'Aircall', + label: 'Company', }), ); const blueSortDropdownButton = getByText('Filter'); diff --git a/front/src/interfaces/filters/interface.ts b/front/src/interfaces/filters/interface.ts index c7f683f0a3..34b38f1061 100644 --- a/front/src/interfaces/filters/interface.ts +++ b/front/src/interfaces/filters/interface.ts @@ -1,58 +1,67 @@ import { ReactNode } from 'react'; -import { SearchConfigType, SearchableType } from '../search/interface'; -import { Person } from '../entities/person.interface'; -import { Company } from '../entities/company.interface'; -import { User } from '../entities/user.interface'; +import { SearchConfigType } from '../search/interface'; import { AnyEntity, BoolExpType } from '../entities/generic.interface'; -export type FilterableFieldsType = Person | Company; -export type FilterWhereType = Person | Company | User | AnyEntity; +export type FilterableFieldsType = AnyEntity; +export type FilterWhereRelationType = AnyEntity; +type UnknownType = void; +export type FilterWhereType = FilterWhereRelationType | string | UnknownType; export type FilterConfigType< FilteredType extends FilterableFieldsType, - WhereType extends FilterWhereType = any, + WhereType extends FilterWhereType = UnknownType, > = { key: string; label: string; icon: ReactNode; operands: FilterOperandType[]; - searchConfig: WhereType extends SearchableType - ? SearchConfigType - : null; - selectedValueRender: (selected: WhereType) => string; -}; +} & (WhereType extends UnknownType + ? { searchConfig?: SearchConfigType } + : WhereType extends AnyEntity + ? { searchConfig: SearchConfigType } + : 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< FilteredType extends FilterableFieldsType, - WhereType extends FilterWhereType = AnyEntity, -> = - | FilterOperandExactMatchType - | FilterOperandComparativeType; + WhereType extends FilterWhereType = UnknownType, +> = WhereType extends UnknownType + ? any + : WhereType extends FilterWhereRelationType + ? FilterOperandRelationType + : WhereType extends string + ? FilterOperandFieldType + : never; -type FilterOperandExactMatchType< +type FilterOperandRelationType< FilteredType extends FilterableFieldsType, WhereType extends FilterWhereType, > = { - label: 'Equal' | 'Not equal'; - id: 'equal' | 'not-equal'; + label: 'Is' | 'Is not'; + id: 'is' | 'is_not'; whereTemplate: (value: WhereType) => BoolExpType; }; -type FilterOperandComparativeType< - FilteredType extends FilterableFieldsType, - WhereType extends FilterWhereType, -> = { - label: 'Like' | 'Not like' | 'Include'; - id: 'like' | 'not_like' | 'include'; - whereTemplate: (value: WhereType) => BoolExpType; +type FilterOperandFieldType = { + label: 'Contains' | 'Does not contain'; + id: 'like' | 'not_like'; + whereTemplate: (value: string) => BoolExpType; }; export type SelectedFilterType< FilteredType extends FilterableFieldsType, - WhereType extends FilterWhereType = AnyEntity, + WhereType extends FilterWhereType = UnknownType, > = { key: string; - value: WhereType; + value: WhereType extends UnknownType ? any : WhereType; displayValue: string; label: string; icon: ReactNode; diff --git a/front/src/interfaces/search/interface.ts b/front/src/interfaces/search/interface.ts index 4f58e304d9..dbfe823c80 100644 --- a/front/src/interfaces/search/interface.ts +++ b/front/src/interfaces/search/interface.ts @@ -5,20 +5,25 @@ import { People_Bool_Exp, Users_Bool_Exp, } from '../../generated/graphql'; -import { Person } from '../entities/person.interface'; -import { Company } from '../entities/company.interface'; -import { User } from '../entities/user.interface'; -import { GqlType } from '../entities/generic.interface'; +import { AnyEntity, GqlType } from '../entities/generic.interface'; -export type SearchableType = Person | Company | User; +type UnknownType = void; -export type SearchConfigType = { - query: DocumentNode; - template: ( - searchInput: string, - ) => People_Bool_Exp | Companies_Bool_Exp | Users_Bool_Exp; - resultMapper: (data: GqlType) => { - value: SearchType; - render: (value: SearchType) => ReactNode; - }; -}; +export type SearchConfigType< + SearchType extends AnyEntity | UnknownType = AnyEntity, +> = SearchType extends AnyEntity + ? { + query: DocumentNode; + template: ( + searchInput: string, + ) => People_Bool_Exp | Companies_Bool_Exp | Users_Bool_Exp; + resultMapper: (data: GqlType) => { + value: SearchType; + render: (value: SearchType) => ReactNode; + }; + } + : { + query: DocumentNode; + template: (searchInput: string) => any; + resultMapper: (data: any) => any; + }; diff --git a/front/src/pages/companies/__stories__/Companies.stories.tsx b/front/src/pages/companies/__stories__/Companies.stories.tsx index a10eb1bae1..56ceacaaba 100644 --- a/front/src/pages/companies/__stories__/Companies.stories.tsx +++ b/front/src/pages/companies/__stories__/Companies.stories.tsx @@ -3,10 +3,8 @@ import Companies from '../Companies'; import { ThemeProvider } from '@emotion/react'; import { lightTheme } from '../../../layout/styles/themes'; 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 { SEARCH_COMPANY_QUERY } from '../../../services/api/search/search'; -import { mockCompanySearchData } from '../../../services/api/search/__data__/mock-search-data'; const component = { title: 'Companies', @@ -26,7 +24,7 @@ const mocks = [ }, result: { data: { - companies: mockData, + companies: mockCompaniesData, }, }, }, @@ -40,28 +38,24 @@ const mocks = [ }, result: { data: { - companies: mockData, + companies: mockCompaniesData, }, }, }, - { - request: { - query: SEARCH_COMPANY_QUERY, - variables: { where: { name: { _ilike: '%%' } }, limit: 5 }, - }, - result: mockCompanySearchData, - }, { request: { query: GET_COMPANIES, variables: { orderBy: [{ created_at: 'desc' }], - where: { domain_name: { _eq: 'linkedin-searched.com' } }, + where: { domain_name: { _ilike: '%aircal%' } }, }, }, result: { data: { - companies: mockData, + companies: mockCompaniesData.filter( + (company) => + company.domain_name && company.domain_name.includes('aircal'), + ), }, }, }, diff --git a/front/src/pages/companies/__tests__/Companies.test.tsx b/front/src/pages/companies/__tests__/Companies.test.tsx index ee4b38395f..da549ecab6 100644 --- a/front/src/pages/companies/__tests__/Companies.test.tsx +++ b/front/src/pages/companies/__tests__/Companies.test.tsx @@ -138,7 +138,9 @@ it('Checks insert data is appending a new line', async () => { }); it('Checks filters are working', async () => { - const { getByText } = render(); + const { getByText, queryByText, getByPlaceholderText } = render( + , + ); await waitFor(() => { expect(getByText('Airbnb')).toBeDefined(); @@ -154,10 +156,12 @@ it('Checks filters are working', async () => { const urlFilter = getByText('Url'); fireEvent.click(urlFilter); - await waitFor(() => { - expect(getByText('linkedin-searched.com')).toBeDefined(); - }); + const filterSearch = getByPlaceholderText('Url'); + fireEvent.change(filterSearch, { target: { value: 'aircal' } }); - const filterByLinkedinOption = getByText('linkedin-searched.com'); - fireEvent.click(filterByLinkedinOption); + await waitFor(() => { + expect(getByText('aircall.io')).toBeDefined(); + const airbnbResult = queryByText('Airbnb'); + expect(airbnbResult).not.toBeInTheDocument(); + }); }); diff --git a/front/src/pages/companies/__tests__/__data__/mock-data.ts b/front/src/pages/companies/__tests__/__data__/mock-data.ts index 54ddbe9f5f..45c85391e2 100644 --- a/front/src/pages/companies/__tests__/__data__/mock-data.ts +++ b/front/src/pages/companies/__tests__/__data__/mock-data.ts @@ -1,6 +1,6 @@ -import { GraphqlQueryCompany } from '../../../../interfaces/company.interface'; +import { GraphqlQueryCompany } from '../../../../interfaces/entities/company.interface'; -export const mockData: Array = [ +export const mockCompaniesData: Array = [ { id: '89bb825c-171e-4bcc-9cf7-43448d6fb278', domain_name: 'airbnb.com', diff --git a/front/src/pages/companies/companies-filters.tsx b/front/src/pages/companies/companies-filters.tsx index a964b43d5a..e404a3ca18 100644 --- a/front/src/pages/companies/companies-filters.tsx +++ b/front/src/pages/companies/companies-filters.tsx @@ -1,9 +1,5 @@ -import { - Company, - mapToCompany, -} from '../../interfaces/entities/company.interface'; +import { Company } from '../../interfaces/entities/company.interface'; import { FaLink, FaBuilding } from 'react-icons/fa'; -import { SEARCH_COMPANY_QUERY } from '../../services/api/search/search'; import { FilterConfigType } from '../../interfaces/filters/interface'; export const availableFilters = [ @@ -11,64 +7,42 @@ export const availableFilters = [ key: 'company_name', label: 'Company', icon: , - searchConfig: { - query: SEARCH_COMPANY_QUERY, - template: (searchInput) => ({ - name: { _ilike: `%${searchInput}%` }, - }), - resultMapper: (company) => ({ - render: (company) => company.name, - value: mapToCompany(company), - }), - }, - selectedValueRender: (company) => company.name || '', operands: [ { - label: 'Equal', - id: 'equal', - whereTemplate: (company) => ({ - name: { _eq: company.name }, + label: 'Contains', + id: 'like', + whereTemplate: (searchString) => ({ + name: { _ilike: `%${searchString}%` }, }), }, { - label: 'Not equal', - id: 'not-equal', - whereTemplate: (company) => ({ - _not: { name: { _eq: company.name } }, + label: 'Does not contain', + id: 'not_like', + whereTemplate: (searchString) => ({ + _not: { name: { _ilike: `%${searchString}%` } }, }), }, ], - } satisfies FilterConfigType, + } satisfies FilterConfigType, { key: 'company_domain_name', label: 'Url', icon: , - searchConfig: { - query: SEARCH_COMPANY_QUERY, - template: (searchInput) => ({ - name: { _ilike: `%${searchInput}%` }, - }), - resultMapper: (company) => ({ - render: (company) => company.domainName, - value: mapToCompany(company), - }), - }, - selectedValueRender: (company) => company.domainName || '', operands: [ { - label: 'Equal', - id: 'equal', - whereTemplate: (company) => ({ - domain_name: { _eq: company.domainName }, + label: 'Contains', + id: 'like', + whereTemplate: (searchString) => ({ + domain_name: { _ilike: `%${searchString}%` }, }), }, { - label: 'Not equal', - id: 'not-equal', - whereTemplate: (company) => ({ - _not: { domain_name: { _eq: company.domainName } }, + label: 'Does not contain', + id: 'not_like', + whereTemplate: (searchString) => ({ + _not: { domain_name: { _ilike: `%${searchString}%` } }, }), }, ], - } satisfies FilterConfigType, + } satisfies FilterConfigType, ]; diff --git a/front/src/pages/people/__stories__/People.stories.tsx b/front/src/pages/people/__stories__/People.stories.tsx index d5240cad82..c58ca40f29 100644 --- a/front/src/pages/people/__stories__/People.stories.tsx +++ b/front/src/pages/people/__stories__/People.stories.tsx @@ -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 { mockData } from '../__tests__/__data__/mock-data'; +import { mockPeopleData } from '../__tests__/__data__/mock-data'; import { GET_PEOPLE } from '../../../services/api/people'; import { SEARCH_PEOPLE_QUERY } from '../../../services/api/search/search'; @@ -25,7 +25,7 @@ const mocks = [ }, result: { data: { - people: mockData, + people: mockPeopleData, }, }, }, @@ -39,7 +39,7 @@ const mocks = [ }, result: { data: { - people: mockData, + people: mockPeopleData, }, }, }, diff --git a/front/src/pages/people/__tests__/__data__/mock-data.ts b/front/src/pages/people/__tests__/__data__/mock-data.ts index fa9308cfbe..9ee462dbf8 100644 --- a/front/src/pages/people/__tests__/__data__/mock-data.ts +++ b/front/src/pages/people/__tests__/__data__/mock-data.ts @@ -1,6 +1,6 @@ -import { GraphqlQueryPerson } from '../../../../interfaces/person.interface'; +import { GraphqlQueryPerson } from '../../../../interfaces/entities/person.interface'; -export const mockData: Array = [ +export const mockPeopleData: Array = [ { id: '7dfbc3f7-6e5e-4128-957e-8d86808cdf6b', __typename: 'Person', diff --git a/front/src/pages/people/__tests__/__snapshots__/people-filter.test.ts.snap b/front/src/pages/people/__tests__/__snapshots__/people-filter.test.ts.snap index b2577ff5f3..7e6d5eb533 100644 --- a/front/src/pages/people/__tests__/__snapshots__/people-filter.test.ts.snap +++ b/front/src/pages/people/__tests__/__snapshots__/people-filter.test.ts.snap @@ -1,9 +1,19 @@ // 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 { "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", + }, }, } `; diff --git a/front/src/pages/people/__tests__/people-filter.test.ts b/front/src/pages/people/__tests__/people-filter.test.ts index f821045b73..88d16c86df 100644 --- a/front/src/pages/people/__tests__/people-filter.test.ts +++ b/front/src/pages/people/__tests__/people-filter.test.ts @@ -1,20 +1,18 @@ -import { cityFilter } from '../people-filters'; +import { cityFilter, companyFilter } from '../people-filters'; describe('PeopleFilter', () => { - it(`should render the filter ${cityFilter.key}`, () => { + it(`should render the filter ${companyFilter.key} which relation search`, () => { expect( - cityFilter.operands[0].whereTemplate({ + companyFilter.operands[0].whereTemplate({ id: 'test-id', - city: 'Paris', - email: 'john@doe.com', - firstname: 'John', - lastname: 'Doe', - phone: '0123456789', - creationDate: new Date(), - pipes: [], - company: null, - __typename: 'people', + name: 'test-name', + domainName: 'test-domain-name', + __typename: 'companies', }), ).toMatchSnapshot(); }); + + it(`should render the filter ${cityFilter.key} which is text search`, () => { + expect(cityFilter.operands[0].whereTemplate('Paris')).toMatchSnapshot(); + }); }); diff --git a/front/src/pages/people/people-filters.tsx b/front/src/pages/people/people-filters.tsx index 2e9be22acc..8daa149dde 100644 --- a/front/src/pages/people/people-filters.tsx +++ b/front/src/pages/people/people-filters.tsx @@ -1,12 +1,6 @@ import { FaEnvelope, FaMapPin, FaUser, FaBuilding } from 'react-icons/fa'; -import { - Person, - mapToPerson, -} from '../../interfaces/entities/person.interface'; -import { - SEARCH_COMPANY_QUERY, - SEARCH_PEOPLE_QUERY, -} from '../../services/api/search/search'; +import { Person } from '../../interfaces/entities/person.interface'; +import { SEARCH_COMPANY_QUERY } from '../../services/api/search/search'; import { Company, mapToCompany, @@ -17,45 +11,31 @@ export const fullnameFilter = { key: 'fullname', label: 'People', icon: , - 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: [ { - label: 'Equal', - id: 'equal', - whereTemplate: (person) => ({ - _and: [ - { firstname: { _eq: `${person.firstname}` } }, - { lastname: { _eq: `${person.lastname}` } }, + label: 'Contains', + id: 'like', + whereTemplate: (searchString) => ({ + _or: [ + { firstname: { _ilike: `%${searchString}%` } }, + { lastname: { _ilike: `%${searchString}%` } }, ], }), }, { - label: 'Not equal', - id: 'not-equal', - whereTemplate: (person) => ({ + label: 'Does not contain', + id: 'not_like', + whereTemplate: (searchString) => ({ _not: { _and: [ - { firstname: { _eq: `${person.firstname}` } }, - { lastname: { _eq: `${person.lastname}` } }, + { firstname: { _ilike: `%${searchString}%` } }, + { lastname: { _ilike: `%${searchString}%` } }, ], }, }), }, ], -} satisfies FilterConfigType; +} satisfies FilterConfigType; export const companyFilter = { key: 'company_name', @@ -63,8 +43,8 @@ export const companyFilter = { icon: , searchConfig: { query: SEARCH_COMPANY_QUERY, - template: (searchInput: string) => ({ - name: { _ilike: `%${searchInput}%` }, + template: (searchString: string) => ({ + name: { _ilike: `%${searchString}%` }, }), resultMapper: (data) => ({ value: mapToCompany(data), @@ -74,15 +54,15 @@ export const companyFilter = { selectedValueRender: (company) => company.name || '', operands: [ { - label: 'Equal', - id: 'equal', + label: 'Is', + id: 'is', whereTemplate: (company) => ({ company: { name: { _eq: company.name } }, }), }, { - label: 'Not equal', - id: 'not-equal', + label: 'Is not', + id: 'is_not', whereTemplate: (company) => ({ _not: { company: { name: { _eq: company.name } } }, }), @@ -94,71 +74,49 @@ export const emailFilter = { key: 'email', label: 'Email', icon: , - searchConfig: { - query: SEARCH_PEOPLE_QUERY, - template: (searchInput: string) => ({ - email: { _ilike: `%${searchInput}%` }, - }), - resultMapper: (person) => ({ - render: (person) => person.email, - value: mapToPerson(person), - }), - }, operands: [ { - label: 'Equal', - id: 'equal', - whereTemplate: (person) => ({ - email: { _eq: person.email }, + label: 'Contains', + id: 'like', + whereTemplate: (searchString) => ({ + email: { _ilike: `%${searchString}%` }, }), }, { - label: 'Not equal', - id: 'not-equal', - whereTemplate: (person) => ({ - _not: { email: { _eq: person.email } }, + label: 'Does not contain', + id: 'not_like', + whereTemplate: (searchString) => ({ + _not: { email: { _ilike: `%${searchString}%` } }, }), }, ], - selectedValueRender: (person) => person.email || '', -} satisfies FilterConfigType; +} satisfies FilterConfigType; export const cityFilter = { key: 'city', label: 'City', icon: , - searchConfig: { - query: SEARCH_PEOPLE_QUERY, - template: (searchInput: string) => ({ - city: { _ilike: `%${searchInput}%` }, - }), - resultMapper: (person) => ({ - render: (person) => person.city, - value: mapToPerson(person), - }), - }, operands: [ { - label: 'Equal', - id: 'equal', - whereTemplate: (person) => ({ - city: { _eq: person.city }, + label: 'Contains', + id: 'like', + whereTemplate: (searchString) => ({ + city: { _ilike: `%${searchString}%` }, }), }, { - label: 'Not equal', - id: 'not-equal', - whereTemplate: (person) => ({ - _not: { city: { _eq: person.city } }, + label: 'Does not contain', + id: 'not_like', + whereTemplate: (searchString) => ({ + _not: { city: { _ilike: `%${searchString}%` } }, }), }, ], - selectedValueRender: (person) => person.email || '', -} satisfies FilterConfigType; +} satisfies FilterConfigType; export const availableFilters = [ fullnameFilter, companyFilter, emailFilter, cityFilter, -] satisfies FilterConfigType[]; +] satisfies FilterConfigType[]; diff --git a/front/src/services/api/search/__data__/mock-search-data.ts b/front/src/services/api/search/__data__/mock-search-data.ts deleted file mode 100644 index 383de323e4..0000000000 --- a/front/src/services/api/search/__data__/mock-search-data.ts +++ /dev/null @@ -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', - }, - ], - }, -}; diff --git a/front/src/services/api/search/search.ts b/front/src/services/api/search/search.ts index 9aa216a4ea..43b4b188b9 100644 --- a/front/src/services/api/search/search.ts +++ b/front/src/services/api/search/search.ts @@ -1,9 +1,7 @@ import { gql, useQuery } from '@apollo/client'; import { useMemo, useState } from 'react'; -import { - SearchConfigType, - SearchableType, -} from '../../../interfaces/search/interface'; +import { SearchConfigType } from '../../../interfaces/search/interface'; +import { AnyEntity } from '../../../interfaces/entities/generic.interface'; export const SEARCH_PEOPLE_QUERY = gql` query SearchQuery($where: people_bool_exp, $limit: Int) { @@ -58,7 +56,7 @@ const debounce = ( }; }; -export type SearchResultsType = { +export type SearchResultsType = { results: { render: (value: T) => string; value: T; @@ -66,7 +64,7 @@ export type SearchResultsType = { loading: boolean; }; -export const useSearch = (): [ +export const useSearch = (): [ SearchResultsType, React.Dispatch>, React.Dispatch | null>>,