diff --git a/src/components/FunctionalArea/MapNavigation/MapNavigation.component.tsx b/src/components/FunctionalArea/MapNavigation/MapNavigation.component.tsx index c1d4d1206263c5392b481c06f990f482b9f62b16..c03fcf7f1f04dd141a61c6ed56a261676a0b362d 100644 --- a/src/components/FunctionalArea/MapNavigation/MapNavigation.component.tsx +++ b/src/components/FunctionalArea/MapNavigation/MapNavigation.component.tsx @@ -10,6 +10,9 @@ import { Button } from '@/shared/Button'; import { Icon } from '@/shared/Icon'; import { MouseEvent } from 'react'; import { twMerge } from 'tailwind-merge'; +import { getComments } from '@/redux/comment/thunks/getComments'; +import { commentSelector } from '@/redux/comment/comment.selectors'; +import { hideComments, showComments } from '@/redux/comment/comment.slice'; export const MapNavigation = (): JSX.Element => { const dispatch = useAppDispatch(); @@ -20,6 +23,8 @@ export const MapNavigation = (): JSX.Element => { const isActive = (modelId: number): boolean => currentModelId === modelId; const isNotMainMap = (modelName: string): boolean => modelName !== MAIN_MAP; + const commentsOpen = useAppSelector(commentSelector).isOpen; + const onCloseSubmap = (event: MouseEvent<HTMLDivElement>, map: OppenedMap): void => { event.stopPropagation(); if (isActive(map.modelId)) { @@ -45,27 +50,47 @@ export const MapNavigation = (): JSX.Element => { } }; + const toggleComments = async (): Promise<void> => { + if (!commentsOpen) { + await dispatch(getComments()); + dispatch(showComments()); + } else { + dispatch(hideComments()); + } + }; + return ( - <div className="flex h-10 w-full flex-row flex-nowrap justify-start overflow-y-auto bg-white-pearl text-xs shadow-primary"> - {openedMaps.map(map => ( + <div className="flex h-10 w-full flex-row flex-nowrap justify-between overflow-y-auto bg-white-pearl text-xs shadow-primary"> + <div className="flex flex-row items-center justify-start"> + {openedMaps.map(map => ( + <Button + key={map.modelId} + className={twMerge( + 'relative h-10 whitespace-nowrap', + isActive(map.modelId) ? 'bg-[#EBF4FF]' : 'font-normal', + )} + variantStyles={isActive(map.modelId) ? 'secondary' : 'ghost'} + onClick={(): void => onSubmapTabClick(map)} + > + {map.modelName} + {isNotMainMap(map.modelName) && ( + // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions + <div onClick={(event): void => onCloseSubmap(event, map)} data-testid="close-icon"> + <Icon name="close" className="ml-3 h-5 w-5 fill-font-400" /> + </div> + )} + </Button> + ))} + </div> + <div className="flex items-center"> <Button - key={map.modelId} - className={twMerge( - 'h-10 whitespace-nowrap', - isActive(map.modelId) ? 'bg-[#EBF4FF]' : 'font-normal', - )} - variantStyles={isActive(map.modelId) ? 'secondary' : 'ghost'} - onClick={(): void => onSubmapTabClick(map)} + className="mx-4 flex-none" + variantStyles="secondary" + onClick={() => toggleComments()} > - {map.modelName} - {isNotMainMap(map.modelName) && ( - // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions - <div onClick={(event): void => onCloseSubmap(event, map)} data-testid="close-icon"> - <Icon name="close" className="ml-3 h-5 w-5 fill-font-400" /> - </div> - )} + {commentsOpen ? 'Hide Comments' : 'Show Comments'} </Button> - ))} + </div> </div> ); }; diff --git a/src/components/Map/Drawer/SearchDrawerWrapper/ResultsList/PinsList/PinsList.component.tsx b/src/components/Map/Drawer/SearchDrawerWrapper/ResultsList/PinsList/PinsList.component.tsx index 9f0b9e949d480487a32fbc560bb64fcc722e05cd..d612aae39c45cc2e5bd3bd258ab2e9a7775b2d4f 100644 --- a/src/components/Map/Drawer/SearchDrawerWrapper/ResultsList/PinsList/PinsList.component.tsx +++ b/src/components/Map/Drawer/SearchDrawerWrapper/ResultsList/PinsList/PinsList.component.tsx @@ -39,6 +39,8 @@ export const PinsList = ({ pinsList, type }: PinsListProps): JSX.Element => { } case 'bioEntity': return <div />; + case 'comment': + return <div />; case 'none': return <div />; default: diff --git a/src/components/Map/Drawer/SearchDrawerWrapper/ResultsList/PinsList/PinsListItem/PinsListItem.component.utils.ts b/src/components/Map/Drawer/SearchDrawerWrapper/ResultsList/PinsList/PinsListItem/PinsListItem.component.utils.ts index f0775c25e6de370a0e0ace771cf6729bff5f4658..bbc87ce8971d920fe9086d6eb0cc88ee5ce7f3e8 100644 --- a/src/components/Map/Drawer/SearchDrawerWrapper/ResultsList/PinsList/PinsListItem/PinsListItem.component.utils.ts +++ b/src/components/Map/Drawer/SearchDrawerWrapper/ResultsList/PinsList/PinsListItem/PinsListItem.component.utils.ts @@ -8,6 +8,7 @@ export const getPinColor = (type: PinTypeWithNone): string => { bioEntity: 'fill-primary-500', drugs: 'fill-orange', chemicals: 'fill-purple', + comment: 'fill-blue', none: 'none', }; diff --git a/src/components/Map/MapViewer/utils/config/commentsLayer/getCommentsFeatures.test.ts b/src/components/Map/MapViewer/utils/config/commentsLayer/getCommentsFeatures.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..6a76072960afb50b534f5d194b2cbf0c5e901450 --- /dev/null +++ b/src/components/Map/MapViewer/utils/config/commentsLayer/getCommentsFeatures.test.ts @@ -0,0 +1,45 @@ +import { initialMapStateFixture } from '@/redux/map/map.fixtures'; +import { PinType } from '@/types/pin'; +import { UsePointToProjectionResult, usePointToProjection } from '@/utils/map/usePointToProjection'; +import { + GetReduxWrapperUsingSliceReducer, + getReduxWrapperWithStore, +} from '@/utils/testing/getReduxWrapperWithStore'; +import { renderHook } from '@testing-library/react'; +import { Feature } from 'ol'; +import Style from 'ol/style/Style'; +import { commentsFixture } from '@/models/fixtures/commentsFixture'; +import { getCommentsFeatures } from '@/components/Map/MapViewer/utils/config/commentsLayer/getCommentsFeatures'; + +const getPointToProjection = ( + wrapper: ReturnType<GetReduxWrapperUsingSliceReducer>['Wrapper'], +): UsePointToProjectionResult => { + const { result: usePointToProjectionHook } = renderHook(() => usePointToProjection(), { + wrapper, + }); + + return usePointToProjectionHook.current; +}; + +describe('getCommentsFeatures', () => { + const { Wrapper } = getReduxWrapperWithStore({ + map: initialMapStateFixture, + }); + const comments = commentsFixture.map(comment => ({ + ...comment, + pinType: 'comment' as PinType, + })); + + const pointToProjection = getPointToProjection(Wrapper); + + it('should return array of instances of Feature with Style', () => { + const result = getCommentsFeatures(comments, { + pointToProjection, + }); + + result.forEach(resultElement => { + expect(resultElement).toBeInstanceOf(Feature); + expect(resultElement.getStyle()).toBeInstanceOf(Style); + }); + }); +}); diff --git a/src/components/Map/MapViewer/utils/config/commentsLayer/getCommentsFeatures.ts b/src/components/Map/MapViewer/utils/config/commentsLayer/getCommentsFeatures.ts new file mode 100644 index 0000000000000000000000000000000000000000..f1e1a82c25bb1108db56c25703f06786da436477 --- /dev/null +++ b/src/components/Map/MapViewer/utils/config/commentsLayer/getCommentsFeatures.ts @@ -0,0 +1,56 @@ +import { UsePointToProjectionResult } from '@/utils/map/usePointToProjection'; +import { Feature } from 'ol'; +import { CommentWithPinType } from '@/types/comment'; +import { getPinFeature } from '@/components/Map/MapViewer/utils/config/pinsLayer/getPinFeature'; +import { PinType } from '@/types/pin'; +import { PINS_COLORS, TEXT_COLOR } from '@/constants/canvas'; +import { getPinStyle } from '@/components/Map/MapViewer/utils/config/pinsLayer/getPinStyle'; + +export const getCommentFeature = ( + comment: CommentWithPinType, + { + pointToProjection, + type, + value, + }: { + pointToProjection: UsePointToProjectionResult; + type: PinType; + value: number; + }, +): Feature => { + const color = PINS_COLORS[type]; + + const textColor = TEXT_COLOR; + + const feature = getPinFeature( + { + x: comment.coord.x, + height: 0, + id: `comment_${comment.id}`, + width: 0, + y: comment.coord.y, + }, + pointToProjection, + ); + const style = getPinStyle({ + color, + value, + textColor, + }); + + feature.setStyle(style); + return feature; +}; + +export const getCommentsFeatures = ( + comments: CommentWithPinType[], + { + pointToProjection, + }: { + pointToProjection: UsePointToProjectionResult; + }, +): Feature[] => { + return comments.map(comment => + getCommentFeature(comment, { pointToProjection, type: comment.pinType, value: 7 }), + ); +}; diff --git a/src/components/Map/MapViewer/utils/config/commentsLayer/useOlMapCommentsLayer.ts b/src/components/Map/MapViewer/utils/config/commentsLayer/useOlMapCommentsLayer.ts new file mode 100644 index 0000000000000000000000000000000000000000..6eb9e2cbd4706836bcbb812e235b7a937d15d32c --- /dev/null +++ b/src/components/Map/MapViewer/utils/config/commentsLayer/useOlMapCommentsLayer.ts @@ -0,0 +1,45 @@ +/* eslint-disable no-magic-numbers */ +import { usePointToProjection } from '@/utils/map/usePointToProjection'; +import Feature from 'ol/Feature'; +import { Geometry } from 'ol/geom'; +import VectorLayer from 'ol/layer/Vector'; +import VectorSource from 'ol/source/Vector'; +import { useSelector } from 'react-redux'; +import { + allCommentsSelectorOfCurrentMap, + commentSelector, +} from '@/redux/comment/comment.selectors'; +import { getCommentsFeatures } from '@/components/Map/MapViewer/utils/config/commentsLayer/getCommentsFeatures'; +import { useMemo } from 'react'; + +export const useOlMapCommentsLayer = (): VectorLayer<VectorSource<Feature<Geometry>>> => { + const pointToProjection = usePointToProjection(); + const comments = useSelector(allCommentsSelectorOfCurrentMap); + const isVisible = useSelector(commentSelector).isOpen; + + const elementsFeatures = useMemo( + () => + [ + getCommentsFeatures(isVisible ? comments : [], { + pointToProjection, + }), + ].flat(), + [comments, pointToProjection, isVisible], + ); + + const vectorSource = useMemo(() => { + return new VectorSource({ + features: [...elementsFeatures], + }); + }, [elementsFeatures]); + + const pinsLayer = useMemo( + () => + new VectorLayer({ + source: vectorSource, + }), + [vectorSource], + ); + + return pinsLayer; +}; diff --git a/src/components/Map/MapViewer/utils/config/useOlMapLayers.ts b/src/components/Map/MapViewer/utils/config/useOlMapLayers.ts index a42e967759c12b43c1ce2da7158b4a2641a2620d..5739c30201df49638f2e1656189c0ab561886450 100644 --- a/src/components/Map/MapViewer/utils/config/useOlMapLayers.ts +++ b/src/components/Map/MapViewer/utils/config/useOlMapLayers.ts @@ -1,6 +1,7 @@ /* eslint-disable no-magic-numbers */ import { MapInstance } from '@/types/map'; import { useEffect } from 'react'; +import { useOlMapCommentsLayer } from '@/components/Map/MapViewer/utils/config/commentsLayer/useOlMapCommentsLayer'; import { MapConfig } from '../../MapViewer.types'; import { useOlMapOverlaysLayer } from './overlaysLayer/useOlMapOverlaysLayer'; import { useOlMapPinsLayer } from './pinsLayer/useOlMapPinsLayer'; @@ -16,14 +17,15 @@ export const useOlMapLayers = ({ mapInstance }: UseOlMapLayersInput): MapConfig[ const pinsLayer = useOlMapPinsLayer(); const reactionsLayer = useOlMapReactionsLayer(); const overlaysLayer = useOlMapOverlaysLayer(); + const commentsLayer = useOlMapCommentsLayer(); useEffect(() => { if (!mapInstance) { return; } - mapInstance.setLayers([tileLayer, reactionsLayer, overlaysLayer, pinsLayer]); - }, [reactionsLayer, tileLayer, pinsLayer, mapInstance, overlaysLayer]); + mapInstance.setLayers([tileLayer, reactionsLayer, overlaysLayer, pinsLayer, commentsLayer]); + }, [reactionsLayer, tileLayer, pinsLayer, mapInstance, overlaysLayer, commentsLayer]); return [tileLayer, pinsLayer, reactionsLayer, overlaysLayer]; }; diff --git a/src/constants/canvas.ts b/src/constants/canvas.ts index 29931be199a20e245d7c54b6185d186b9e089839..e5ab204a5e6465bf226c874f336676ecc86637f8 100644 --- a/src/constants/canvas.ts +++ b/src/constants/canvas.ts @@ -16,6 +16,7 @@ export const PINS_COLORS: Record<PinType, string> = { drugs: '#F48C41', chemicals: '#008325', bioEntity: '#106AD7', + comment: '#106AD7', }; export const PINS_COLOR_WITH_NONE: Record<PinTypeWithNone, string> = { diff --git a/src/models/commentSchema.ts b/src/models/commentSchema.ts new file mode 100644 index 0000000000000000000000000000000000000000..191a682cfcd93ab1ed63ec7357d2c056011d1a10 --- /dev/null +++ b/src/models/commentSchema.ts @@ -0,0 +1,20 @@ +import { z } from 'zod'; + +const coordinatesSchema = z.object({ + x: z.number(), + y: z.number(), +}); + +export const commentSchema = z.object({ + title: z.string(), + icon: z.string(), + type: z.enum(['POINT', 'ALIAS', 'REACTION']), + content: z.string(), + removed: z.boolean(), + coord: coordinatesSchema, + modelId: z.number(), + elementId: z.string(), + id: z.number(), + pinned: z.boolean(), + owner: z.string().optional(), +}); diff --git a/src/models/fixtures/commentsFixture.ts b/src/models/fixtures/commentsFixture.ts new file mode 100644 index 0000000000000000000000000000000000000000..7b06271447f126d390a443f5f9537f6dcbed26ea --- /dev/null +++ b/src/models/fixtures/commentsFixture.ts @@ -0,0 +1,10 @@ +import { ZOD_SEED } from '@/constants'; +import { z } from 'zod'; +// eslint-disable-next-line import/no-extraneous-dependencies +import { createFixture } from 'zod-fixture'; +import { commentSchema } from '@/models/commentSchema'; + +export const commentsFixture = createFixture(z.array(commentSchema), { + seed: ZOD_SEED, + array: { min: 2, max: 10 }, +}); diff --git a/src/redux/apiPath.ts b/src/redux/apiPath.ts index 88149929bcef8afc5edfcd50700f51822f930206..cb9346911d7f088a8260c7913e34df6a24fa0b05 100644 --- a/src/redux/apiPath.ts +++ b/src/redux/apiPath.ts @@ -96,4 +96,6 @@ export const apiPath = { user: (login: string): string => `users/${login}`, getStacktrace: (code: string): string => `stacktrace/${code}`, submitError: (): string => `minervanet/submitError`, + userPrivileges: (login: string): string => `users/${login}?columns=privileges`, + getComments: (): string => `projects/${PROJECT_ID}/comments/models/*/`, }; diff --git a/src/redux/comment/comment.constants.ts b/src/redux/comment/comment.constants.ts new file mode 100644 index 0000000000000000000000000000000000000000..36914070334299cf7d027a2e225937c3a0f43d7d --- /dev/null +++ b/src/redux/comment/comment.constants.ts @@ -0,0 +1,15 @@ +import { FetchDataState } from '@/types/fetchDataState'; +import { CommentsState } from '@/redux/comment/comment.types'; + +export const COMMENT_SUBMAP_CONNECTIONS_INITIAL_STATE: FetchDataState<Comment[]> = { + data: [], + loading: 'idle', + error: { name: '', message: '' }, +}; + +export const COMMENT_INITIAL_STATE: CommentsState = { + data: [], + loading: 'idle', + error: { name: '', message: '' }, + isOpen: false, +}; diff --git a/src/redux/comment/comment.mock.ts b/src/redux/comment/comment.mock.ts new file mode 100644 index 0000000000000000000000000000000000000000..e84f898da65376f1950279ff87e2c19366b8b148 --- /dev/null +++ b/src/redux/comment/comment.mock.ts @@ -0,0 +1,9 @@ +import { DEFAULT_ERROR } from '@/constants/errors'; +import { CommentsState } from '@/redux/comment/comment.types'; + +export const COMMENT_INITIAL_STATE_MOCK: CommentsState = { + data: [], + loading: 'idle', + error: DEFAULT_ERROR, + isOpen: false, +}; diff --git a/src/redux/comment/comment.reducers.ts b/src/redux/comment/comment.reducers.ts new file mode 100644 index 0000000000000000000000000000000000000000..df94d01485e1418dd595333e2955a5a47c5466de --- /dev/null +++ b/src/redux/comment/comment.reducers.ts @@ -0,0 +1,26 @@ +import { ActionReducerMapBuilder } from '@reduxjs/toolkit'; +import { CommentsState } from '@/redux/comment/comment.types'; +import { getComments } from '@/redux/comment/thunks/getComments'; + +export const getCommentsReducer = (builder: ActionReducerMapBuilder<CommentsState>): void => { + builder.addCase(getComments.pending, state => { + state.loading = 'pending'; + }); + + builder.addCase(getComments.fulfilled, (state, action) => { + state.loading = 'succeeded'; + state.data = action.payload; + }); + + builder.addCase(getComments.rejected, state => { + state.loading = 'failed'; + }); +}; + +export const showCommentsReducer = (state: CommentsState): void => { + state.isOpen = true; +}; + +export const hideCommentsReducer = (state: CommentsState): void => { + state.isOpen = false; +}; diff --git a/src/redux/comment/comment.selectors.ts b/src/redux/comment/comment.selectors.ts new file mode 100644 index 0000000000000000000000000000000000000000..975e7139c52043c67234f2e5dc04306d41a4b6b7 --- /dev/null +++ b/src/redux/comment/comment.selectors.ts @@ -0,0 +1,25 @@ +import { rootSelector } from '@/redux/root/root.selectors'; +import { createSelector } from '@reduxjs/toolkit'; +import { CommentWithPinType } from '@/types/comment'; +import { currentModelIdSelector } from '../models/models.selectors'; + +export const commentSelector = createSelector(rootSelector, state => state.comment); + +export const allCommentsSelectorOfCurrentMap = createSelector( + commentSelector, + currentModelIdSelector, + (commentState, currentModelId): CommentWithPinType[] => { + if (!commentState) { + return []; + } + + return (commentState.data || []) + .filter(comment => comment.modelId === currentModelId) + .map(comment => { + return { + ...comment, + pinType: 'comment', + }; + }); + }, +); diff --git a/src/redux/comment/comment.slice.ts b/src/redux/comment/comment.slice.ts new file mode 100644 index 0000000000000000000000000000000000000000..2910a8727ddb8600ed1123571388441ef5a37cab --- /dev/null +++ b/src/redux/comment/comment.slice.ts @@ -0,0 +1,23 @@ +import { createSlice } from '@reduxjs/toolkit'; +import { COMMENT_INITIAL_STATE } from '@/redux/comment/comment.constants'; +import { + getCommentsReducer, + hideCommentsReducer, + showCommentsReducer, +} from '@/redux/comment/comment.reducers'; + +export const commentsSlice = createSlice({ + name: 'comments', + initialState: COMMENT_INITIAL_STATE, + reducers: { + showComments: showCommentsReducer, + hideComments: hideCommentsReducer, + }, + extraReducers: builder => { + getCommentsReducer(builder); + }, +}); + +export const { showComments, hideComments } = commentsSlice.actions; + +export default commentsSlice.reducer; diff --git a/src/redux/comment/comment.thunks.ts b/src/redux/comment/comment.thunks.ts new file mode 100644 index 0000000000000000000000000000000000000000..b8ee25a46581d5fb4701d81fc0c98c0a4df74e41 --- /dev/null +++ b/src/redux/comment/comment.thunks.ts @@ -0,0 +1,3 @@ +import { getComments } from './thunks/getComments'; + +export { getComments }; diff --git a/src/redux/comment/comment.types.ts b/src/redux/comment/comment.types.ts new file mode 100644 index 0000000000000000000000000000000000000000..b6389d9504f14cab6ff5c7d3feb9142eeb8a2d25 --- /dev/null +++ b/src/redux/comment/comment.types.ts @@ -0,0 +1,6 @@ +import { FetchDataState } from '@/types/fetchDataState'; +import { Comment } from '@/types/models'; + +export interface CommentsState extends FetchDataState<Comment[], []> { + isOpen: boolean; +} diff --git a/src/redux/comment/thunks/getComments.ts b/src/redux/comment/thunks/getComments.ts new file mode 100644 index 0000000000000000000000000000000000000000..5bc9013c661cc45207280e260897cc346d9f6b23 --- /dev/null +++ b/src/redux/comment/thunks/getComments.ts @@ -0,0 +1,23 @@ +import { commentSchema } from '@/models/commentSchema'; +import { apiPath } from '@/redux/apiPath'; +import { axiosInstance } from '@/services/api/utils/axiosInstance'; +import { ThunkConfig } from '@/types/store'; +import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema'; +import { createAsyncThunk } from '@reduxjs/toolkit'; +import { Comment } from '@/types/models'; +import { z } from 'zod'; + +export const getComments = createAsyncThunk<Comment[], void, ThunkConfig>( + 'project/getComments', + async () => { + try { + const response = await axiosInstance.get<Comment[]>(apiPath.getComments()); + + const isDataValid = validateDataUsingZodSchema(response.data, z.array(commentSchema)); + + return isDataValid ? response.data : []; + } catch (error) { + return Promise.reject(error); + } + }, +); diff --git a/src/redux/root/root.fixtures.ts b/src/redux/root/root.fixtures.ts index feaaaedffe924b2b295a0e3d2d9590e85e137df0..9c5e6df4433c43af3513df7ed427b22681695bc5 100644 --- a/src/redux/root/root.fixtures.ts +++ b/src/redux/root/root.fixtures.ts @@ -1,4 +1,5 @@ import { CONSTANT_INITIAL_STATE } from '@/redux/constant/constant.adapter'; +import { COMMENT_INITIAL_STATE_MOCK } from '@/redux/comment/comment.mock'; import { BACKGROUND_INITIAL_STATE_MOCK } from '../backgrounds/background.mock'; import { BIOENTITY_INITIAL_STATE_MOCK } from '../bioEntity/bioEntity.mock'; import { CHEMICALS_INITIAL_STATE_MOCK } from '../chemicals/chemicals.mock'; @@ -53,4 +54,5 @@ export const INITIAL_STORE_STATE_MOCK: RootState = { plugins: PLUGINS_INITIAL_STATE_MOCK, markers: MARKERS_INITIAL_STATE_MOCK, entityNumber: ENTITY_NUMBER_INITIAL_STATE_MOCK, + comment: COMMENT_INITIAL_STATE_MOCK, }; diff --git a/src/redux/store.ts b/src/redux/store.ts index 891c7cfec500361e496e7d2d53b534f4a79f1bfe..3e957717d60ad91e097b46beca519bbc8f3dab33 100644 --- a/src/redux/store.ts +++ b/src/redux/store.ts @@ -23,6 +23,7 @@ import { TypedStartListening, configureStore, } from '@reduxjs/toolkit'; +import commentReducer from '@/redux/comment/comment.slice'; import compartmentPathwaysReducer from './compartmentPathways/compartmentPathways.slice'; import entityNumberReducer from './entityNumber/entityNumber.slice'; import exportReducer from './export/export.slice'; @@ -40,6 +41,7 @@ export const reducers = { drugs: drugsReducer, chemicals: chemicalsReducer, bioEntity: bioEntityReducer, + comment: commentReducer, drawer: drawerReducer, modal: modalReducer, map: mapReducer, diff --git a/src/types/comment.ts b/src/types/comment.ts new file mode 100644 index 0000000000000000000000000000000000000000..f42abd2f28d347c4b3c352a9b731e47e6db2f6dc --- /dev/null +++ b/src/types/comment.ts @@ -0,0 +1,6 @@ +import { Comment } from './models'; +import { PinType } from './pin'; + +export interface CommentWithPinType extends Comment { + pinType: PinType; +} diff --git a/src/types/models.ts b/src/types/models.ts index 57a80ea1f2197c7218258246eeca684f677cf702..53d477a7f88a2dcb40b451db206236aa3e9fd86d 100644 --- a/src/types/models.ts +++ b/src/types/models.ts @@ -61,6 +61,7 @@ import { targetSchema } from '@/models/targetSchema'; import { targetSearchNameResult } from '@/models/targetSearchNameResult'; import { userPrivilegeSchema } from '@/models/userPrivilegesSchema'; import { z } from 'zod'; +import { commentSchema } from '@/models/commentSchema'; import { userSchema } from '@/models/userSchema'; import { javaStacktraceSchema } from '@/models/javaStacktraceSchema'; @@ -122,3 +123,4 @@ export type MarkerLine = z.infer<typeof markerLineSchema>; export type MarkerWithPosition = z.infer<typeof markerWithPositionSchema>; export type Marker = z.infer<typeof markerSchema>; export type JavaStacktrace = z.infer<typeof javaStacktraceSchema>; +export type Comment = z.infer<typeof commentSchema>; diff --git a/src/types/pin.ts b/src/types/pin.ts index ffb2ab266d0d59f35c35896c30edf39c145930b8..7310215f63f09860d9f84880f2fbf024063adff0 100644 --- a/src/types/pin.ts +++ b/src/types/pin.ts @@ -1 +1 @@ -export type PinType = 'chemicals' | 'drugs' | 'bioEntity'; +export type PinType = 'chemicals' | 'drugs' | 'bioEntity' | 'comment'; diff --git a/src/utils/validateDataUsingZodSchema.ts b/src/utils/validateDataUsingZodSchema.ts index a7b0cf0858cc2e62842047cfea56bb5066324457..673e253dcbbd2f25ca32612d2e67393adf729e26 100644 --- a/src/utils/validateDataUsingZodSchema.ts +++ b/src/utils/validateDataUsingZodSchema.ts @@ -8,7 +8,7 @@ export const validateDataUsingZodSchema: IsApiResponseValid = (data, schema: Zod if (validationResults.success === false) { // TODO - probably need to rething way of handling parsing errors, for now let's leave it to console.log // eslint-disable-next-line no-console - console.error('Error on parsing data', validationResults.error); + console.error('Error on parsing data', validationResults.error.message); } return validationResults.success;