diff --git a/src/components/FunctionalArea/TopBar/SearchBar/hooks/useParamsQuery.ts b/src/components/FunctionalArea/TopBar/SearchBar/hooks/useParamsQuery.ts index c332beda9c2f6ce79499c2435ddca59b7c7fba19..1cb9c187113f1d431dbff62731f9248c55e14101 100644 --- a/src/components/FunctionalArea/TopBar/SearchBar/hooks/useParamsQuery.ts +++ b/src/components/FunctionalArea/TopBar/SearchBar/hooks/useParamsQuery.ts @@ -32,7 +32,7 @@ export const useParamsQuery = (): UseParamsQuery => { useEffect(() => { if (searchParams?.search) dispatch(getSearchData(searchParams.search as string)); - }, [dispatch]); + }, [dispatch, searchParams.search]); return { setSearchQueryInRouter, searchParams }; }; diff --git a/src/components/FunctionalArea/TopBar/TopBar.component.test.tsx b/src/components/FunctionalArea/TopBar/TopBar.component.test.tsx index e6e44ac13532f839d4b82fbde10dfdd537fc49ce..ae9f17c99c9042ec6c43ad19c455f335e7071b19 100644 --- a/src/components/FunctionalArea/TopBar/TopBar.component.test.tsx +++ b/src/components/FunctionalArea/TopBar/TopBar.component.test.tsx @@ -1,10 +1,14 @@ import { StoreType } from '@/redux/store'; -import { getReduxWrapperWithStore } from '@/utils/testing/getReduxWrapperWithStore'; +import { + InitialStoreState, + getReduxWrapperWithStore, +} from '@/utils/testing/getReduxWrapperWithStore'; import { render, screen } from '@testing-library/react'; +import { initialStateFixture } from '@/redux/drawer/drawerFixture'; import { TopBar } from './TopBar.component'; -const renderComponent = (): { store: StoreType } => { - const { Wrapper, store } = getReduxWrapperWithStore(); +const renderComponent = (initialStoreState: InitialStoreState = {}): { store: StoreType } => { + const { Wrapper, store } = getReduxWrapperWithStore(initialStoreState); return ( render( @@ -25,4 +29,16 @@ describe('TopBar - component', () => { expect(screen.getByTestId('user-avatar')).toBeInTheDocument(); expect(screen.getByTestId('search-bar')).toBeInTheDocument(); }); + + it('should open submaps drawer on submaps button click', () => { + const { store } = renderComponent({ drawer: initialStateFixture }); + + const button = screen.getByRole('button', { name: 'Submaps' }); + button.click(); + + const { isOpen, drawerName } = store.getState().drawer; + + expect(isOpen).toBe(true); + expect(drawerName).toBe('submaps'); + }); }); diff --git a/src/components/FunctionalArea/TopBar/TopBar.component.tsx b/src/components/FunctionalArea/TopBar/TopBar.component.tsx index 83a5c29958bc523d8becaf55be32cbcf3b2f3144..9641b686e7aa05ef497a1bf4022bd50c2c8779f7 100644 --- a/src/components/FunctionalArea/TopBar/TopBar.component.tsx +++ b/src/components/FunctionalArea/TopBar/TopBar.component.tsx @@ -1,21 +1,31 @@ import { SearchBar } from '@/components/FunctionalArea/TopBar/SearchBar'; import { UserAvatar } from '@/components/FunctionalArea/TopBar/UserAvatar'; +import { openSubmapsDrawer } from '@/redux/drawer/drawer.slice'; +import { useAppDispatch } from '@/redux/hooks/useAppDispatch'; import { Button } from '@/shared/Button'; -export const TopBar = (): JSX.Element => ( - <div className="flex h-16 w-full flex-row items-center justify-between border-b border-font-500 border-opacity-[0.12] bg-white py-4 pl-7 pr-6"> - <div className="flex flex-row items-center"> - <UserAvatar /> - <SearchBar /> - <Button icon="plus" isIcon isFrontIcon className="ml-8 mr-4"> - Submaps - </Button> - <Button icon="plus" isIcon isFrontIcon> - Overlays - </Button> - </div> - <div className="bg-primary-100 px-4 py-1 text-xs leading-6 text-primary-500"> - Parkinson disease map +export const TopBar = (): JSX.Element => { + const dispatch = useAppDispatch(); + + const onSubmapsClick = (): void => { + dispatch(openSubmapsDrawer()); + }; + + return ( + <div className="flex h-16 w-full flex-row items-center justify-between border-b border-font-500 border-opacity-[0.12] bg-white py-4 pl-7 pr-6"> + <div className="flex flex-row items-center"> + <UserAvatar /> + <SearchBar /> + <Button icon="plus" isIcon isFrontIcon className="ml-8 mr-4" onClick={onSubmapsClick}> + Submaps + </Button> + <Button icon="plus" isIcon isFrontIcon> + Overlays + </Button> + </div> + <div className="bg-primary-100 px-4 py-1 text-xs leading-6 text-primary-500"> + Parkinson disease map + </div> </div> - </div> -); + ); +}; diff --git a/src/components/Map/Drawer/Drawer.component.test.tsx b/src/components/Map/Drawer/Drawer.component.test.tsx index 26c7bd9828544a863a6198988441c7b9cdb7927b..bb39e5bb8d1c4cad03766c35733f6b57b74b4185 100644 --- a/src/components/Map/Drawer/Drawer.component.test.tsx +++ b/src/components/Map/Drawer/Drawer.component.test.tsx @@ -1,4 +1,4 @@ -import { openSearchDrawer } from '@/redux/drawer/drawer.slice'; +import { openSearchDrawer, openSubmapsDrawer } from '@/redux/drawer/drawer.slice'; import { StoreType } from '@/redux/store'; import { getReduxWrapperWithStore } from '@/utils/testing/getReduxWrapperWithStore'; import { act, fireEvent, render, screen } from '@testing-library/react'; @@ -63,4 +63,18 @@ describe('Drawer - component', () => { expect(screen.queryByTestId('search-drawer-content')).not.toBeInTheDocument(); }); }); + + describe('submap drawer', () => { + it('should open drawer and display submaps', async () => { + const { store } = renderComponent(); + + expect(screen.queryByTestId('submap-drawer')).not.toBeInTheDocument(); + + await act(() => { + store.dispatch(openSubmapsDrawer()); + }); + + expect(screen.getByTestId('submap-drawer')).toBeInTheDocument(); + }); + }); }); diff --git a/src/components/Map/Drawer/Drawer.component.tsx b/src/components/Map/Drawer/Drawer.component.tsx index 6415d40280dc158cb40cdde23b7a379432a76a0f..269281bd16f691fb84569c7ba489964751128432 100644 --- a/src/components/Map/Drawer/Drawer.component.tsx +++ b/src/components/Map/Drawer/Drawer.component.tsx @@ -3,6 +3,7 @@ import { drawerSelector } from '@/redux/drawer/drawer.selectors'; import { useAppSelector } from '@/redux/hooks/useAppSelector'; import { twMerge } from 'tailwind-merge'; import { SearchDrawerWrapper as SearchDrawerContent } from './SearchDrawerWrapper'; +import { SubmapsDrawer } from './SubmapsDrawer'; export const Drawer = (): JSX.Element => { const { isOpen, drawerName } = useAppSelector(drawerSelector); @@ -10,12 +11,13 @@ export const Drawer = (): JSX.Element => { return ( <div className={twMerge( - 'absolute left-[88px] top-[104px] z-10 h-calc-drawer w-[432px] -translate-x-full transform bg-white-pearl text-font-500 transition-all duration-500', + 'absolute bottom-0 left-[88px] top-[104px] z-10 h-calc-drawer w-[432px] -translate-x-full transform bg-white-pearl text-font-500 transition-all duration-500', isOpen && 'translate-x-0', )} role={DRAWER_ROLE} > {isOpen && drawerName === 'search' && <SearchDrawerContent />} + {isOpen && drawerName === 'submaps' && <SubmapsDrawer />} </div> ); }; diff --git a/src/components/Map/Drawer/SearchDrawerWrapper/GroupedSearchResults/GroupedSearchResults.component.tsx b/src/components/Map/Drawer/SearchDrawerWrapper/GroupedSearchResults/GroupedSearchResults.component.tsx index 29a6076e9937a13aea95d1245c0e8c3c2ac48834..28aac2b5b95fc3ef1c86b687a6cc1ad63c2631f3 100644 --- a/src/components/Map/Drawer/SearchDrawerWrapper/GroupedSearchResults/GroupedSearchResults.component.tsx +++ b/src/components/Map/Drawer/SearchDrawerWrapper/GroupedSearchResults/GroupedSearchResults.component.tsx @@ -6,11 +6,14 @@ import { closeDrawer } from '@/redux/drawer/drawer.slice'; import { useAppDispatch } from '@/redux/hooks/useAppDispatch'; import { IconButton } from '@/shared/IconButton'; import { Accordion } from '@/shared/Accordion'; +import { useAppSelector } from '@/redux/hooks/useAppSelector'; +import { searchValueSelector } from '@/redux/search/search.selectors'; export const CLOSE_BUTTON_ROLE = 'close-drawer-button'; export const GroupedSearchResults = (): JSX.Element => { const dispatch = useAppDispatch(); + const searchValue = useAppSelector(searchValueSelector); const handleCloseDrawer = (): void => { dispatch(closeDrawer()); @@ -21,7 +24,7 @@ export const GroupedSearchResults = (): JSX.Element => { <div className="flex items-center justify-between border-b border-b-divide px-6"> <div className=" py-8 text-xl"> <span className="font-normal">Search: </span> - <span className="font-semibold">NADH</span> + <span className="font-semibold">{searchValue}</span> </div> <IconButton className="bg-white-pearl" diff --git a/src/components/Map/Drawer/SubmapsDrawer/SubmapItem/SubmapItem.component.tsx b/src/components/Map/Drawer/SubmapsDrawer/SubmapItem/SubmapItem.component.tsx new file mode 100644 index 0000000000000000000000000000000000000000..a143386895dc17b677d768ac5ee16ef8ac51e6f7 --- /dev/null +++ b/src/components/Map/Drawer/SubmapsDrawer/SubmapItem/SubmapItem.component.tsx @@ -0,0 +1,22 @@ +import { Button } from '@/shared/Button'; +import { IconButton } from '@/shared/IconButton'; + +interface SubmapItemProps { + modelName: string; +} + +export const SubmpamItem = ({ modelName }: SubmapItemProps): JSX.Element => ( + <div className="flex flex-row flex-nowrap items-center justify-between border-b py-6"> + {modelName} + <div className="flex flex-row flex-nowrap items-center"> + <Button variantStyles="ghost" className="mr-4"> + Download + </Button> + <IconButton + icon="chevron-right" + className="h-6 w-6 bg-white-pearl" + classNameIcon="fill-font-500 h-6 w-6" + /> + </div> + </div> +); diff --git a/src/components/Map/Drawer/SubmapsDrawer/SubmapsDrawer.test.tsx b/src/components/Map/Drawer/SubmapsDrawer/SubmapsDrawer.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..f37efc2cedc5f130a0c3647433411cb167dcc778 --- /dev/null +++ b/src/components/Map/Drawer/SubmapsDrawer/SubmapsDrawer.test.tsx @@ -0,0 +1,51 @@ +import { + InitialStoreState, + getReduxWrapperWithStore, +} from '@/utils/testing/getReduxWrapperWithStore'; +import { render, screen } from '@testing-library/react'; +import { StoreType } from '@/redux/store'; +import { MODELS_MOCK_SHORT } from '@/models/mocks/modelsMock'; +import { openedDrawerSubmapsFixture } from '@/redux/drawer/drawerFixture'; +import { SubmapsDrawer } from './SubmapsDrawer'; + +const renderComponent = (initialStoreState: InitialStoreState = {}): { store: StoreType } => { + const { Wrapper, store } = getReduxWrapperWithStore(initialStoreState); + + return ( + render( + <Wrapper> + <SubmapsDrawer /> + </Wrapper>, + ), + { + store, + } + ); +}; + +describe('SubmapsDrawer - component', () => { + it('should display drawer heading and list of submaps', () => { + renderComponent({ + models: { data: MODELS_MOCK_SHORT, loading: 'succeeded', error: { name: '', message: '' } }, + }); + + expect(screen.getByText('Core PD map')).toBeInTheDocument(); + expect(screen.getByText('Histamine signaling')).toBeInTheDocument(); + expect(screen.getByText('PRKN substrates')).toBeInTheDocument(); + }); + it('should close drawer after clicking close button', () => { + const { store } = renderComponent({ + models: { data: MODELS_MOCK_SHORT, loading: 'succeeded', error: { name: '', message: '' } }, + drawer: openedDrawerSubmapsFixture, + }); + const closeButton = screen.getByRole('close-drawer-button'); + + closeButton.click(); + + const { + drawer: { isOpen }, + } = store.getState(); + + expect(isOpen).toBe(false); + }); +}); diff --git a/src/components/Map/Drawer/SubmapsDrawer/SubmapsDrawer.tsx b/src/components/Map/Drawer/SubmapsDrawer/SubmapsDrawer.tsx new file mode 100644 index 0000000000000000000000000000000000000000..35af92d1994ef36b6de85fe734667d7344f88b75 --- /dev/null +++ b/src/components/Map/Drawer/SubmapsDrawer/SubmapsDrawer.tsx @@ -0,0 +1,19 @@ +import { useAppSelector } from '@/redux/hooks/useAppSelector'; +import { modelsDataSelector } from '@/redux/models/models.selectors'; +import { DrawerHeading } from '@/shared/DrawerHeading'; +import { SubmpamItem } from './SubmapItem/SubmapItem.component'; + +export const SubmapsDrawer = (): JSX.Element => { + const models = useAppSelector(modelsDataSelector); + + return ( + <div data-testid="submap-drawer" className="h-full max-h-full"> + <DrawerHeading title="Submaps" /> + <ul className="h-[calc(100%-93px)] max-h-[calc(100%-93px)] overflow-y-auto px-6"> + {models.map(model => ( + <SubmpamItem key={model.idObject} modelName={model.name} /> + ))} + </ul> + </div> + ); +}; diff --git a/src/components/Map/Drawer/SubmapsDrawer/index.ts b/src/components/Map/Drawer/SubmapsDrawer/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..a842ee2eb9d390e5773d2f28416d62d8053f5ad4 --- /dev/null +++ b/src/components/Map/Drawer/SubmapsDrawer/index.ts @@ -0,0 +1 @@ +export { SubmapsDrawer } from './SubmapsDrawer'; diff --git a/src/models/mocks/modelsMock.ts b/src/models/mocks/modelsMock.ts index e4aa9a0a2d900c05d0b41edf786d0ca2895d2be2..96cd8bf94a4a098980ee270c78be6a17f95349ea 100644 --- a/src/models/mocks/modelsMock.ts +++ b/src/models/mocks/modelsMock.ts @@ -403,3 +403,57 @@ export const MODELS_MOCK: MapModel[] = [ maxZoom: 7, }, ]; + +export const MODELS_MOCK_SHORT: MapModel[] = [ + { + idObject: 5053, + width: 26779.25, + height: 13503.0, + defaultCenterX: null, + defaultCenterY: null, + description: '', + name: 'Core PD map', + defaultZoomLevel: null, + tileSize: 256, + references: [], + authors: [], + creationDate: null, + modificationDates: [], + minZoom: 2, + maxZoom: 9, + }, + { + idObject: 5052, + width: 3511.09375, + height: 1312.125, + defaultCenterX: null, + defaultCenterY: null, + description: '', + name: 'Histamine signaling', + defaultZoomLevel: null, + tileSize: 256, + references: [], + authors: [], + creationDate: null, + modificationDates: [], + minZoom: 2, + maxZoom: 6, + }, + { + idObject: 5054, + width: 1652.75, + height: 1171.9429798877356, + defaultCenterX: null, + defaultCenterY: null, + description: '', + name: 'PRKN substrates', + defaultZoomLevel: null, + tileSize: 256, + references: [], + authors: [], + creationDate: null, + modificationDates: [], + minZoom: 2, + maxZoom: 5, + }, +]; diff --git a/src/redux/drawer/drawer.reducers.test.ts b/src/redux/drawer/drawer.reducers.test.ts index ff8f02c239dbf2cc5a72c41ae3bd1001f856af12..a4a172f27d699a73c645d83d48c74ba37af4b52f 100644 --- a/src/redux/drawer/drawer.reducers.test.ts +++ b/src/redux/drawer/drawer.reducers.test.ts @@ -10,6 +10,7 @@ import drawerReducer, { displayGroupedSearchResults, openDrawer, openSearchDrawer, + openSubmapsDrawer, } from './drawer.slice'; import type { DrawerState } from './drawer.types'; @@ -79,6 +80,15 @@ describe('drawer reducer', () => { expect(currentStep).toEqual(STEP.FIRST); }); + it('should update the store after openSubmapsDrawer action', async () => { + const { type } = await store.dispatch(openSubmapsDrawer()); + const { isOpen, drawerName } = store.getState().drawer; + + expect(type).toBe('drawer/openSubmapsDrawer'); + expect(isOpen).toBe(true); + expect(drawerName).toEqual('submaps'); + }); + it('should update the store after closeDrawerReducer action', async () => { const { type } = await store.dispatch(closeDrawer()); const { diff --git a/src/redux/drawer/drawer.reducers.ts b/src/redux/drawer/drawer.reducers.ts index e68271b783561b56761808589cd2785eb126548f..8efdb78007bef6b3c4d22105975d2077cd8c63e6 100644 --- a/src/redux/drawer/drawer.reducers.ts +++ b/src/redux/drawer/drawer.reducers.ts @@ -14,6 +14,11 @@ export const openSearchDrawerReducer = (state: DrawerState): void => { state.searchDrawerState.currentStep = STEP.FIRST; }; +export const openSubmapsDrawerReducer = (state: DrawerState): void => { + state.isOpen = true; + state.drawerName = 'submaps'; +}; + export const closeDrawerReducer = (state: DrawerState): void => { state.isOpen = false; state.drawerName = 'none'; diff --git a/src/redux/drawer/drawer.slice.ts b/src/redux/drawer/drawer.slice.ts index 3deb088ddb094612b0e6086781c88b26026d4f31..612009e1a9173bab17c6c8e8b9555c17edc1a3ce 100644 --- a/src/redux/drawer/drawer.slice.ts +++ b/src/redux/drawer/drawer.slice.ts @@ -9,6 +9,7 @@ import { displayMirnaListReducer, openDrawerReducer, openSearchDrawerReducer, + openSubmapsDrawerReducer, } from './drawer.reducers'; const initialState: DrawerState = { @@ -27,6 +28,7 @@ const drawerSlice = createSlice({ reducers: { openDrawer: openDrawerReducer, openSearchDrawer: openSearchDrawerReducer, + openSubmapsDrawer: openSubmapsDrawerReducer, closeDrawer: closeDrawerReducer, displayDrugsList: displayDrugsListReducer, displayChemicalsList: displayChemicalsListReducer, @@ -39,6 +41,7 @@ const drawerSlice = createSlice({ export const { openDrawer, openSearchDrawer, + openSubmapsDrawer, closeDrawer, displayDrugsList, displayChemicalsList, diff --git a/src/redux/drawer/drawerFixture.ts b/src/redux/drawer/drawerFixture.ts index 46c1896ac9808ffe7a802cc9db9c746ce95c1529..987c01d30345dc72d97f44b28cd5281a2474033e 100644 --- a/src/redux/drawer/drawerFixture.ts +++ b/src/redux/drawer/drawerFixture.ts @@ -10,6 +10,16 @@ export const initialStateFixture: DrawerState = { }, }; +export const openedDrawerSubmapsFixture: DrawerState = { + isOpen: true, + drawerName: 'submaps', + searchDrawerState: { + currentStep: 0, + stepType: 'none', + selectedValue: undefined, + }, +}; + export const drawerSearchStepOneFixture: DrawerState = { isOpen: true, drawerName: 'search', diff --git a/src/shared/DrawerHeading/DrawerHeading.component.test.tsx b/src/shared/DrawerHeading/DrawerHeading.component.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..0b42c4a04240d4bce88205aefba8dacfe52adc15 --- /dev/null +++ b/src/shared/DrawerHeading/DrawerHeading.component.test.tsx @@ -0,0 +1,49 @@ +import { StoreType } from '@/redux/store'; +import { render, screen } from '@testing-library/react'; +import { + InitialStoreState, + getReduxWrapperWithStore, +} from '@/utils/testing/getReduxWrapperWithStore'; +import { openedDrawerSubmapsFixture } from '@/redux/drawer/drawerFixture'; +import { DrawerHeading } from './DrawerHeading.component'; + +const renderComponent = ( + title: string, + initialStoreState: InitialStoreState = {}, +): { store: StoreType } => { + const { Wrapper, store } = getReduxWrapperWithStore(initialStoreState); + + return ( + render( + <Wrapper> + <DrawerHeading title={title} /> + </Wrapper>, + ), + { + store, + } + ); +}; + +describe('DrawerHeading - component', () => { + it('should display passed title', () => { + renderComponent('Example'); + + expect(screen.getByText('Example')).toBeInTheDocument(); + }); + + it('should call class drawer on close button click', () => { + const { store } = renderComponent('Example', { + drawer: { + ...openedDrawerSubmapsFixture, + }, + }); + + expect(store.getState().drawer.isOpen).toBe(true); + + const closeButton = screen.getByRole('close-drawer-button'); + closeButton.click(); + + expect(store.getState().drawer.isOpen).toBe(false); + }); +}); diff --git a/src/shared/DrawerHeading/DrawerHeading.component.tsx b/src/shared/DrawerHeading/DrawerHeading.component.tsx new file mode 100644 index 0000000000000000000000000000000000000000..650c76f3941101f182cd3e941e51328134a3da6d --- /dev/null +++ b/src/shared/DrawerHeading/DrawerHeading.component.tsx @@ -0,0 +1,31 @@ +import { useAppDispatch } from '@/redux/hooks/useAppDispatch'; +import { IconButton } from '@/shared/IconButton'; +import { closeDrawer } from '@/redux/drawer/drawer.slice'; +import { CLOSE_BUTTON_ROLE } from '../DrawerHeadingBackwardButton/DrawerHeadingBackwardButton.constants'; + +interface DrawerHeadingProps { + title: string; +} + +export const DrawerHeading = ({ title }: DrawerHeadingProps): JSX.Element => { + const dispatch = useAppDispatch(); + + const onCloseButtonClick = (): void => { + dispatch(closeDrawer()); + }; + + return ( + <div className="flex items-center justify-between border-b border-b-divide px-6"> + <div className=" py-8 text-xl"> + <span className="font-semibold">{title}</span> + </div> + <IconButton + className="bg-white-pearl" + classNameIcon="fill-font-500" + icon="close" + role={CLOSE_BUTTON_ROLE} + onClick={onCloseButtonClick} + /> + </div> + ); +}; diff --git a/src/shared/DrawerHeading/index.ts b/src/shared/DrawerHeading/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..f2ecd0e8f7f8abaae90d35a950cf9ecf33b8cfd2 --- /dev/null +++ b/src/shared/DrawerHeading/index.ts @@ -0,0 +1 @@ +export { DrawerHeading } from './DrawerHeading.component'; diff --git a/src/types/drawerName.ts b/src/types/drawerName.ts index 1ee1478dcee864ab2339be99906cb6f583d84cf1..3dcb4bdce5c8ce8fbf6c6129956eaa23d55ab0f4 100644 --- a/src/types/drawerName.ts +++ b/src/types/drawerName.ts @@ -1 +1,8 @@ -export type DrawerName = 'none' | 'search' | 'project-info' | 'plugins' | 'export' | 'legend'; +export type DrawerName = + | 'none' + | 'search' + | 'project-info' + | 'plugins' + | 'export' + | 'legend' + | 'submaps';