From 5eb12f4634dd6de80c35d04d84c8aec1d1510739 Mon Sep 17 00:00:00 2001 From: Piotr Gawron <p.gawron@atcomp.pl> Date: Wed, 5 Feb 2025 16:47:14 +0100 Subject: [PATCH] adding group overlay added --- .../Map/Drawer/Drawer.component.tsx | 2 + .../OverlaysGroupDrawer.component.tsx | 47 ++++++++++++++++ .../hooks/useOverlayGroupForm.ts | 40 ++++++++++++++ .../Map/Drawer/OverlayGroupDrawer/index.ts | 1 + .../UserOverlays/UserOverlays.component.tsx | 11 +++- src/redux/apiPath.ts | 1 + src/redux/drawer/drawer.reducers.ts | 6 +++ src/redux/drawer/drawer.slice.ts | 3 ++ .../overlayGroup/overlayGroup.selectors.ts | 2 + src/redux/overlayGroup/overlayGroup.thunks.ts | 53 ++++++++++++++++++- src/redux/root/init.thunks.ts | 2 + src/types/drawerName.ts | 1 + 12 files changed, 167 insertions(+), 2 deletions(-) create mode 100644 src/components/Map/Drawer/OverlayGroupDrawer/OverlaysGroupDrawer.component.tsx create mode 100644 src/components/Map/Drawer/OverlayGroupDrawer/hooks/useOverlayGroupForm.ts create mode 100644 src/components/Map/Drawer/OverlayGroupDrawer/index.ts diff --git a/src/components/Map/Drawer/Drawer.component.tsx b/src/components/Map/Drawer/Drawer.component.tsx index b18e949f..de0f9d35 100644 --- a/src/components/Map/Drawer/Drawer.component.tsx +++ b/src/components/Map/Drawer/Drawer.component.tsx @@ -4,6 +4,7 @@ import { useAppSelector } from '@/redux/hooks/useAppSelector'; import { twMerge } from 'tailwind-merge'; import { CommentDrawer } from '@/components/Map/Drawer/CommentDrawer'; import { LayersDrawer } from '@/components/Map/Drawer/LayersDrawer/LayersDrawer.component'; +import { OverlayGroupDrawer } from '@/components/Map/Drawer/OverlayGroupDrawer'; import { AvailablePluginsDrawer } from './AvailablePluginsDrawer'; import { BioEntityDrawer } from './BioEntityDrawer/BioEntityDrawer.component'; import { ExportDrawer } from './ExportDrawer'; @@ -29,6 +30,7 @@ export const Drawer = (): JSX.Element => { {isOpen && drawerName === 'reaction' && <ReactionDrawer />} {isOpen && drawerName === 'overlays' && <OverlaysDrawer />} {isOpen && drawerName === 'bio-entity' && <BioEntityDrawer />} + {isOpen && drawerName === 'overlay-group' && <OverlayGroupDrawer />} {isOpen && drawerName === 'project-info' && <ProjectInfoDrawer />} {isOpen && drawerName === 'export' && <ExportDrawer />} {isOpen && drawerName === 'available-plugins' && <AvailablePluginsDrawer />} diff --git a/src/components/Map/Drawer/OverlayGroupDrawer/OverlaysGroupDrawer.component.tsx b/src/components/Map/Drawer/OverlayGroupDrawer/OverlaysGroupDrawer.component.tsx new file mode 100644 index 00000000..43188a92 --- /dev/null +++ b/src/components/Map/Drawer/OverlayGroupDrawer/OverlaysGroupDrawer.component.tsx @@ -0,0 +1,47 @@ +import { DrawerHeadingBackwardButton } from '@/shared/DrawerHeadingBackwardButton'; +import { Input } from '@/shared/Input'; +import { Button } from '@/shared/Button'; +import { openOverlaysDrawer } from '@/redux/drawer/drawer.slice'; +import { useAppDispatch } from '@/redux/hooks/useAppDispatch'; +import { useOverlayGroupForm } from '@/components/Map/Drawer/OverlayGroupDrawer/hooks/useOverlayGroupForm'; + +export const OverlayGroupDrawer = (): JSX.Element => { + const dispatch = useAppDispatch(); + const navigateToOverlays = (): void => { + dispatch(openOverlaysDrawer()); + }; + + const { name, handleChangeName, handleSubmit } = useOverlayGroupForm(); + + return ( + <> + <DrawerHeadingBackwardButton backwardFunction={navigateToOverlays}> + Add overlay group + </DrawerHeadingBackwardButton> + <form className="flex h-[calc(100%-93px)] max-h-[calc(100%-93px)] flex-col overflow-y-auto p-6"> + <label className="mb-2.5 text-sm" htmlFor="name"> + Name + <Input + type="text" + name="name" + id="name" + data-testid="overlay-name" + value={name} + onChange={handleChangeName} + placeholder="Fancy group name" + sizeVariant="medium" + className="mt-2.5 text-xs" + /> + </label> + + <Button + className="mt-2.5 items-center justify-center self-start" + onClick={handleSubmit} + aria-label="add overlay" + > + Add + </Button> + </form> + </> + ); +}; diff --git a/src/components/Map/Drawer/OverlayGroupDrawer/hooks/useOverlayGroupForm.ts b/src/components/Map/Drawer/OverlayGroupDrawer/hooks/useOverlayGroupForm.ts new file mode 100644 index 00000000..28b86724 --- /dev/null +++ b/src/components/Map/Drawer/OverlayGroupDrawer/hooks/useOverlayGroupForm.ts @@ -0,0 +1,40 @@ +import { useState, ChangeEvent } from 'react'; +import { useAppSelector } from '@/redux/hooks/useAppSelector'; +import { useAppDispatch } from '@/redux/hooks/useAppDispatch'; +import { overlayGroupsSelector } from '@/redux/overlayGroup/overlayGroup.selectors'; +import { addOverlayGroup } from '@/redux/overlayGroup/overlayGroup.thunks'; + +type ReturnType = { + name: string; + handleChangeName: (e: ChangeEvent<HTMLInputElement>) => void; + handleSubmit: () => Promise<void>; +}; + +export const useOverlayGroupForm = (): ReturnType => { + const dispatch = useAppDispatch(); + const overlayGroups = useAppSelector(overlayGroupsSelector); + + const [name, setName] = useState(''); + + const handleChangeName = (e: ChangeEvent<HTMLInputElement>): void => { + setName(e.target.value); + }; + const handleSubmit = async (): Promise<void> => { + if (!name) return; + + dispatch( + addOverlayGroup({ + name, + order: overlayGroups.length, + }), + ); + + setName(''); + }; + + return { + name, + handleChangeName, + handleSubmit, + }; +}; diff --git a/src/components/Map/Drawer/OverlayGroupDrawer/index.ts b/src/components/Map/Drawer/OverlayGroupDrawer/index.ts new file mode 100644 index 00000000..5859600f --- /dev/null +++ b/src/components/Map/Drawer/OverlayGroupDrawer/index.ts @@ -0,0 +1 @@ +export { OverlayGroupDrawer } from './OverlaysGroupDrawer.component'; diff --git a/src/components/Map/Drawer/OverlaysDrawer/UserOverlays/UserOverlays.component.tsx b/src/components/Map/Drawer/OverlaysDrawer/UserOverlays/UserOverlays.component.tsx index 60d7cb01..99b57046 100644 --- a/src/components/Map/Drawer/OverlaysDrawer/UserOverlays/UserOverlays.component.tsx +++ b/src/components/Map/Drawer/OverlaysDrawer/UserOverlays/UserOverlays.component.tsx @@ -1,4 +1,7 @@ -import { displayAddOverlaysDrawer } from '@/redux/drawer/drawer.slice'; +import { + displayAddOverlayGroupDrawer, + displayAddOverlaysDrawer, +} from '@/redux/drawer/drawer.slice'; import { useAppDispatch } from '@/redux/hooks/useAppDispatch'; import { useAppSelector } from '@/redux/hooks/useAppSelector'; import { authenticatedUserSelector, loadingUserSelector } from '@/redux/user/user.selectors'; @@ -17,6 +20,9 @@ export const UserOverlays = (): JSX.Element => { const handleAddOverlay = (): void => { dispatch(displayAddOverlaysDrawer()); }; + const handleAddOverlayGroup = (): void => { + dispatch(displayAddOverlayGroupDrawer()); + }; return ( <div className="py-6"> @@ -35,6 +41,9 @@ export const UserOverlays = (): JSX.Element => { <> <div className="flex items-center justify-between px-6"> <p className="font-semibold">User provided overlays:</p> + <Button onClick={handleAddOverlayGroup} aria-label="add overlay group button"> + Add group + </Button> <Button onClick={handleAddOverlay} aria-label="add overlay button"> Add overlay </Button> diff --git a/src/redux/apiPath.ts b/src/redux/apiPath.ts index 4457eee6..d14f7fc5 100644 --- a/src/redux/apiPath.ts +++ b/src/redux/apiPath.ts @@ -135,4 +135,5 @@ export const apiPath = { getDrugAutocomplete: (): string => `projects/${PROJECT_ID}/drugs/suggestedQueryList`, getChemicalAutocomplete: (): string => `projects/${PROJECT_ID}/chemicals/suggestedQueryList`, getOverlayGroups: (): string => `projects/${PROJECT_ID}/overlay_groups/`, + addOverlayGroup: (): string => `projects/${PROJECT_ID}/overlay_groups/`, }; diff --git a/src/redux/drawer/drawer.reducers.ts b/src/redux/drawer/drawer.reducers.ts index 88f1096c..156866b2 100644 --- a/src/redux/drawer/drawer.reducers.ts +++ b/src/redux/drawer/drawer.reducers.ts @@ -45,6 +45,12 @@ export const displayAddOverlaysDrawerReducer = (state: DrawerState): void => { state.overlayDrawerState.currentStep = STEP.SECOND; }; +export const displayAddOverlayGroupDrawerReducer = (state: DrawerState): void => { + state.isOpen = true; + state.drawerName = 'overlay-group'; + state.overlayDrawerState.currentStep = STEP.FIRST; +}; + export const selectTabReducer = ( state: DrawerState, action: OpenSearchDrawerWithSelectedTabReducerAction, diff --git a/src/redux/drawer/drawer.slice.ts b/src/redux/drawer/drawer.slice.ts index ddb7c23e..fe752832 100644 --- a/src/redux/drawer/drawer.slice.ts +++ b/src/redux/drawer/drawer.slice.ts @@ -2,6 +2,7 @@ import { createSlice } from '@reduxjs/toolkit'; import { DRAWER_INITIAL_STATE } from './drawer.constants'; import { closeDrawerReducer, + displayAddOverlayGroupDrawerReducer, displayAddOverlaysDrawerReducer, displayBioEntitiesListReducer, displayChemicalsListReducer, @@ -29,6 +30,7 @@ const drawerSlice = createSlice({ openSubmapsDrawer: openSubmapsDrawerReducer, openOverlaysDrawer: openOverlaysDrawerReducer, displayAddOverlaysDrawer: displayAddOverlaysDrawerReducer, + displayAddOverlayGroupDrawer: displayAddOverlayGroupDrawerReducer, selectTab: selectTabReducer, closeDrawer: closeDrawerReducer, displayDrugsList: displayDrugsListReducer, @@ -52,6 +54,7 @@ export const { openSubmapsDrawer, openOverlaysDrawer, displayAddOverlaysDrawer, + displayAddOverlayGroupDrawer, selectTab, closeDrawer, displayDrugsList, diff --git a/src/redux/overlayGroup/overlayGroup.selectors.ts b/src/redux/overlayGroup/overlayGroup.selectors.ts index bd26e08f..8c2831ee 100644 --- a/src/redux/overlayGroup/overlayGroup.selectors.ts +++ b/src/redux/overlayGroup/overlayGroup.selectors.ts @@ -10,5 +10,7 @@ export const overlayGroupsSelector = createSelector(overlayGroupSelector, overla if (overlayGroup?.data) { result = result.concat(overlayGroup?.data); } + // eslint-disable-next-line no-console + console.log(result); return result; }); diff --git a/src/redux/overlayGroup/overlayGroup.thunks.ts b/src/redux/overlayGroup/overlayGroup.thunks.ts index aed7d0a0..711bb8ef 100644 --- a/src/redux/overlayGroup/overlayGroup.thunks.ts +++ b/src/redux/overlayGroup/overlayGroup.thunks.ts @@ -1,11 +1,13 @@ import { axiosInstanceNewAPI } from '@/services/api/utils/axiosInstance'; -import { OverlayGroup, PageOf } from '@/types/models'; +import { MapOverlay, OverlayGroup, PageOf } from '@/types/models'; import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema'; import { createAsyncThunk } from '@reduxjs/toolkit'; import { ThunkConfig } from '@/types/store'; import { getError } from '@/utils/error-report/getError'; import { pageableSchema } from '@/models/pageableSchema'; import { overlayGroupSchema } from '@/models/overlayGroupSchema'; +import { showToast } from '@/utils/showToast'; +import axios from 'axios'; import { apiPath } from '../apiPath'; export const getOverlayGroups = createAsyncThunk<OverlayGroup[], void, ThunkConfig>( @@ -14,6 +16,9 @@ export const getOverlayGroups = createAsyncThunk<OverlayGroup[], void, ThunkConf try { const response = await axiosInstanceNewAPI.get<PageOf<OverlayGroup>>( apiPath.getOverlayGroups(), + { + withCredentials: true, + }, ); const isDataValid = validateDataUsingZodSchema( @@ -27,3 +32,49 @@ export const getOverlayGroups = createAsyncThunk<OverlayGroup[], void, ThunkConf } }, ); + +type AddOverlayGroupArgs = { + name: string; + order: number; +}; + +export const addOverlayGroup = createAsyncThunk<undefined, AddOverlayGroupArgs, ThunkConfig>( + 'overlays/addOverlayGroup', + async ( + { name, order }, + { dispatch }, + // eslint-disable-next-line consistent-return + ) => { + try { + const response = await axiosInstanceNewAPI.post<MapOverlay>( + apiPath.addOverlayGroup(), + { + name, + order, + }, + { + withCredentials: true, + }, + ); + + const isDataValid = validateDataUsingZodSchema(response.data, overlayGroupSchema); + if (!isDataValid) { + showToast({ + type: 'error', + message: 'Problem with adding group encountered', + duration: 120000, + }); + } else { + showToast({ type: 'success', message: 'Overlay group added successfully' }); + } + await dispatch(getOverlayGroups()); + } catch (error) { + if (axios.isAxiosError(error) && error.code === 'ERR_BAD_REQUEST') { + const data = error.response?.data; + showToast({ type: 'error', message: data.reason, duration: 120000 }); + } else { + return Promise.reject(getError({ error, prefix: 'Failed to add overlay group' })); + } + } + }, +); diff --git a/src/redux/root/init.thunks.ts b/src/redux/root/init.thunks.ts index 4514bd87..bc082c8e 100644 --- a/src/redux/root/init.thunks.ts +++ b/src/redux/root/init.thunks.ts @@ -23,6 +23,7 @@ import { } from '@/components/FunctionalArea/CookieBanner/CookieBanner.constants'; import { injectMatomoTracking } from '@/utils/injectMatomoTracking'; import { getGlyphs } from '@/redux/glyphs/glyphs.thunks'; +import { getOverlayGroups } from '@/redux/overlayGroup/overlayGroup.thunks'; import { getConfiguration, getConfigurationOptions } from '../configuration/configuration.thunks'; import { initMapBackground, @@ -145,6 +146,7 @@ export const fetchInitialAppData = createAsyncThunk< } await dispatch(getAllUserOverlaysByCreator()); + await dispatch(getOverlayGroups()); /** fetch overlays */ if (queryData.overlaysId) { dispatch(getInitOverlays({ overlaysId: queryData.overlaysId })); diff --git a/src/types/drawerName.ts b/src/types/drawerName.ts index 4a7f5114..36e1055b 100644 --- a/src/types/drawerName.ts +++ b/src/types/drawerName.ts @@ -8,6 +8,7 @@ export type DrawerName = | 'submaps' | 'reaction' | 'overlays' + | 'overlay-group' | 'bio-entity' | 'comment' | 'available-plugins' -- GitLab