Skip to content
Snippets Groups Projects
Commit 7c753c8f authored by Adrian Orłów's avatar Adrian Orłów
Browse files

feat: add display pins of categories + global search input

parent e92abcca
No related branches found
No related tags found
2 merge requests!223reset the pin numbers before search results are fetch (so the results will be...,!63feat: add category pins rendering and global search input
Showing
with 142 additions and 53 deletions
......@@ -5,12 +5,14 @@ import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
import {
isPendingSearchStatusSelector,
perfectMatchSelector,
searchInputSelector,
} from '@/redux/search/search.selectors';
import { setSearchInput } from '@/redux/search/search.slice';
import { getSearchData } from '@/redux/search/search.thunks';
import Image from 'next/image';
import { ChangeEvent, KeyboardEvent, useEffect, useState } from 'react';
import { useSelector } from 'react-redux';
import { useRouter } from 'next/router';
import { ChangeEvent, KeyboardEvent } from 'react';
import { useSelector } from 'react-redux';
import { getDefaultSearchTab, getSearchValuesArrayAndTrimToSeven } from './SearchBar.utils';
const ENTER_KEY_CODE = 'Enter';
......@@ -19,24 +21,19 @@ export const SearchBar = (): JSX.Element => {
const isPendingSearchStatus = useSelector(isPendingSearchStatusSelector);
const isDrawerOpen = useSelector(isDrawerOpenSelector);
const isPerfectMatch = useSelector(perfectMatchSelector);
const [searchValue, setSearchValue] = useState<string>('');
const searchValue = useSelector(searchInputSelector);
const dispatch = useAppDispatch();
const { query } = useRouter();
useEffect(() => {
if (!searchValue && query.searchValue) {
setSearchValue(String(query.searchValue));
}
}, [searchValue, query]);
const openSearchDrawerIfClosed = (defaultSearchTab: string): void => {
if (!isDrawerOpen) {
dispatch(openSearchDrawerWithSelectedTab(defaultSearchTab));
}
};
const onSearchChange = (event: ChangeEvent<HTMLInputElement>): void =>
setSearchValue(event.target.value);
const onSearchChange = (event: ChangeEvent<HTMLInputElement>): void => {
dispatch(setSearchInput(event.target.value));
};
const onSearchClick = (): void => {
const searchValues = getSearchValuesArrayAndTrimToSeven(searchValue);
......
/* eslint-disable no-magic-numbers */
import { searchedBioEntitesSelectorOfCurrentMap } from '@/redux/bioEntity/bioEntity.selectors';
import { searchedChemicalsBioEntitesOfCurrentMapSelector } from '@/redux/chemicals/chemicals.selectors';
import { searchedDrugsBioEntitesOfCurrentMapSelector } from '@/redux/drugs/drugs.selectors';
import { usePointToProjection } from '@/utils/map/usePointToProjection';
import BaseLayer from 'ol/layer/Base';
import VectorLayer from 'ol/layer/Vector';
......@@ -11,11 +13,17 @@ import { getBioEntitiesFeatures } from './getBioEntitiesFeatures';
export const useOlMapPinsLayer = (): BaseLayer => {
const pointToProjection = usePointToProjection();
const contentBioEntites = useSelector(searchedBioEntitesSelectorOfCurrentMap);
const chemicalsBioEntities = useSelector(searchedChemicalsBioEntitesOfCurrentMapSelector);
const drugsBioEntities = useSelector(searchedDrugsBioEntitesOfCurrentMapSelector);
const bioEntityFeatures = useMemo(
() =>
[getBioEntitiesFeatures(contentBioEntites, { pointToProjection, type: 'bioEntity' })].flat(),
[contentBioEntites, pointToProjection],
[
getBioEntitiesFeatures(contentBioEntites, { pointToProjection, type: 'bioEntity' }),
getBioEntitiesFeatures(chemicalsBioEntities, { pointToProjection, type: 'chemicals' }),
getBioEntitiesFeatures(drugsBioEntities, { pointToProjection, type: 'drugs' }),
].flat(),
[contentBioEntites, drugsBioEntities, chemicalsBioEntities, pointToProjection],
);
const vectorSource = useMemo(() => {
......
import { FIRST } from '@/constants/common';
import { getReduxStoreWithActionsListener } from '@/utils/testing/getReduxStoreActionsListener';
import { handleDataReset } from './handleDataReset';
......@@ -6,10 +5,17 @@ describe('handleDataReset', () => {
const { store } = getReduxStoreWithActionsListener();
const { dispatch } = store;
it('should dispatch resetReactionsData action', () => {
it('should dispatch reset actions', () => {
dispatch(handleDataReset);
const actions = store.getActions();
expect(actions[FIRST].type).toBe('reactions/resetReactionsData');
const actionsTypes = [
'reactions/resetReactionsData',
'search/clearSearchData',
'drugs/clearDrugsData',
'chemicals/clearChemicalsData',
];
expect(actions.map(a => a.type)).toStrictEqual(actionsTypes);
});
});
import { clearChemicalsData } from '@/redux/chemicals/chemicals.slice';
import { clearDrugsData } from '@/redux/drugs/drugs.slice';
import { resetReactionsData } from '@/redux/reactions/reactions.slice';
import { clearSearchData } from '@/redux/search/search.slice';
import { AppDispatch } from '@/redux/store';
export const handleDataReset = (dispatch: AppDispatch): void => {
dispatch(resetReactionsData());
dispatch(clearSearchData());
dispatch(clearDrugsData());
dispatch(clearChemicalsData());
};
import { z } from 'zod';
import { bioEntitySchema } from './bioEntitySchema';
import { referenceSchema } from './referenceSchema';
import { targetElementSchema } from './targetElementSchema';
import { targetParticipantSchema } from './targetParticipantSchema';
export const targetSchema = z.object({
......@@ -9,7 +9,7 @@ export const targetSchema = z.object({
/** list of target references */
references: z.array(referenceSchema),
/** list of elements on the map associated with this target */
targetElements: z.array(targetElementSchema),
targetElements: z.array(bioEntitySchema),
/** list of identifiers associated with this target */
targetParticipants: z.array(targetParticipantSchema),
});
import { DEFAULT_ERROR } from '@/constants/errors';
import { chemicalsFixture } from '@/models/fixtures/chemicalsFixture';
import { apiPath } from '@/redux/apiPath';
import { DEFAULT_ERROR } from '@/constants/errors';
import {
ToolkitStoreWithSingleSlice,
createStoreInstanceUsingSliceReducer,
} from '@/utils/createStoreInstanceUsingSliceReducer';
import { mockNetworkResponse } from '@/utils/mockNetworkResponse';
import { mockNetworkNewAPIResponse } from '@/utils/mockNetworkResponse';
import { HttpStatusCode } from 'axios';
import chemicalsReducer from './chemicals.slice';
import { getChemicals } from './chemicals.thunks';
import { ChemicalsState } from './chemicals.types';
const mockedAxiosClient = mockNetworkResponse();
const mockedAxiosClient = mockNetworkNewAPIResponse();
const SEARCH_QUERY = 'Corticosterone';
const INITIAL_STATE: ChemicalsState = {
......
import { ActionReducerMapBuilder } from '@reduxjs/toolkit';
import { DEFAULT_ERROR } from '@/constants/errors';
import { ActionReducerMapBuilder } from '@reduxjs/toolkit';
import { getChemicals, getMultiChemicals } from './chemicals.thunks';
import { ChemicalsState } from './chemicals.types';
......@@ -43,3 +43,8 @@ export const getMultiChemicalsReducer = (
// TODO: error management to be discussed in the team
});
};
export const clearChemicalsDataReducer = (state: ChemicalsState): void => {
state.data = [];
state.loading = 'idle';
};
import { SIZE_OF_EMPTY_ARRAY } from '@/constants/common';
import { rootSelector } from '@/redux/root/root.selectors';
import { Chemical } from '@/types/models';
import { createSelector } from '@reduxjs/toolkit';
import { MultiSearchData } from '@/types/fetchDataState';
import { BioEntity, Chemical } from '@/types/models';
import { createSelector } from '@reduxjs/toolkit';
import { currentSelectedSearchElement } from '../drawer/drawer.selectors';
import { currentModelIdSelector } from '../models/models.selectors';
export const chemicalsSelector = createSelector(rootSelector, state => state.chemicals);
......@@ -16,6 +17,24 @@ export const chemicalsForSelectedSearchElementSelector = createSelector(
),
);
export const searchedChemicalsBioEntitesOfCurrentMapSelector = createSelector(
chemicalsSelector,
currentSelectedSearchElement,
currentModelIdSelector,
(chemicalsState, currentSearchElement, currentModelId): BioEntity[] => {
return (chemicalsState?.data || [])
.filter(({ searchQueryElement }) =>
currentSearchElement ? searchQueryElement === currentSearchElement : true,
)
.map(({ data }) => data || [])
.flat()
.map(({ targets }) => targets.map(({ targetElements }) => targetElements))
.flat()
.flat()
.filter(bioEntity => bioEntity.model === currentModelId);
},
);
export const loadingChemicalsStatusSelector = createSelector(
chemicalsForSelectedSearchElementSelector,
state => state?.loading,
......
import { ChemicalsState } from '@/redux/chemicals/chemicals.types';
import { createSlice } from '@reduxjs/toolkit';
import { getChemicalsReducer, getMultiChemicalsReducer } from './chemicals.reducers';
import {
clearChemicalsDataReducer,
getChemicalsReducer,
getMultiChemicalsReducer,
} from './chemicals.reducers';
const initialState: ChemicalsState = {
data: [],
......@@ -11,11 +15,15 @@ const initialState: ChemicalsState = {
export const chemicalsSlice = createSlice({
name: 'chemicals',
initialState,
reducers: {},
reducers: {
clearChemicalsData: clearChemicalsDataReducer,
},
extraReducers: builder => {
getChemicalsReducer(builder);
getMultiChemicalsReducer(builder);
},
});
export const { clearChemicalsData } = chemicalsSlice.actions;
export default chemicalsSlice.reducer;
......@@ -4,13 +4,13 @@ import {
ToolkitStoreWithSingleSlice,
createStoreInstanceUsingSliceReducer,
} from '@/utils/createStoreInstanceUsingSliceReducer';
import { mockNetworkResponse } from '@/utils/mockNetworkResponse';
import { mockNetworkNewAPIResponse } from '@/utils/mockNetworkResponse';
import { HttpStatusCode } from 'axios';
import chemicalsReducer from './chemicals.slice';
import { getChemicals } from './chemicals.thunks';
import { ChemicalsState } from './chemicals.types';
const mockedAxiosClient = mockNetworkResponse();
const mockedAxiosClient = mockNetworkNewAPIResponse();
const SEARCH_QUERY = 'Corticosterone';
describe('chemicals thunks', () => {
......
import { chemicalSchema } from '@/models/chemicalSchema';
import { apiPath } from '@/redux/apiPath';
import { axiosInstance } from '@/services/api/utils/axiosInstance';
import { axiosInstanceNewAPI } from '@/services/api/utils/axiosInstance';
import { Chemical } from '@/types/models';
import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema';
import { createAsyncThunk } from '@reduxjs/toolkit';
......@@ -9,7 +9,7 @@ import { z } from 'zod';
export const getChemicals = createAsyncThunk(
'project/getChemicals',
async (searchQuery: string): Promise<Chemical[] | undefined> => {
const response = await axiosInstance.get<Chemical[]>(
const response = await axiosInstanceNewAPI.get<Chemical[]>(
apiPath.getChemicalsStringWithQuery(searchQuery),
);
......
import { HttpStatusCode } from 'axios';
import { DEFAULT_ERROR } from '@/constants/errors';
import { drugsFixture } from '@/models/fixtures/drugFixtures';
import { mockNetworkResponse } from '@/utils/mockNetworkResponse';
import { apiPath } from '@/redux/apiPath';
import {
ToolkitStoreWithSingleSlice,
createStoreInstanceUsingSliceReducer,
} from '@/utils/createStoreInstanceUsingSliceReducer';
import { DEFAULT_ERROR } from '@/constants/errors';
import { apiPath } from '@/redux/apiPath';
import { getDrugs } from './drugs.thunks';
import { mockNetworkNewAPIResponse } from '@/utils/mockNetworkResponse';
import { HttpStatusCode } from 'axios';
import drugsReducer from './drugs.slice';
import { getDrugs } from './drugs.thunks';
import { DrugsState } from './drugs.types';
const mockedAxiosClient = mockNetworkResponse();
const mockedAxiosClient = mockNetworkNewAPIResponse();
const SEARCH_QUERY = 'aspirin';
const INITIAL_STATE: DrugsState = {
......
import { DEFAULT_ERROR } from '@/constants/errors';
import { ActionReducerMapBuilder } from '@reduxjs/toolkit';
import { DrugsState } from './drugs.types';
import { getDrugs, getMultiDrugs } from './drugs.thunks';
import { DrugsState } from './drugs.types';
export const getDrugsReducer = (builder: ActionReducerMapBuilder<DrugsState>): void => {
builder.addCase(getDrugs.pending, (state, action) => {
......@@ -41,3 +41,8 @@ export const getMultiDrugsReducer = (builder: ActionReducerMapBuilder<DrugsState
// TODO: error management to be discussed in the team
});
};
export const clearDrugsDataReducer = (state: DrugsState): void => {
state.data = [];
state.loading = 'idle';
};
import { SIZE_OF_EMPTY_ARRAY } from '@/constants/common';
import { rootSelector } from '@/redux/root/root.selectors';
import { createSelector } from '@reduxjs/toolkit';
import { MultiSearchData } from '@/types/fetchDataState';
import { Drug } from '@/types/models';
import { BioEntity, Drug } from '@/types/models';
import { createSelector } from '@reduxjs/toolkit';
import { currentSelectedSearchElement } from '../drawer/drawer.selectors';
import { currentModelIdSelector } from '../models/models.selectors';
export const drugsSelector = createSelector(rootSelector, state => state.drugs);
......@@ -29,3 +30,21 @@ export const numberOfDrugsSelector = createSelector(
return state.data.length && state?.data.map(e => e.targets.length)?.reduce((a, b) => a + b);
},
);
export const searchedDrugsBioEntitesOfCurrentMapSelector = createSelector(
drugsSelector,
currentSelectedSearchElement,
currentModelIdSelector,
(drugsState, currentSearchElement, currentModelId): BioEntity[] => {
return (drugsState?.data || [])
.filter(({ searchQueryElement }) =>
currentSearchElement ? searchQueryElement === currentSearchElement : true,
)
.map(({ data }) => data || [])
.flat()
.map(({ targets }) => targets.map(({ targetElements }) => targetElements))
.flat()
.flat()
.filter(bioEntity => bioEntity.model === currentModelId);
},
);
import { createSlice } from '@reduxjs/toolkit';
import { DrugsState } from '@/redux/drugs/drugs.types';
import { getDrugsReducer, getMultiDrugsReducer } from './drugs.reducers';
import { createSlice } from '@reduxjs/toolkit';
import { clearDrugsDataReducer, getDrugsReducer, getMultiDrugsReducer } from './drugs.reducers';
const initialState: DrugsState = {
data: [],
......@@ -11,11 +11,15 @@ const initialState: DrugsState = {
export const drugsSlice = createSlice({
name: 'drugs',
initialState,
reducers: {},
reducers: {
clearDrugsData: clearDrugsDataReducer,
},
extraReducers: builder => {
getDrugsReducer(builder);
getMultiDrugsReducer(builder);
},
});
export const { clearDrugsData } = drugsSlice.actions;
export default drugsSlice.reducer;
import { HttpStatusCode } from 'axios';
import { drugsFixture } from '@/models/fixtures/drugFixtures';
import { mockNetworkResponse } from '@/utils/mockNetworkResponse';
import { apiPath } from '@/redux/apiPath';
import {
ToolkitStoreWithSingleSlice,
createStoreInstanceUsingSliceReducer,
} from '@/utils/createStoreInstanceUsingSliceReducer';
import { apiPath } from '@/redux/apiPath';
import { getDrugs } from './drugs.thunks';
import { mockNetworkNewAPIResponse } from '@/utils/mockNetworkResponse';
import { HttpStatusCode } from 'axios';
import drugsReducer from './drugs.slice';
import { getDrugs } from './drugs.thunks';
import { DrugsState } from './drugs.types';
const mockedAxiosClient = mockNetworkResponse();
const mockedAxiosClient = mockNetworkNewAPIResponse();
const SEARCH_QUERY = 'aspirin';
describe('drugs thunks', () => {
......
import { z } from 'zod';
import { createAsyncThunk } from '@reduxjs/toolkit';
import { axiosInstance } from '@/services/api/utils/axiosInstance';
import { Drug } from '@/types/models';
import { drugSchema } from '@/models/drugSchema';
import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema';
import { apiPath } from '@/redux/apiPath';
import { axiosInstanceNewAPI } from '@/services/api/utils/axiosInstance';
import { Drug } from '@/types/models';
import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema';
import { createAsyncThunk } from '@reduxjs/toolkit';
import { z } from 'zod';
export const getDrugs = createAsyncThunk(
'project/getDrugs',
async (searchQuery: string): Promise<Drug[] | undefined> => {
const response = await axiosInstance.get<Drug[]>(apiPath.getDrugsStringWithQuery(searchQuery));
const response = await axiosInstanceNewAPI.get<Drug[]>(
apiPath.getDrugsStringWithQuery(searchQuery),
);
const isDataValid = validateDataUsingZodSchema(response.data, z.array(drugSchema));
......
import { SearchState } from './search.types';
export const SEARCH_INITIAL_STATE: SearchState = {
searchInput: '',
searchValue: [''],
perfectMatch: false,
loading: 'idle',
};
import { SearchState } from './search.types';
export const SEARCH_STATE_INITIAL_MOCK: SearchState = {
searchInput: '',
searchValue: [''],
perfectMatch: false,
loading: 'idle',
......
......@@ -9,6 +9,7 @@ import searchReducer, { setPerfectMatch } from './search.slice';
const SEARCH_QUERY = ['Corticosterone'];
const INITIAL_STATE: SearchState = {
searchInput: '',
searchValue: [''],
perfectMatch: false,
loading: 'idle',
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment