diff --git a/src/components/Map/Drawer/BioEntityDrawer/BioEntityDrawer.component.tsx b/src/components/Map/Drawer/BioEntityDrawer/BioEntityDrawer.component.tsx new file mode 100644 index 0000000000000000000000000000000000000000..a0368e2b3a3b0403b6956eae991e3fd58012c759 --- /dev/null +++ b/src/components/Map/Drawer/BioEntityDrawer/BioEntityDrawer.component.tsx @@ -0,0 +1,55 @@ +import { DrawerHeading } from '@/shared/DrawerHeading'; +// import { bioEnititiesResultListSelector } from '@/redux/drawer/drawer.selectors'; +import { useAppSelector } from '@/redux/hooks/useAppSelector'; +import { searchedFromMapBioEntityElement } from '@/redux/bioEntity/bioEntity.selectors'; +import { Icon } from '@/shared/Icon'; + +export const BioEntityDrawer = (): React.ReactNode => { + const bioEntityData = useAppSelector(searchedFromMapBioEntityElement); + + if (!bioEntityData) { + return null; + } + + return ( + <div className="h-full max-h-full" data-testid="reaction-drawer"> + <DrawerHeading + title={ + <> + <span className="font-normal">{bioEntityData.stringType}:</span> + {bioEntityData.name} + </> + } + /> + <div className="flex flex-col gap-6 p-6"> + <div className="text-sm font-normal"> + Compartment: <b className="font-semibold">{bioEntityData.compartment}</b> + </div> + <div className="text-sm font-normal"> + Full name: <b className="font-semibold">{bioEntityData.fullName}</b> + </div> + <h3 className="font-semibold">Annotations:</h3> + {bioEntityData.references.map(reference => { + return ( + <a + className="pl-3 text-sm font-normal" + href={reference.link?.toString()} + key={reference.id} + target="_blank" + > + <div className="flex justify-between"> + <span> + Source:{' '} + <b className="font-semibold"> + {reference?.type} ({reference.resource}) + </b> + </span> + <Icon name="arrow" className="h-6 w-6 fill-font-500" /> + </div> + </a> + ); + })} + </div> + </div> + ); +}; diff --git a/src/components/Map/Drawer/Drawer.component.tsx b/src/components/Map/Drawer/Drawer.component.tsx index abc4e3a2880001d5e5463ec393963f139b54b308..f47cc969b69a6d31352f9ca949b13c682584347d 100644 --- a/src/components/Map/Drawer/Drawer.component.tsx +++ b/src/components/Map/Drawer/Drawer.component.tsx @@ -5,6 +5,7 @@ import { twMerge } from 'tailwind-merge'; import { ReactionDrawer } from './ReactionDrawer'; import { SearchDrawerWrapper as SearchDrawerContent } from './SearchDrawerWrapper'; import { SubmapsDrawer } from './SubmapsDrawer'; +import { BioEntityDrawer } from './BioEntityDrawer/BioEntityDrawer.component'; export const Drawer = (): JSX.Element => { const { isOpen, drawerName } = useAppSelector(drawerSelector); @@ -20,6 +21,7 @@ export const Drawer = (): JSX.Element => { {isOpen && drawerName === 'search' && <SearchDrawerContent />} {isOpen && drawerName === 'submaps' && <SubmapsDrawer />} {isOpen && drawerName === 'reaction' && <ReactionDrawer />} + {isOpen && drawerName === 'bio-entity' && <BioEntityDrawer />} </div> ); }; diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleAliasResults.test.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleAliasResults.test.ts index 8c3eb1e6f95f2461b398610cf139e1096e50d81c..44ac11c9c8e12c0b7a4d357f7851aae3292ee68d 100644 --- a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleAliasResults.test.ts +++ b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleAliasResults.test.ts @@ -1,4 +1,4 @@ -import { FIRST_ARRAY_ELEMENT, SIZE_OF_EMPTY_ARRAY } from '@/constants/common'; +import { FIRST_ARRAY_ELEMENT, SECOND_ARRAY_ELEMENT, SIZE_OF_EMPTY_ARRAY } from '@/constants/common'; import { bioEntityResponseFixture } from '@/models/fixtures/bioEntityContentsFixture'; import { ELEMENT_SEARCH_RESULT_MOCK_ALIAS } from '@/models/mocks/elementSearchResultMock'; import { apiPath } from '@/redux/apiPath'; @@ -27,11 +27,19 @@ describe('handleAliasResults - util', () => { handleAliasResults(dispatch)(ELEMENT_SEARCH_RESULT_MOCK_ALIAS); }); - it('should run getBioEntityAction', async () => { + it('should run openBioEntityDrawerById as first action', async () => { await waitFor(() => { const actions = store.getActions(); expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY); - expect(actions[FIRST_ARRAY_ELEMENT].type).toEqual('project/getMultiBioEntity/pending'); + expect(actions[FIRST_ARRAY_ELEMENT].type).toEqual('drawer/openBioEntityDrawerById'); + }); + }); + + it('should run getMultiBioEntity as second action', async () => { + await waitFor(() => { + const actions = store.getActions(); + expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY); + expect(actions[SECOND_ARRAY_ELEMENT].type).toEqual('project/getMultiBioEntity/pending'); }); }); }); diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleAliasResults.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleAliasResults.ts index 024f627edf2bd8f150a2bb19e738005a433917e3..b14875831efa1192261f2f9dc7f3099a87c41a03 100644 --- a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleAliasResults.ts +++ b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleAliasResults.ts @@ -1,4 +1,5 @@ import { getMultiBioEntity } from '@/redux/bioEntity/bioEntity.thunks'; +import { openBioEntityDrawerById } from '@/redux/drawer/drawer.slice'; import { AppDispatch } from '@/redux/store'; import { ElementSearchResult } from '@/types/models'; @@ -6,6 +7,8 @@ import { ElementSearchResult } from '@/types/models'; export const handleAliasResults = (dispatch: AppDispatch) => async ({ id }: ElementSearchResult): Promise<void> => { + + dispatch(openBioEntityDrawerById(id)); dispatch( getMultiBioEntity({ searchQueries: [id.toString()], diff --git a/src/redux/bioEntity/bioEntity.selectors.ts b/src/redux/bioEntity/bioEntity.selectors.ts index ee2abd0fe3710f10509c05d82bfbb071f03e8e41..dfb1c82afeb17fbedda804ca81e13e02c3cd7923 100644 --- a/src/redux/bioEntity/bioEntity.selectors.ts +++ b/src/redux/bioEntity/bioEntity.selectors.ts @@ -3,7 +3,10 @@ import { rootSelector } from '@/redux/root/root.selectors'; import { MultiSearchData } from '@/types/fetchDataState'; import { BioEntity, BioEntityContent } from '@/types/models'; import { createSelector } from '@reduxjs/toolkit'; -import { currentSelectedSearchElement } from '../drawer/drawer.selectors'; +import { + currentSearchedBioEntityId, + currentSelectedSearchElement, +} from '../drawer/drawer.selectors'; import { currentModelIdSelector, modelsDataSelector } from '../models/models.selectors'; export const bioEntitySelector = createSelector(rootSelector, state => state.bioEntity); @@ -17,6 +20,17 @@ export const bioEntitiesForSelectedSearchElement = createSelector( ), ); +export const searchedFromMapBioEntityElement = createSelector( + bioEntitiesForSelectedSearchElement, + currentSearchedBioEntityId, + (bioEntitiesState, currentBioEntityId): BioEntity | undefined => { + return ( + bioEntitiesState && + bioEntitiesState.data?.find(({ bioEntity }) => bioEntity.id === currentBioEntityId)?.bioEntity + ); + }, +); + export const loadingBioEntityStatusSelector = createSelector( bioEntitiesForSelectedSearchElement, state => state?.loading, diff --git a/src/redux/drawer/drawer.constants.ts b/src/redux/drawer/drawer.constants.ts index 68d612ac9444f668bcf19cbcbfde1efee4b1c430..c04c02963c448309b61fa0d854c93875012d8514 100644 --- a/src/redux/drawer/drawer.constants.ts +++ b/src/redux/drawer/drawer.constants.ts @@ -11,4 +11,5 @@ export const DRAWER_INITIAL_STATE: DrawerState = { selectedSearchElement: '', }, reactionDrawerState: {}, + bioEntityDrawerState: {}, }; diff --git a/src/redux/drawer/drawer.reducers.ts b/src/redux/drawer/drawer.reducers.ts index 44812e00fd50996969c7db1689f773ee3df9314c..2350a2da3b5abaef6b7b7cba1718958f32096851 100644 --- a/src/redux/drawer/drawer.reducers.ts +++ b/src/redux/drawer/drawer.reducers.ts @@ -1,6 +1,7 @@ import { STEP } from '@/constants/searchDrawer'; import type { DrawerState, + OpenBioEntityDrawerByIdAction, OpenReactionDrawerByIdAction, OpenSearchDrawerWithSelectedTabReducerAction, } from '@/redux/drawer/drawer.types'; @@ -92,3 +93,13 @@ export const openReactionDrawerByIdReducer = ( state.drawerName = 'reaction'; state.reactionDrawerState.reactionId = action.payload; }; + +export const openBioEntityDrawerByIdReducer = ( + state: DrawerState, + action: OpenBioEntityDrawerByIdAction, +): void => { + state.isOpen = true; + state.drawerName = 'bio-entity'; + state.bioEntityDrawerState.bioentityId = action.payload; + state.searchDrawerState.selectedSearchElement = action.payload.toString(); +}; diff --git a/src/redux/drawer/drawer.selectors.ts b/src/redux/drawer/drawer.selectors.ts index dc8cb74d8b6e248b999df35cc5f7c501d7dfe281..d0388b6a25ff695562c9ab5f39954ee1b1404eaf 100644 --- a/src/redux/drawer/drawer.selectors.ts +++ b/src/redux/drawer/drawer.selectors.ts @@ -36,6 +36,16 @@ export const currentStepTypeSelector = createSelector( state => state.stepType, ); +export const bioEntityDrawerStateSelector = createSelector( + drawerSelector, + state => state.bioEntityDrawerState, +); + +export const currentSearchedBioEntityId = createSelector( + bioEntityDrawerStateSelector, + state => state.bioentityId, +); + export const resultListSelector = createSelector( rootSelector, currentStepTypeSelector, diff --git a/src/redux/drawer/drawer.slice.ts b/src/redux/drawer/drawer.slice.ts index b0a9e52cf7c350980a46def2f4168974e80fc767..2774d710deee18269fbac31d6d6c872f3d2b4fb6 100644 --- a/src/redux/drawer/drawer.slice.ts +++ b/src/redux/drawer/drawer.slice.ts @@ -7,6 +7,7 @@ import { displayEntityDetailsReducer, displayGroupedSearchResultsReducer, displayMirnaListReducer, + openBioEntityDrawerByIdReducer, openDrawerReducer, openReactionDrawerByIdReducer, openSearchDrawerWithSelectedTabReducer, @@ -31,6 +32,7 @@ const drawerSlice = createSlice({ displayGroupedSearchResults: displayGroupedSearchResultsReducer, displayEntityDetails: displayEntityDetailsReducer, openReactionDrawerById: openReactionDrawerByIdReducer, + openBioEntityDrawerById: openBioEntityDrawerByIdReducer, }, }); @@ -47,6 +49,7 @@ export const { displayGroupedSearchResults, displayEntityDetails, openReactionDrawerById, + openBioEntityDrawerById, } = drawerSlice.actions; export default drawerSlice.reducer; diff --git a/src/redux/drawer/drawer.types.ts b/src/redux/drawer/drawer.types.ts index 3046389c05ccb350d506125d67284c507d62ce80..cb5e8d6bee4a1d9229cd21634898c886bc0c816f 100644 --- a/src/redux/drawer/drawer.types.ts +++ b/src/redux/drawer/drawer.types.ts @@ -14,11 +14,16 @@ export type ReactionDrawerState = { reactionId?: number; }; +export type BioEntityDrawerState = { + bioentityId?: number; +}; + export type DrawerState = { isOpen: boolean; drawerName: DrawerName; searchDrawerState: SearchDrawerState; reactionDrawerState: ReactionDrawerState; + bioEntityDrawerState: BioEntityDrawerState; }; export type OpenSearchDrawerWithSelectedTabReducerPayload = string; @@ -27,3 +32,6 @@ export type OpenSearchDrawerWithSelectedTabReducerAction = export type OpenReactionDrawerByIdPayload = number; export type OpenReactionDrawerByIdAction = PayloadAction<OpenReactionDrawerByIdPayload>; + +export type OpenBioEntityDrawerByIdPayload = number; +export type OpenBioEntityDrawerByIdAction = PayloadAction<OpenBioEntityDrawerByIdPayload>; diff --git a/src/types/drawerName.ts b/src/types/drawerName.ts index 2f7938506e57bacf7160c83abce8d4886aeba945..d34edf92b24a530f1055aa5fe92aa29d26820e8d 100644 --- a/src/types/drawerName.ts +++ b/src/types/drawerName.ts @@ -6,4 +6,5 @@ export type DrawerName = | 'export' | 'legend' | 'submaps' - | 'reaction'; + | 'reaction' + | 'bio-entity';