Skip to content
Snippets Groups Projects
Commit 25d44f3e authored by Adrian Orłów's avatar Adrian Orłów :fire:
Browse files

Merge branch 'display-pins-of-categories' into 'development'

feat: add category pins rendering and global search input

See merge request !63
parents a0cb4e95 b2da20d0
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
Pipeline #82275 failed
Showing
with 155 additions and 63 deletions
......@@ -8,26 +8,18 @@ import {
} from '@/redux/search/search.selectors';
import { getSearchData } from '@/redux/search/search.thunks';
import Image from 'next/image';
import { ChangeEvent, KeyboardEvent, useEffect, useState } from 'react';
import { ChangeEvent, KeyboardEvent, useState } from 'react';
import { useSelector } from 'react-redux';
import { useRouter } from 'next/router';
import { getDefaultSearchTab, getSearchValuesArrayAndTrimToSeven } from './SearchBar.utils';
const ENTER_KEY_CODE = 'Enter';
export const SearchBar = (): JSX.Element => {
const [searchValue, setSearchValue] = useState<string>('');
const isPendingSearchStatus = useSelector(isPendingSearchStatusSelector);
const isDrawerOpen = useSelector(isDrawerOpenSelector);
const isPerfectMatch = useSelector(perfectMatchSelector);
const [searchValue, setSearchValue] = useState<string>('');
const dispatch = useAppDispatch();
const { query } = useRouter();
useEffect(() => {
if (!searchValue && query.searchValue) {
setSearchValue(String(query.searchValue));
}
}, [searchValue, query]);
const openSearchDrawerIfClosed = (defaultSearchTab: string): void => {
if (!isDrawerOpen) {
......@@ -35,8 +27,9 @@ export const SearchBar = (): JSX.Element => {
}
};
const onSearchChange = (event: ChangeEvent<HTMLInputElement>): void =>
const onSearchChange = (event: ChangeEvent<HTMLInputElement>): void => {
setSearchValue(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 => {
// Reset reactions list to prevent keeping the old selected reaction rendered
dispatch(resetReactionsData());
// Reset search data to prevent invalid filtering of the click-search ()
dispatch(clearSearchData());
// Reset old pins data
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 = {
searchValue: [''],
perfectMatch: false,
loading: 'idle',
};
......@@ -2,6 +2,7 @@
import { getSearchData } from '@/redux/search/search.thunks';
import { SearchState, SetPerfectMatchAction } from '@/redux/search/search.types';
import { ActionReducerMapBuilder } from '@reduxjs/toolkit';
import { SEARCH_INITIAL_STATE } from './search.constants';
export const getSearchDataReducer = (builder: ActionReducerMapBuilder<SearchState>): void => {
builder.addCase(getSearchData.pending, (state, action) => {
......@@ -21,3 +22,8 @@ export const getSearchDataReducer = (builder: ActionReducerMapBuilder<SearchStat
export const setPerfectMatchReducer = (state: SearchState, action: SetPerfectMatchAction): void => {
state.perfectMatch = action.payload;
};
export const clearSearchDataReducer = (state: SearchState): void => {
state.searchValue = SEARCH_INITIAL_STATE.searchValue;
state.loading = SEARCH_INITIAL_STATE.loading;
};
import { getSearchDataReducer, setPerfectMatchReducer } from '@/redux/search/search.reducers';
import { SearchState } from '@/redux/search/search.types';
import {
clearSearchDataReducer,
getSearchDataReducer,
setPerfectMatchReducer,
} from '@/redux/search/search.reducers';
import { createSlice } from '@reduxjs/toolkit';
const initialState: SearchState = {
searchValue: [''],
perfectMatch: false,
loading: 'idle',
};
import { SEARCH_INITIAL_STATE } from './search.constants';
export const searchSlice = createSlice({
name: 'search',
initialState,
initialState: SEARCH_INITIAL_STATE,
reducers: {
setPerfectMatch: setPerfectMatchReducer,
clearSearchData: clearSearchDataReducer,
},
extraReducers(builder) {
getSearchDataReducer(builder);
},
});
export const { setPerfectMatch } = searchSlice.actions;
export const { setPerfectMatch, clearSearchData } = searchSlice.actions;
export default searchSlice.reducer;
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