From ceaf482f6214d4876e97347dccd7268f2acbdce7 Mon Sep 17 00:00:00 2001 From: Lucas Bordeau Date: Fri, 23 Jun 2023 12:39:16 +0200 Subject: [PATCH] First working version of new dropdown UI (#360) * First working version of new dropdown UI * Removed consolelog * Fixed test storybook * Cleaned optional args --- .../modules/search/interfaces/interface.ts | 2 +- front/src/modules/search/services/search.ts | 15 +++- .../menu/DropdownMenuSelectableItem.tsx | 1 + .../table-header/FilterDropdownButton.tsx | 76 +++++++++++++------ .../Companies.filterBy.stories.tsx | 14 +++- .../src/pages/companies/companies-filters.tsx | 17 +++-- .../__stories__/People.filterBy.stories.tsx | 12 ++- front/src/pages/people/people-filters.tsx | 14 +++- 8 files changed, 111 insertions(+), 40 deletions(-) diff --git a/front/src/modules/search/interfaces/interface.ts b/front/src/modules/search/interfaces/interface.ts index 2423932c68..2eabeb751a 100644 --- a/front/src/modules/search/interfaces/interface.ts +++ b/front/src/modules/search/interfaces/interface.ts @@ -2,6 +2,6 @@ import { DocumentNode } from 'graphql'; export type SearchConfigType = { query: DocumentNode; - template: (searchInput: string) => any; + template: (searchInput: string, currentSelectedId?: string) => any; resultMapper: (data: any) => any; }; diff --git a/front/src/modules/search/services/search.ts b/front/src/modules/search/services/search.ts index e5d713901e..39bf4a270e 100644 --- a/front/src/modules/search/services/search.ts +++ b/front/src/modules/search/services/search.ts @@ -79,7 +79,13 @@ export type SearchResultsType = { loading: boolean; }; -export const useSearch = (): [ +type SearchArgs = { + currentSelectedId?: string | null; +}; + +export const useSearch = ( + searchArgs?: SearchArgs, +): [ SearchResultsType, React.Dispatch>, React.Dispatch>, @@ -99,9 +105,12 @@ export const useSearch = (): [ return ( searchConfig && searchConfig.template && - searchConfig.template(searchInput) + searchConfig.template( + searchInput, + searchArgs?.currentSelectedId ?? undefined, + ) ); - }, [searchConfig, searchInput]); + }, [searchConfig, searchInput, searchArgs]); const searchQueryResults = useQuery(searchConfig?.query || EMPTY_QUERY, { variables: { diff --git a/front/src/modules/ui/components/menu/DropdownMenuSelectableItem.tsx b/front/src/modules/ui/components/menu/DropdownMenuSelectableItem.tsx index 7da6fa30f5..b94637323d 100644 --- a/front/src/modules/ui/components/menu/DropdownMenuSelectableItem.tsx +++ b/front/src/modules/ui/components/menu/DropdownMenuSelectableItem.tsx @@ -57,6 +57,7 @@ export function DropdownMenuSelectableItem({ onClick={onClick} selected={selected} hovered={hovered} + data-testid="dropdown-menu-item" > {children} diff --git a/front/src/modules/ui/components/table/table-header/FilterDropdownButton.tsx b/front/src/modules/ui/components/table/table-header/FilterDropdownButton.tsx index 8b544bb090..d410ab50b6 100644 --- a/front/src/modules/ui/components/table/table-header/FilterDropdownButton.tsx +++ b/front/src/modules/ui/components/table/table-header/FilterDropdownButton.tsx @@ -11,6 +11,9 @@ import { SearchResultsType, useSearch } from '@/search/services/search'; import { humanReadableDate } from '@/utils/utils'; import DatePicker from '../../form/DatePicker'; +import { DropdownMenuItemContainer } from '../../menu/DropdownMenuItemContainer'; +import { DropdownMenuSelectableItem } from '../../menu/DropdownMenuSelectableItem'; +import { DropdownMenuSeparator } from '../../menu/DropdownMenuSeparator'; import DropdownButton from './DropdownButton'; @@ -29,6 +32,8 @@ export const FilterDropdownButton = ({ }: OwnProps) => { const [isUnfolded, setIsUnfolded] = useState(false); + const [selectedEntityId, setSelectedEntityId] = useState(null); + const [isOperandSelectionUnfolded, setIsOperandSelectionUnfolded] = useState(false); @@ -41,7 +46,7 @@ export const FilterDropdownButton = ({ >(undefined); const [filterSearchResults, setSearchInput, setFilterSearch] = - useSearch(); + useSearch({ currentSelectedId: selectedEntityId }); const resetState = useCallback(() => { setIsOperandSelectionUnfolded(false); @@ -92,29 +97,50 @@ export const FilterDropdownButton = ({ ); } - return filterSearchResults.results.map((result, index) => { - return ( - { - onFilterSelect({ - key: selectedFilter.key, - label: selectedFilter.label, - value: result.value, - displayValue: result.render(result.value), - icon: selectedFilter.icon, - operand: selectedFilterOperand, - }); - setIsUnfolded(false); - setSelectedFilter(undefined); - }} - > - - {result.render(result.value)} - - - ); - }); + function resultIsEntity(result: any): result is { id: string } { + return Object.keys(result ?? {}).includes('id'); + } + + return ( + <> + + + {filterSearchResults.results.map((result, index) => { + return ( + { + console.log({ result }); + + if (resultIsEntity(result.value)) { + setSelectedEntityId(result.value.id); + } + + onFilterSelect({ + key: selectedFilter.key, + label: selectedFilter.label, + value: result.value, + displayValue: result.render(result.value), + icon: selectedFilter.icon, + operand: selectedFilterOperand, + }); + setIsUnfolded(false); + setSelectedFilter(undefined); + }} + > + + {result.render(result.value)} + + + ); + })} + + + ); }; function renderValueSelection( @@ -131,7 +157,7 @@ export const FilterDropdownButton = ({ - + {['text', 'relation'].includes(selectedFilter.type) && ( span', - }); + const charlesChip = canvas + .getAllByTestId('dropdown-menu-item') + .find((item) => { + return item.textContent === 'Charles Test'; + }); + + expect(charlesChip).toBeInTheDocument(); + + assert(charlesChip); + await userEvent.click(charlesChip); expect(await canvas.findByText('Airbnb')).toBeInTheDocument(); diff --git a/front/src/pages/companies/companies-filters.tsx b/front/src/pages/companies/companies-filters.tsx index a22690a1cd..488a31358a 100644 --- a/front/src/pages/companies/companies-filters.tsx +++ b/front/src/pages/companies/companies-filters.tsx @@ -164,11 +164,18 @@ export const accountOwnerFilter = { type: 'relation', searchConfig: { query: SEARCH_USER_QUERY, - template: (searchString: string) => ({ - displayName: { - contains: `%${searchString}%`, - mode: QueryMode.Insensitive, - }, + template: (searchString: string, currentSelectedId?: string) => ({ + OR: [ + { + displayName: { + contains: `%${searchString}%`, + mode: QueryMode.Insensitive, + }, + }, + { + id: currentSelectedId ? { equals: currentSelectedId } : undefined, + }, + ], }), resultMapper: (data: any) => ({ value: data, diff --git a/front/src/pages/people/__stories__/People.filterBy.stories.tsx b/front/src/pages/people/__stories__/People.filterBy.stories.tsx index 51f128e098..f4482e7c0f 100644 --- a/front/src/pages/people/__stories__/People.filterBy.stories.tsx +++ b/front/src/pages/people/__stories__/People.filterBy.stories.tsx @@ -1,6 +1,7 @@ import { expect } from '@storybook/jest'; import type { Meta } from '@storybook/react'; import { userEvent, within } from '@storybook/testing-library'; +import assert from 'assert'; import { graphqlMocks } from '~/testing/graphqlMocks'; import { getRenderWrapperForPage } from '~/testing/renderWrappers'; @@ -59,7 +60,16 @@ export const CompanyName: Story = { delay: 200, }); - const qontoChip = canvas.getByText('Qonto', { selector: 'li > span' }); + const qontoChip = canvas + .getAllByTestId('dropdown-menu-item') + .find((item) => { + return item.textContent === 'Qonto'; + }); + + expect(qontoChip).toBeInTheDocument(); + + assert(qontoChip); + await userEvent.click(qontoChip); expect(await canvas.findByText('Alexandre Prot')).toBeInTheDocument(); diff --git a/front/src/pages/people/people-filters.tsx b/front/src/pages/people/people-filters.tsx index 3bb1ecc937..aa29160920 100644 --- a/front/src/pages/people/people-filters.tsx +++ b/front/src/pages/people/people-filters.tsx @@ -100,8 +100,18 @@ export const companyFilter = { type: 'relation', searchConfig: { query: SEARCH_COMPANY_QUERY, - template: (searchString: string) => ({ - name: { contains: `%${searchString}%`, mode: QueryMode.Insensitive }, + template: (searchString: string, currentSelectedId?: string) => ({ + OR: [ + { + name: { + contains: `%${searchString}%`, + mode: QueryMode.Insensitive, + }, + }, + { + id: currentSelectedId ? { equals: currentSelectedId } : undefined, + }, + ], }), resultMapper: (data) => ({ value: data,