diff --git a/src/components/FunctionalArea/Modal/EditOverlayGroupModal/EditOverlayGroupModal.component.test.tsx b/src/components/FunctionalArea/Modal/EditOverlayGroupModal/EditOverlayGroupModal.component.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..e6a7c2630d38293914fe95a8946efdf6d5129ad4 --- /dev/null +++ b/src/components/FunctionalArea/Modal/EditOverlayGroupModal/EditOverlayGroupModal.component.test.tsx @@ -0,0 +1,297 @@ +import { render, screen, fireEvent } from '@testing-library/react'; +import { StoreType } from '@/redux/store'; +import { + InitialStoreState, + getReduxWrapperWithStore, +} from '@/utils/testing/getReduxWrapperWithStore'; +import { overlayFixture, overlaysPageFixture } from '@/models/fixtures/overlaysFixture'; +import { mockNetworkNewAPIResponse } from '@/utils/mockNetworkResponse'; +import { apiPath } from '@/redux/apiPath'; +import { HttpStatusCode } from 'axios'; +import { DEFAULT_ERROR } from '@/constants/errors'; +import { act } from 'react-dom/test-utils'; +import { OVERLAYS_INITIAL_STATE_MOCK } from '@/redux/overlays/overlays.mock'; +import { showToast } from '@/utils/showToast'; +import { overlayGroupFixture } from '@/models/fixtures/overlayGroupsFixture'; +import { Modal } from '../Modal.component'; + +const mockedAxiosNewClient = mockNetworkNewAPIResponse(); + +jest.mock('../../../../utils/showToast'); + +const renderComponent = (initialStoreState: InitialStoreState = {}): { store: StoreType } => { + const { Wrapper, store } = getReduxWrapperWithStore(initialStoreState); + + return ( + render( + <Wrapper> + <Modal /> + </Wrapper>, + ), + { + store, + } + ); +}; + +const overlayGroup = { + ...overlayGroupFixture, + id: 1, +}; +describe('EditOverlayModal - component', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + it('should render modal with correct data', () => { + renderComponent({ + modal: { + isOpen: true, + modalTitle: overlayFixture.name, + modalName: 'edit-overlay-group', + editOverlayState: overlayFixture, + editOverlayGroupState: overlayGroupFixture, + molArtState: {}, + overviewImagesState: {}, + errorReportState: {}, + layerFactoryState: { id: undefined }, + layerImageObjectFactoryState: undefined, + layerTextFactoryState: undefined, + }, + }); + + expect(screen.getByLabelText('Name')).toBeVisible(); + expect(screen.getByLabelText('Order')).toBeVisible(); + expect(screen.getByTestId('overlay-group-name')).toHaveValue(overlayGroupFixture.name); + expect(screen.getByTestId('overlay-group-order')).toHaveValue(`${overlayGroupFixture.order}`); + }); + it('should handle input change correctly', () => { + renderComponent({ + modal: { + isOpen: true, + modalTitle: overlayFixture.name, + modalName: 'edit-overlay-group', + editOverlayState: overlayFixture, + editOverlayGroupState: overlayGroup, + molArtState: {}, + overviewImagesState: {}, + errorReportState: {}, + layerFactoryState: { id: undefined }, + layerImageObjectFactoryState: undefined, + layerTextFactoryState: undefined, + }, + }); + + const overlayNameInput: HTMLInputElement = screen.getByTestId('overlay-group-name'); + const overlayOrderInput: HTMLInputElement = screen.getByTestId('overlay-group-order'); + + fireEvent.change(overlayNameInput, { target: { value: 'Test name' } }); + fireEvent.change(overlayOrderInput, { target: { value: `11` } }); + + expect(overlayNameInput.value).toBe('Test name'); + expect(overlayOrderInput.value).toBe('11'); + }); + it('should handle remove user overlay', async () => { + const { store } = renderComponent({ + user: { + authenticated: true, + loading: 'succeeded', + error: DEFAULT_ERROR, + login: 'test', + role: 'user', + userData: null, + token: null, + }, + modal: { + isOpen: true, + modalTitle: overlayFixture.name, + modalName: 'edit-overlay-group', + editOverlayState: overlayFixture, + editOverlayGroupState: overlayGroup, + molArtState: {}, + overviewImagesState: {}, + errorReportState: {}, + layerFactoryState: { id: undefined }, + layerImageObjectFactoryState: undefined, + layerTextFactoryState: undefined, + }, + overlays: OVERLAYS_INITIAL_STATE_MOCK, + }); + mockedAxiosNewClient + .onDelete(apiPath.removeOverlayGroup(overlayGroup.id)) + .reply(HttpStatusCode.Ok, {}); + + const page = { + ...overlaysPageFixture, + data: [], + }; + + mockedAxiosNewClient.onGet(apiPath.getOverlayGroups()).reply(HttpStatusCode.Ok, page); + + const removeButton = screen.getByTestId('remove-button'); + expect(removeButton).toBeVisible(); + await act(() => { + removeButton.click(); + }); + + const { loading } = store.getState().overlayGroups; + expect(loading).toBe('succeeded'); + expect(removeButton).not.toBeVisible(); + }); + it('should show toast after successful removing user overlay', async () => { + renderComponent({ + user: { + authenticated: true, + loading: 'succeeded', + error: DEFAULT_ERROR, + login: 'test', + role: 'user', + userData: null, + token: null, + }, + modal: { + isOpen: true, + modalTitle: overlayFixture.name, + modalName: 'edit-overlay-group', + editOverlayState: overlayFixture, + editOverlayGroupState: overlayGroup, + molArtState: {}, + overviewImagesState: {}, + errorReportState: {}, + layerFactoryState: { id: undefined }, + layerImageObjectFactoryState: undefined, + layerTextFactoryState: undefined, + }, + overlays: OVERLAYS_INITIAL_STATE_MOCK, + }); + mockedAxiosNewClient + .onDelete(apiPath.removeOverlayGroup(overlayGroup.id)) + .reply(HttpStatusCode.Ok, {}); + + const removeButton = screen.getByTestId('remove-button'); + expect(removeButton).toBeVisible(); + await act(() => { + removeButton.click(); + }); + + expect(showToast).toHaveBeenCalledWith({ + message: 'User overlay group removed successfully', + type: 'success', + }); + }); + it('should handle save edited user overlay', async () => { + const { store } = renderComponent({ + user: { + authenticated: true, + loading: 'succeeded', + error: DEFAULT_ERROR, + login: 'test', + role: 'user', + userData: null, + token: null, + }, + modal: { + isOpen: true, + modalTitle: overlayGroup.name, + modalName: 'edit-overlay-group', + editOverlayState: null, + editOverlayGroupState: overlayGroup, + molArtState: {}, + overviewImagesState: {}, + errorReportState: {}, + layerFactoryState: { id: undefined }, + layerImageObjectFactoryState: undefined, + layerTextFactoryState: undefined, + }, + overlays: OVERLAYS_INITIAL_STATE_MOCK, + }); + mockedAxiosNewClient + .onPut(apiPath.updateOverlay(overlayGroup.id)) + .reply(HttpStatusCode.Ok, overlayGroup); + + const page = { + ...overlaysPageFixture, + data: [overlayGroup], + }; + + mockedAxiosNewClient.onGet(apiPath.getOverlayGroups()).reply(HttpStatusCode.Ok, page); + + const saveButton = screen.getByTestId('save-button'); + expect(saveButton).toBeVisible(); + await act(() => { + saveButton.click(); + }); + + const { loading } = store.getState().overlayGroups; + expect(loading).toBe('succeeded'); + expect(saveButton).not.toBeVisible(); + }); + it('should show toast after successful editing user overlay', async () => { + renderComponent({ + user: { + authenticated: true, + loading: 'succeeded', + error: DEFAULT_ERROR, + login: 'test', + role: 'user', + userData: null, + token: null, + }, + modal: { + isOpen: true, + modalTitle: overlayGroup.name, + modalName: 'edit-overlay-group', + editOverlayState: null, + editOverlayGroupState: overlayGroup, + molArtState: {}, + overviewImagesState: {}, + errorReportState: {}, + layerFactoryState: { id: undefined }, + layerImageObjectFactoryState: undefined, + layerTextFactoryState: undefined, + }, + overlays: OVERLAYS_INITIAL_STATE_MOCK, + }); + mockedAxiosNewClient + .onPut(apiPath.updateOverlayGroup(overlayGroup.id)) + .reply(HttpStatusCode.Ok, overlayGroup); + + const saveButton = screen.getByTestId('save-button'); + expect(saveButton).toBeVisible(); + await act(() => { + saveButton.click(); + }); + + expect(showToast).toHaveBeenCalledWith({ + message: 'User overlay group updated successfully', + type: 'success', + }); + }); + + it('should handle cancel edit user overlay', async () => { + const { store } = renderComponent({ + modal: { + isOpen: true, + modalTitle: overlayFixture.name, + modalName: 'edit-overlay-group', + editOverlayState: overlayFixture, + editOverlayGroupState: overlayGroup, + molArtState: {}, + overviewImagesState: {}, + errorReportState: {}, + layerFactoryState: { id: undefined }, + layerImageObjectFactoryState: undefined, + layerTextFactoryState: undefined, + }, + }); + + const cancelButton = screen.getByTestId('cancel-button'); + expect(cancelButton).toBeVisible(); + await act(() => { + cancelButton.click(); + }); + + const { isOpen } = store.getState().modal; + expect(isOpen).toBe(false); + expect(cancelButton).not.toBeVisible(); + }); +}); diff --git a/src/components/FunctionalArea/Modal/EditOverlayGroupModal/EditOverlayGroupModal.component.tsx b/src/components/FunctionalArea/Modal/EditOverlayGroupModal/EditOverlayGroupModal.component.tsx new file mode 100644 index 0000000000000000000000000000000000000000..f339e89d33efac424758013cfbc4a7b2d9e9fbdc --- /dev/null +++ b/src/components/FunctionalArea/Modal/EditOverlayGroupModal/EditOverlayGroupModal.component.tsx @@ -0,0 +1,76 @@ +import { Button } from '@/shared/Button'; +import { Input } from '@/shared/Input'; +import React from 'react'; +import { useEditOverlayGroup } from './hooks/useEditOverlayGroup'; + +export const EditOverlayGroupModal = (): React.ReactNode => { + const { + name, + order, + handleCancelEdit, + handleNameChange, + handleOrderChange, + handleRemoveOverlayGroup, + handleSaveEditedOverlayGroup, + } = useEditOverlayGroup(); + + return ( + <div className="w-full border border-t-[#E1E0E6] bg-white p-[24px]"> + <form> + <label className="text-sm font-semibold" htmlFor="overlayGroupName"> + Name + <Input + type="text" + value={name} + onChange={handleNameChange} + className="mt-2.5 text-sm font-medium" + data-testid="overlay-group-name" + name="overlayGroupName" + id="overlayGroupName" + /> + </label> + <label className="mt-5 block text-sm font-semibold" htmlFor="overlayGroupOrder"> + Order + <Input + type="numbe" + value={order} + onChange={handleOrderChange} + className="mt-2.5 text-sm font-medium" + data-testid="overlay-group-order" + name="overlayGroupOrder" + id="overlayGroupOrder" + /> + </label> + <div className="mt-10 flex items-center justify-between gap-5 text-center"> + <Button + type="button" + variantStyles="ghost" + className="flex-1 justify-center" + onClick={handleCancelEdit} + data-testid="cancel-button" + > + Cancel + </Button> + <Button + type="button" + variantStyles="ghost" + className="flex-1 justify-center" + onClick={handleRemoveOverlayGroup} + data-testid="remove-button" + > + Remove + </Button> + + <Button + type="button" + className="flex-1 justify-center" + onClick={handleSaveEditedOverlayGroup} + data-testid="save-button" + > + Save + </Button> + </div> + </form> + </div> + ); +}; diff --git a/src/components/FunctionalArea/Modal/EditOverlayGroupModal/hooks/useEditOverlayGroup.test.ts b/src/components/FunctionalArea/Modal/EditOverlayGroupModal/hooks/useEditOverlayGroup.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..e5a051392bf4c34efde53c2a03bd3553e563243b --- /dev/null +++ b/src/components/FunctionalArea/Modal/EditOverlayGroupModal/hooks/useEditOverlayGroup.test.ts @@ -0,0 +1,217 @@ +/* eslint-disable no-magic-numbers */ +import { DEFAULT_ERROR } from '@/constants/errors'; +import { overlayFixture } from '@/models/fixtures/overlaysFixture'; +import { getReduxStoreWithActionsListener } from '@/utils/testing/getReduxStoreActionsListener'; +import { renderHook } from '@testing-library/react'; +import { overlayGroupFixture } from '@/models/fixtures/overlayGroupsFixture'; +import { useEditOverlayGroup } from './useEditOverlayGroup'; + +const overlayGroup = { ...overlayGroupFixture, id: 109 }; +describe('useEditOverlayGroup', () => { + it('should handle cancel edit overlay', () => { + const { Wrapper, store } = getReduxStoreWithActionsListener({ + user: { + authenticated: true, + loading: 'succeeded', + error: DEFAULT_ERROR, + login: 'test', + role: 'user', + userData: null, + token: null, + }, + modal: { + isOpen: true, + modalTitle: overlayFixture.name, + modalName: 'edit-overlay-group', + editOverlayState: overlayFixture, + editOverlayGroupState: overlayGroup, + molArtState: {}, + overviewImagesState: {}, + errorReportState: {}, + layerFactoryState: { id: undefined }, + layerImageObjectFactoryState: undefined, + layerTextFactoryState: undefined, + }, + }); + + const { + result: { + current: { handleCancelEdit }, + }, + } = renderHook(() => useEditOverlayGroup(), { + wrapper: Wrapper, + }); + + handleCancelEdit(); + + const actions = store.getActions(); + + expect(actions[0].type).toBe('modal/closeModal'); + }); + + it('should handle handleRemoveOverlay if proper data is provided', () => { + const { Wrapper, store } = getReduxStoreWithActionsListener({ + user: { + authenticated: true, + loading: 'succeeded', + error: DEFAULT_ERROR, + login: 'test', + role: 'user', + userData: null, + token: null, + }, + modal: { + isOpen: true, + modalTitle: overlayFixture.name, + modalName: 'edit-overlay-group', + editOverlayState: null, + editOverlayGroupState: overlayGroup, + molArtState: {}, + overviewImagesState: {}, + errorReportState: {}, + layerFactoryState: { id: undefined }, + layerImageObjectFactoryState: undefined, + layerTextFactoryState: undefined, + }, + }); + + const { + result: { + current: { handleRemoveOverlayGroup }, + }, + } = renderHook(() => useEditOverlayGroup(), { + wrapper: Wrapper, + }); + + handleRemoveOverlayGroup(); + + const actions = store.getActions(); + + expect(actions[0].type).toBe('overlayGroups/removeOverlayGroup/pending'); + + const { overlayGroupId } = actions[0].meta.arg; + + expect(overlayGroupId).toBe(overlayGroup.id); + }); + it('should not handle handleRemoveOverlay if proper data is not provided', () => { + const { Wrapper, store } = getReduxStoreWithActionsListener({ + user: { + authenticated: true, + loading: 'failed', + error: DEFAULT_ERROR, + login: null, + role: 'user', + userData: null, + token: null, + }, + modal: { + isOpen: true, + modalTitle: overlayFixture.name, + modalName: 'edit-overlay-group', + editOverlayState: overlayFixture, + editOverlayGroupState: overlayGroupFixture, + molArtState: {}, + overviewImagesState: {}, + errorReportState: {}, + layerFactoryState: { id: undefined }, + layerImageObjectFactoryState: undefined, + layerTextFactoryState: undefined, + }, + }); + + const { + result: { + current: { handleRemoveOverlayGroup }, + }, + } = renderHook(() => useEditOverlayGroup(), { + wrapper: Wrapper, + }); + + handleRemoveOverlayGroup(); + + const actions = store.getActions(); + + expect(actions.length).toBe(0); + }); + it('should handle handleSaveEditedOverlay if proper data is provided', () => { + const { Wrapper, store } = getReduxStoreWithActionsListener({ + user: { + authenticated: true, + loading: 'succeeded', + error: DEFAULT_ERROR, + login: 'test', + role: 'user', + userData: null, + token: null, + }, + modal: { + isOpen: true, + modalTitle: overlayGroup.name, + modalName: 'edit-overlay-group', + editOverlayState: null, + editOverlayGroupState: overlayGroup, + molArtState: {}, + overviewImagesState: {}, + errorReportState: {}, + layerFactoryState: { id: undefined }, + layerImageObjectFactoryState: undefined, + layerTextFactoryState: undefined, + }, + }); + + const { + result: { + current: { handleSaveEditedOverlayGroup }, + }, + } = renderHook(() => useEditOverlayGroup(), { + wrapper: Wrapper, + }); + + handleSaveEditedOverlayGroup(); + + const actions = store.getActions(); + + expect(actions[0].type).toBe('overlayGroups/updateOverlayGroups/pending'); + expect(actions[0].meta.arg).toEqual([overlayGroup]); + }); + it('should not handle handleSaveEditedOverlay if proper data is not provided', () => { + const { Wrapper, store } = getReduxStoreWithActionsListener({ + user: { + authenticated: true, + loading: 'succeeded', + error: DEFAULT_ERROR, + login: null, + role: 'user', + userData: null, + token: null, + }, + modal: { + isOpen: true, + modalTitle: overlayFixture.name, + modalName: 'edit-overlay-group', + editOverlayState: overlayFixture, + editOverlayGroupState: overlayGroupFixture, + molArtState: {}, + overviewImagesState: {}, + errorReportState: {}, + layerFactoryState: { id: undefined }, + layerImageObjectFactoryState: undefined, + layerTextFactoryState: undefined, + }, + }); + + const { + result: { + current: { handleSaveEditedOverlayGroup }, + }, + } = renderHook(() => useEditOverlayGroup(), { + wrapper: Wrapper, + }); + + handleSaveEditedOverlayGroup(); + + const actions = store.getActions(); + + expect(actions.length).toBe(0); + }); +}); diff --git a/src/components/FunctionalArea/Modal/EditOverlayGroupModal/hooks/useEditOverlayGroup.ts b/src/components/FunctionalArea/Modal/EditOverlayGroupModal/hooks/useEditOverlayGroup.ts new file mode 100644 index 0000000000000000000000000000000000000000..3a8147f430f567511d37f9642a1333cb05420ad1 --- /dev/null +++ b/src/components/FunctionalArea/Modal/EditOverlayGroupModal/hooks/useEditOverlayGroup.ts @@ -0,0 +1,101 @@ +import { useAppDispatch } from '@/redux/hooks/useAppDispatch'; +import { useAppSelector } from '@/redux/hooks/useAppSelector'; +import { currentEditedOverlayGroupSelector } from '@/redux/modal/modal.selector'; +import { closeModal } from '@/redux/modal/modal.slice'; +import { loginUserSelector } from '@/redux/user/user.selectors'; +import { OverlayGroup } from '@/types/models'; +import React, { useState } from 'react'; +import { ONE } from '@/constants/common'; +import { + getOverlayGroups, + removeOverlayGroup, + updateOverlayGroups, +} from '@/redux/overlayGroup/overlayGroup.thunks'; + +type UseEditOverlayGroupReturn = { + name: string | undefined; + order: number | undefined; + handleCancelEdit: () => void; + handleRemoveOverlayGroup: () => void; + handleSaveEditedOverlayGroup: () => Promise<void>; + handleNameChange: (e: React.ChangeEvent<HTMLInputElement>) => void; + handleOrderChange: (e: React.ChangeEvent<HTMLInputElement>) => void; +}; + +type UpdatedOverlayGroup = { + editedOverlayGroup: OverlayGroup; + overlayName: string; + overlayOrder: number; +}; + +export const useEditOverlayGroup = (): UseEditOverlayGroupReturn => { + const currentEditedOverlayGroup = useAppSelector(currentEditedOverlayGroupSelector); + const login = useAppSelector(loginUserSelector); + const dispatch = useAppDispatch(); + const [name, setName] = useState(currentEditedOverlayGroup?.name); + const [order, setOrder] = useState(currentEditedOverlayGroup?.order); + + const handleCancelEdit = (): void => { + dispatch(closeModal()); + }; + + const handleNameChange = (e: React.ChangeEvent<HTMLInputElement>): void => { + setName(e.target.value); + }; + + const handleOrderChange = (e: React.ChangeEvent<HTMLInputElement>): void => { + setOrder(Number(e.target.value)); + }; + + const handleRemoveOverlayGroup = (): void => { + if (!login || !currentEditedOverlayGroup || !currentEditedOverlayGroup.id) return; + dispatch(removeOverlayGroup({ overlayGroupId: currentEditedOverlayGroup.id })); + }; + + const handleUpdateOverlay = async ({ + overlayName, + overlayOrder, + editedOverlayGroup, + }: UpdatedOverlayGroup): Promise<void> => { + await dispatch( + updateOverlayGroups([ + { + ...editedOverlayGroup, + name: overlayName, + order: overlayOrder, + }, + ]), + ); + }; + + const refreshOverlayGroups = async (): Promise<void> => { + await dispatch(getOverlayGroups()); + }; + + const handleCloseModal = (): void => { + dispatch(closeModal()); + }; + + const handleSaveEditedOverlayGroup = async (): Promise<void> => { + if (!currentEditedOverlayGroup || !name || !login) return; + await handleUpdateOverlay({ + editedOverlayGroup: currentEditedOverlayGroup, + overlayName: name, + overlayOrder: order || ONE, + }); + + await refreshOverlayGroups(); + + handleCloseModal(); + }; + + return { + handleCancelEdit, + handleRemoveOverlayGroup, + handleSaveEditedOverlayGroup, + handleNameChange, + handleOrderChange, + name, + order, + }; +}; diff --git a/src/components/FunctionalArea/Modal/EditOverlayGroupModal/index.ts b/src/components/FunctionalArea/Modal/EditOverlayGroupModal/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..78625bea3ddea535479a0f118d8a03651bc1ef8e --- /dev/null +++ b/src/components/FunctionalArea/Modal/EditOverlayGroupModal/index.ts @@ -0,0 +1 @@ +export { EditOverlayGroupModal } from './EditOverlayGroupModal.component'; diff --git a/src/components/FunctionalArea/Modal/EditOverlayModal/EditOverlayModal.component.test.tsx b/src/components/FunctionalArea/Modal/EditOverlayModal/EditOverlayModal.component.test.tsx index c7f3f156cbe91b17d1f84a2dba35ab70d25ca150..824b1503092f23e62e4184af2333eea2e4126b76 100644 --- a/src/components/FunctionalArea/Modal/EditOverlayModal/EditOverlayModal.component.test.tsx +++ b/src/components/FunctionalArea/Modal/EditOverlayModal/EditOverlayModal.component.test.tsx @@ -12,6 +12,7 @@ import { DEFAULT_ERROR } from '@/constants/errors'; import { act } from 'react-dom/test-utils'; import { OVERLAYS_INITIAL_STATE_MOCK } from '@/redux/overlays/overlays.mock'; import { showToast } from '@/utils/showToast'; +import { overlayGroupFixture } from '@/models/fixtures/overlayGroupsFixture'; import { Modal } from '../Modal.component'; const mockedAxiosClient = mockNetworkResponse(); @@ -50,6 +51,7 @@ describe('EditOverlayModal - component', () => { modalTitle: overlayFixture.name, modalName: 'edit-overlay', editOverlayState: overlayFixture, + editOverlayGroupState: overlayGroupFixture, molArtState: {}, overviewImagesState: {}, errorReportState: {}, @@ -71,6 +73,7 @@ describe('EditOverlayModal - component', () => { modalTitle: overlayFixture.name, modalName: 'edit-overlay', editOverlayState: overlayFixture, + editOverlayGroupState: overlayGroupFixture, molArtState: {}, overviewImagesState: {}, errorReportState: {}, @@ -105,6 +108,7 @@ describe('EditOverlayModal - component', () => { modalTitle: overlayFixture.name, modalName: 'edit-overlay', editOverlayState: overlayFixture, + editOverlayGroupState: overlayGroupFixture, molArtState: {}, overviewImagesState: {}, errorReportState: {}, @@ -144,6 +148,7 @@ describe('EditOverlayModal - component', () => { modalTitle: overlayFixture.name, modalName: 'edit-overlay', editOverlayState: overlayFixture, + editOverlayGroupState: overlayGroupFixture, molArtState: {}, overviewImagesState: {}, errorReportState: {}, @@ -184,6 +189,7 @@ describe('EditOverlayModal - component', () => { modalTitle: overlay.name, modalName: 'edit-overlay', editOverlayState: overlay, + editOverlayGroupState: overlayGroupFixture, molArtState: {}, overviewImagesState: {}, errorReportState: {}, @@ -230,6 +236,7 @@ describe('EditOverlayModal - component', () => { modalTitle: overlay.name, modalName: 'edit-overlay', editOverlayState: overlay, + editOverlayGroupState: overlayGroupFixture, molArtState: {}, overviewImagesState: {}, errorReportState: {}, @@ -260,6 +267,7 @@ describe('EditOverlayModal - component', () => { modalTitle: overlayFixture.name, modalName: 'edit-overlay', editOverlayState: overlayFixture, + editOverlayGroupState: overlayGroupFixture, molArtState: {}, overviewImagesState: {}, errorReportState: {}, diff --git a/src/components/FunctionalArea/Modal/EditOverlayModal/hooks/useEditOverlay.test.ts b/src/components/FunctionalArea/Modal/EditOverlayModal/hooks/useEditOverlay.test.ts index ac20ca6dbb4bff2878871d3fa430d70c397a4454..5bcb2c1067cb905c9f87172faddb7d5ed1f4a46c 100644 --- a/src/components/FunctionalArea/Modal/EditOverlayModal/hooks/useEditOverlay.test.ts +++ b/src/components/FunctionalArea/Modal/EditOverlayModal/hooks/useEditOverlay.test.ts @@ -3,6 +3,7 @@ import { DEFAULT_ERROR } from '@/constants/errors'; import { overlayFixture } from '@/models/fixtures/overlaysFixture'; import { getReduxStoreWithActionsListener } from '@/utils/testing/getReduxStoreActionsListener'; import { renderHook } from '@testing-library/react'; +import { overlayGroupFixture } from '@/models/fixtures/overlayGroupsFixture'; import { useEditOverlay } from './useEditOverlay'; describe('useEditOverlay', () => { @@ -22,6 +23,7 @@ describe('useEditOverlay', () => { modalTitle: overlayFixture.name, modalName: 'edit-overlay', editOverlayState: overlayFixture, + editOverlayGroupState: overlayGroupFixture, molArtState: {}, overviewImagesState: {}, errorReportState: {}, @@ -62,6 +64,7 @@ describe('useEditOverlay', () => { modalTitle: overlayFixture.name, modalName: 'edit-overlay', editOverlayState: overlayFixture, + editOverlayGroupState: overlayGroupFixture, molArtState: {}, overviewImagesState: {}, errorReportState: {}, @@ -105,6 +108,7 @@ describe('useEditOverlay', () => { modalTitle: overlayFixture.name, modalName: 'edit-overlay', editOverlayState: overlayFixture, + editOverlayGroupState: overlayGroupFixture, molArtState: {}, overviewImagesState: {}, errorReportState: {}, @@ -145,6 +149,7 @@ describe('useEditOverlay', () => { modalTitle: overlay.name, modalName: 'edit-overlay', editOverlayState: overlay, + editOverlayGroupState: overlayGroupFixture, molArtState: {}, overviewImagesState: {}, errorReportState: {}, @@ -185,6 +190,7 @@ describe('useEditOverlay', () => { modalTitle: overlayFixture.name, modalName: 'edit-overlay', editOverlayState: overlayFixture, + editOverlayGroupState: overlayGroupFixture, molArtState: {}, overviewImagesState: {}, errorReportState: {}, diff --git a/src/components/FunctionalArea/Modal/LayerImageObjectModal/LayerImageObjectEditFactoryModal.component.test.tsx b/src/components/FunctionalArea/Modal/LayerImageObjectModal/LayerImageObjectEditFactoryModal.component.test.tsx index 0c0973f2bb3ea4843ff5788e9c6e36863681c94d..775e9c5d827d949e56a396e4e78f3dad179fa757 100644 --- a/src/components/FunctionalArea/Modal/LayerImageObjectModal/LayerImageObjectEditFactoryModal.component.test.tsx +++ b/src/components/FunctionalArea/Modal/LayerImageObjectModal/LayerImageObjectEditFactoryModal.component.test.tsx @@ -21,6 +21,7 @@ import { MAP_EDIT_TOOLS_STATE_INITIAL_MOCK } from '@/redux/mapEditTools/mapEditT import { MAP_EDIT_ACTIONS } from '@/redux/mapEditTools/mapEditTools.constants'; import { Feature } from 'ol'; import Polygon from 'ol/geom/Polygon'; +import { overlayGroupFixture } from '@/models/fixtures/overlayGroupsFixture'; import { LayerImageObjectEditFactoryModal } from './LayerImageObjectEditFactoryModal.component'; const mockedAxiosNewClient = mockNetworkNewAPIResponse(); @@ -52,6 +53,7 @@ const renderComponent = ( modalTitle: overlayFixture.name, modalName: 'edit-overlay', editOverlayState: overlayFixture, + editOverlayGroupState: overlayGroupFixture, molArtState: {}, overviewImagesState: {}, errorReportState: {}, diff --git a/src/components/FunctionalArea/Modal/LayerImageObjectModal/LayerImageObjectFactoryModal.component.test.tsx b/src/components/FunctionalArea/Modal/LayerImageObjectModal/LayerImageObjectFactoryModal.component.test.tsx index 0d0de1fe5c6508f1e307a9bb7999a21bb980bba7..159cae33ce266bd9ec1578095e0666e4b07bac08 100644 --- a/src/components/FunctionalArea/Modal/LayerImageObjectModal/LayerImageObjectFactoryModal.component.test.tsx +++ b/src/components/FunctionalArea/Modal/LayerImageObjectModal/LayerImageObjectFactoryModal.component.test.tsx @@ -16,6 +16,7 @@ import { import { MODELS_DATA_MOCK_WITH_MAIN_MAP } from '@/redux/models/models.mock'; import { overlayFixture } from '@/models/fixtures/overlaysFixture'; import { showToast } from '@/utils/showToast'; +import { overlayGroupFixture } from '@/models/fixtures/overlayGroupsFixture'; import { LayerImageObjectFactoryModal } from './LayerImageObjectFactoryModal.component'; const mockedAxiosNewClient = mockNetworkNewAPIResponse(); @@ -46,6 +47,7 @@ const renderComponent = (): { store: StoreType } => { modalTitle: overlayFixture.name, modalName: 'edit-overlay', editOverlayState: overlayFixture, + editOverlayGroupState: overlayGroupFixture, molArtState: {}, overviewImagesState: {}, errorReportState: {}, diff --git a/src/components/FunctionalArea/Modal/LayerTextFactoryModal/LayerTextFactoryModal.component.test.tsx b/src/components/FunctionalArea/Modal/LayerTextFactoryModal/LayerTextFactoryModal.component.test.tsx index c739fb38104b2a4c91fbff35c657dea106843d10..4f57d68427e0f4ca68d17d60200b10fd40e74d4a 100644 --- a/src/components/FunctionalArea/Modal/LayerTextFactoryModal/LayerTextFactoryModal.component.test.tsx +++ b/src/components/FunctionalArea/Modal/LayerTextFactoryModal/LayerTextFactoryModal.component.test.tsx @@ -21,6 +21,7 @@ import { TEXT_VERTICAL_ALIGNMENTS, } from '@/components/FunctionalArea/Modal/LayerTextFactoryModal/LayerTextFactory.constants'; import { layerTextFixture } from '@/models/fixtures/layerTextFixture'; +import { overlayGroupFixture } from '@/models/fixtures/overlayGroupsFixture'; import { LayerTextFactoryModal } from './LayerTextFactoryModal.component'; const mockedAxiosNewClient = mockNetworkNewAPIResponse(); @@ -51,6 +52,7 @@ const renderComponent = (): { store: StoreType } => { modalTitle: overlayFixture.name, modalName: 'edit-overlay', editOverlayState: overlayFixture, + editOverlayGroupState: overlayGroupFixture, molArtState: {}, overviewImagesState: {}, errorReportState: {}, diff --git a/src/components/FunctionalArea/Modal/Modal.component.tsx b/src/components/FunctionalArea/Modal/Modal.component.tsx index 6cc342ee01daab46d18eca8bd47b0224f9b2b628..43b135cd4b44a1c3c491c66dc75d6e35cb044f9d 100644 --- a/src/components/FunctionalArea/Modal/Modal.component.tsx +++ b/src/components/FunctionalArea/Modal/Modal.component.tsx @@ -10,6 +10,7 @@ import { LayerImageObjectFactoryModal, } from '@/components/FunctionalArea/Modal/LayerImageObjectModal'; import { LayerTextFactoryModal } from '@/components/FunctionalArea/Modal/LayerTextFactoryModal/LayerTextFactoryModal.component'; +import { EditOverlayGroupModal } from '@/components/FunctionalArea/Modal/EditOverlayGroupModal'; import { EditOverlayModal } from './EditOverlayModal'; import { LoginModal } from './LoginModal'; import { ErrorReportModal } from './ErrorReportModal'; @@ -60,6 +61,11 @@ export const Modal = (): React.ReactNode => { <EditOverlayModal /> </ModalLayout> )} + {isOpen && modalName === 'edit-overlay-group' && ( + <ModalLayout> + <EditOverlayGroupModal /> + </ModalLayout> + )} {isOpen && modalName === 'logged-in-menu' && ( <ModalLayout> <LoggedInMenuModal /> diff --git a/src/components/Map/Drawer/OverlaysDrawer/UserOverlays/UserOverlaysGroup/UserOverlaysGroup.component.tsx b/src/components/Map/Drawer/OverlaysDrawer/UserOverlays/UserOverlaysGroup/UserOverlaysGroup.component.tsx index 7e6053a2ca46a0c963b6eab69428cba0a02f80e6..872cf5847758f5109deb9287654e132e34f6d823 100644 --- a/src/components/Map/Drawer/OverlaysDrawer/UserOverlays/UserOverlaysGroup/UserOverlaysGroup.component.tsx +++ b/src/components/Map/Drawer/OverlaysDrawer/UserOverlays/UserOverlaysGroup/UserOverlaysGroup.component.tsx @@ -10,8 +10,12 @@ import { HTML5Backend } from 'react-dnd-html5-backend'; import { MapOverlay } from '@/types/models'; import React from 'react'; import { ZERO } from '@/constants/common'; -import { UserOverlayListItem } from './UserOverlayListItem'; +import { Icon } from '@/shared/Icon'; +import { openEditOverlayGroupModal } from '@/redux/modal/modal.slice'; +import { overlayGroupSelector } from '@/redux/overlayGroup/overlayGroup.selectors'; +import { store } from '@/redux/store'; import { useUserOverlays } from './hooks/useUserOverlays'; +import { UserOverlayListItem } from './UserOverlayListItem'; type OverlayGroupProps = { groupId: number | null; @@ -27,6 +31,10 @@ export const UserOverlaysGroup = ({ const { moveUserOverlayListItem, updateUserOverlaysOrder, isPending, userOverlaysList } = useUserOverlays(overlays, groupId); + const { dispatch, getState } = store; + const state = getState(); + const overlayGroup = overlayGroupSelector(state, groupId); + const nullOverlay: MapOverlay = { id: groupId ? -groupId : ZERO, group: groupId, @@ -40,16 +48,34 @@ export const UserOverlaysGroup = ({ order: 0, }; + const openEditGroup = (): void => { + // eslint-disable-next-line no-console + console.log('Open edit group', groupId); + if (overlayGroup) { + dispatch(openEditOverlayGroupModal(overlayGroup)); + } + }; + return ( <DndProvider backend={HTML5Backend}> <div className="mt-2.5"> <Accordion allowZeroExpanded> <AccordionItem className="border-b-0"> <AccordionItemHeading> - <AccordionItemButton className="px-6 text-sm font-semibold"> + <AccordionItemButton + className="px-6 text-sm font-semibold" + sideMenu={ + groupId && ( + <button onClick={openEditGroup} type="button" className="mr-2"> + <Icon name="edit-image" /> + </button> + ) + } + > {groupName} </AccordionItemButton> </AccordionItemHeading> + <AccordionItemPanel> {isPending ? ( <span className="py-4 pl-10 pr-5">Loading...</span> diff --git a/src/redux/apiPath.ts b/src/redux/apiPath.ts index d42b790a93973ca77e038fa46fd44052cad2836f..0c35f8dabead5e065da7f4b153d72e68314b0aa5 100644 --- a/src/redux/apiPath.ts +++ b/src/redux/apiPath.ts @@ -137,4 +137,8 @@ export const apiPath = { getChemicalAutocomplete: (): string => `projects/${PROJECT_ID}/chemicals/suggestedQueryList`, getOverlayGroups: (): string => `projects/${PROJECT_ID}/overlay_groups/`, addOverlayGroup: (): string => `projects/${PROJECT_ID}/overlay_groups/`, + removeOverlayGroup: (overlayGroupId: number): string => + `projects/${PROJECT_ID}/overlay_groups/${overlayGroupId}/`, + updateOverlayGroup: (overlayGroupId: number): string => + `projects/${PROJECT_ID}/overlay_groups/${overlayGroupId}/`, }; diff --git a/src/redux/modal/modal.constants.ts b/src/redux/modal/modal.constants.ts index f858a91b08dc92a5e280c9fcbbfe37a45de80013..5d9f7748cd590638a5a4a630a717bfd6a2a0ba0a 100644 --- a/src/redux/modal/modal.constants.ts +++ b/src/redux/modal/modal.constants.ts @@ -12,6 +12,7 @@ export const MODAL_INITIAL_STATE: ModalState = { uniprotId: MOL_ART_UNIPROT_ID_DEFAULT, }, editOverlayState: null, + editOverlayGroupState: null, errorReportState: {}, layerFactoryState: { id: undefined }, layerImageObjectFactoryState: undefined, diff --git a/src/redux/modal/modal.mock.ts b/src/redux/modal/modal.mock.ts index 85f6e5bd48bb91fee342f740e4b9c46a66d6b138..59ce90baf78c027f3696a46ffd4d6d7fbf429618 100644 --- a/src/redux/modal/modal.mock.ts +++ b/src/redux/modal/modal.mock.ts @@ -12,6 +12,7 @@ export const MODAL_INITIAL_STATE_MOCK: ModalState = { uniprotId: MOL_ART_UNIPROT_ID_DEFAULT, }, editOverlayState: null, + editOverlayGroupState: null, errorReportState: {}, layerFactoryState: { id: undefined }, layerImageObjectFactoryState: undefined, diff --git a/src/redux/modal/modal.reducers.ts b/src/redux/modal/modal.reducers.ts index 1b903069073d88dccca9693049569dd708bb51af..1667a059d821c8559f6e6d154c4f3adaea56a739 100644 --- a/src/redux/modal/modal.reducers.ts +++ b/src/redux/modal/modal.reducers.ts @@ -2,7 +2,11 @@ import { ModalName } from '@/types/modal'; import { PayloadAction } from '@reduxjs/toolkit'; import { ErrorData } from '@/utils/error-report/ErrorData'; import { BoundingBox } from '@/components/Map/MapViewer/MapViewer.types'; -import { ModalState, OpenEditOverlayModalAction } from './modal.types'; +import { + ModalState, + OpenEditOverlayGroupModalAction, + OpenEditOverlayModalAction, +} from './modal.types'; const getOpenedModel = (state: ModalState): ModalName | null => { if (state.isOpen) { @@ -114,6 +118,16 @@ export const openEditOverlayModalReducer = ( state.editOverlayState = action.payload; }; +export const openEditOverlayGroupModalReducer = ( + state: ModalState, + action: OpenEditOverlayGroupModalAction, +): void => { + state.isOpen = true; + state.modalName = 'edit-overlay-group'; + state.modalTitle = action.payload.name; + state.editOverlayGroupState = action.payload; +}; + export const openLicenseModalReducer = (state: ModalState, action: PayloadAction<string>): void => { state.isOpen = true; state.modalName = 'license'; diff --git a/src/redux/modal/modal.selector.ts b/src/redux/modal/modal.selector.ts index 4f5c8fb0fee30573562f51b3bac3ba749a15fcd6..0576f26a0a3af2371b8b2b1eb1464a49953062e1 100644 --- a/src/redux/modal/modal.selector.ts +++ b/src/redux/modal/modal.selector.ts @@ -21,6 +21,11 @@ export const currentEditedOverlaySelector = createSelector( modal => modal.editOverlayState, ); +export const currentEditedOverlayGroupSelector = createSelector( + modalSelector, + modal => modal.editOverlayGroupState, +); + export const layerFactoryStateSelector = createSelector( modalSelector, modal => modal.layerFactoryState, diff --git a/src/redux/modal/modal.slice.ts b/src/redux/modal/modal.slice.ts index bf01d86a156af9a3df19550e82b0337ceda56725..c046894e90b49cb92feb0ebf57f5df086b456eb0 100644 --- a/src/redux/modal/modal.slice.ts +++ b/src/redux/modal/modal.slice.ts @@ -20,6 +20,7 @@ import { openLayerImageObjectFactoryModalReducer, openLayerImageObjectEditFactoryModalReducer, openLayerTextFactoryModalReducer, + openEditOverlayGroupModalReducer, } from './modal.reducers'; const modalSlice = createSlice({ @@ -35,6 +36,7 @@ const modalSlice = createSlice({ openAddCommentModal: openAddCommentModalReducer, openPublicationsModal: openPublicationsModalReducer, openEditOverlayModal: openEditOverlayModalReducer, + openEditOverlayGroupModal: openEditOverlayGroupModalReducer, openLoggedInMenuModal: openLoggedInMenuModalReducer, openErrorReportModal: openErrorReportModalReducer, openAccessDeniedModal: openAccessDeniedModalReducer, @@ -58,6 +60,7 @@ export const { openLoginModal, openPublicationsModal, openEditOverlayModal, + openEditOverlayGroupModal, openLoggedInMenuModal, openErrorReportModal, openAccessDeniedModal, diff --git a/src/redux/modal/modal.types.ts b/src/redux/modal/modal.types.ts index 20f7a9982ac831b2c9fbbfa76f3a9f89ad394e49..05b0f40cc0a38381f35cd1a80ed04e74c5b80605 100644 --- a/src/redux/modal/modal.types.ts +++ b/src/redux/modal/modal.types.ts @@ -1,5 +1,5 @@ import { ModalName } from '@/types/modal'; -import { MapOverlay } from '@/types/models'; +import { MapOverlay, OverlayGroup } from '@/types/models'; import { PayloadAction } from '@reduxjs/toolkit'; import { ErrorData } from '@/utils/error-report/ErrorData'; import { BoundingBox } from '@/components/Map/MapViewer/MapViewer.types'; @@ -17,6 +17,7 @@ export type ErrorRepostState = { }; export type EditOverlayState = MapOverlay | null; +export type EditOverlayGroupState = OverlayGroup | null; export type LayerFactoryState = { id: number | undefined; @@ -34,6 +35,7 @@ export interface ModalState { molArtState: MolArtModalState; errorReportState: ErrorRepostState; editOverlayState: EditOverlayState; + editOverlayGroupState: EditOverlayGroupState; layerFactoryState: LayerFactoryState; layerImageObjectFactoryState: LayerImageObjectFactoryState; layerTextFactoryState: LayerTextFactoryState; @@ -42,3 +44,7 @@ export interface ModalState { export type OpenEditOverlayModalPayload = MapOverlay; export type OpenEditOverlayModalAction = PayloadAction<OpenEditOverlayModalPayload>; + +export type OpenEditOverlayGroupModalPayload = OverlayGroup; + +export type OpenEditOverlayGroupModalAction = PayloadAction<OpenEditOverlayGroupModalPayload>; diff --git a/src/redux/overlayGroup/overlayGroup.selectors.ts b/src/redux/overlayGroup/overlayGroup.selectors.ts index bd26e08fbc60087b2f41465085fb7179b5747df4..309ecc8a3cb740d156497dc1efd45d7b1a04b5eb 100644 --- a/src/redux/overlayGroup/overlayGroup.selectors.ts +++ b/src/redux/overlayGroup/overlayGroup.selectors.ts @@ -3,12 +3,17 @@ import { DEFAULT_GROUP } from '@/components/Map/Drawer/OverlaysDrawer/UserOverla import { OverlayGroup } from '@/types/models'; import { rootSelector } from '../root/root.selectors'; -const overlayGroupSelector = createSelector(rootSelector, state => state.overlayGroups); +const overlayGroupsDataSelector = createSelector(rootSelector, state => state.overlayGroups); -export const overlayGroupsSelector = createSelector(overlayGroupSelector, overlayGroup => { +export const overlayGroupsSelector = createSelector(overlayGroupsDataSelector, overlayGroup => { let result: OverlayGroup[] = [DEFAULT_GROUP]; if (overlayGroup?.data) { result = result.concat(overlayGroup?.data); } return result; }); + +export const overlayGroupSelector = createSelector( + [overlayGroupsSelector, (_, overlayGroupId: number | null): number | null => overlayGroupId], + (groups, overlayGroupId) => groups.find(group => group.id === overlayGroupId), +); diff --git a/src/redux/overlayGroup/overlayGroup.thunks.ts b/src/redux/overlayGroup/overlayGroup.thunks.ts index 711bb8ef3b8b60fdc111dcca8e311691b2664adf..63d8c2a8bc6c667d3cfae4c6bdc440e1dce943fc 100644 --- a/src/redux/overlayGroup/overlayGroup.thunks.ts +++ b/src/redux/overlayGroup/overlayGroup.thunks.ts @@ -8,6 +8,9 @@ import { pageableSchema } from '@/models/pageableSchema'; import { overlayGroupSchema } from '@/models/overlayGroupSchema'; import { showToast } from '@/utils/showToast'; import axios from 'axios'; +import { closeModal } from '@/redux/modal/modal.slice'; +import { getAllUserOverlaysByCreator } from '@/redux/overlays/overlays.thunks'; +import { z } from 'zod'; import { apiPath } from '../apiPath'; export const getOverlayGroups = createAsyncThunk<OverlayGroup[], void, ThunkConfig>( @@ -39,7 +42,7 @@ type AddOverlayGroupArgs = { }; export const addOverlayGroup = createAsyncThunk<undefined, AddOverlayGroupArgs, ThunkConfig>( - 'overlays/addOverlayGroup', + 'overlayGroups/addOverlayGroup', async ( { name, order }, { dispatch }, @@ -78,3 +81,58 @@ export const addOverlayGroup = createAsyncThunk<undefined, AddOverlayGroupArgs, } }, ); + +export const removeOverlayGroup = createAsyncThunk< + undefined, + { overlayGroupId: number }, + ThunkConfig +>( + 'overlayGroups/removeOverlayGroup', + // eslint-disable-next-line consistent-return + async ({ overlayGroupId }, { dispatch }) => { + try { + await axiosInstanceNewAPI.delete(apiPath.removeOverlayGroup(overlayGroupId), { + withCredentials: true, + }); + await dispatch(getAllUserOverlaysByCreator()); + await dispatch(getOverlayGroups()); + dispatch(closeModal()); + + showToast({ type: 'success', message: 'User overlay group removed successfully' }); + } catch (error) { + return Promise.reject(getError({ error, prefix: 'Failed to remove user overlay group' })); + } + }, +); + +export const updateOverlayGroups = createAsyncThunk<undefined, OverlayGroup[], ThunkConfig>( + 'overlayGroups/updateOverlayGroups', + // eslint-disable-next-line consistent-return + async userOverlayGroups => { + try { + const userOverlaysPromises = userOverlayGroups.map(overlayGroup => { + if (overlayGroup.id !== null) + return axiosInstanceNewAPI.put<OverlayGroup>( + apiPath.updateOverlayGroup(overlayGroup.id), + overlayGroup, + { + withCredentials: true, + }, + ); + return Promise.resolve({ data: overlayGroup }); + }); + + const overlayGroupsResponses = await Promise.all(userOverlaysPromises); + + const updatedUserOverlayGroups = overlayGroupsResponses.map( + updatedUserOverlay => updatedUserOverlay.data, + ); + + validateDataUsingZodSchema(updatedUserOverlayGroups, z.array(overlayGroupSchema)); + + showToast({ type: 'success', message: 'User overlay group updated successfully' }); + } catch (error) { + return Promise.reject(getError({ error, prefix: 'Failed to update user overlay group' })); + } + }, +); diff --git a/src/shared/Accordion/AccordionItemButton/AccordionItemButton.component.tsx b/src/shared/Accordion/AccordionItemButton/AccordionItemButton.component.tsx index fb22d501fb80a1f16e756d3160b2fac75bab4d71..74aff60f75289143f8bfb3ab93921d4422ca3a72 100644 --- a/src/shared/Accordion/AccordionItemButton/AccordionItemButton.component.tsx +++ b/src/shared/Accordion/AccordionItemButton/AccordionItemButton.component.tsx @@ -10,6 +10,7 @@ type AccordionItemButtonProps = { onClick?: () => void; disabled?: boolean; className?: string; + sideMenu?: React.ReactNode; }; export const AccordionItemButton = ({ @@ -18,6 +19,7 @@ export const AccordionItemButton = ({ onClick, disabled, className, + sideMenu, }: AccordionItemButtonProps): JSX.Element => { const ButtonIcon = getIcon(variant); @@ -33,6 +35,7 @@ export const AccordionItemButton = ({ {children} {ButtonIcon} </button> + {sideMenu} </AIB> ); }; diff --git a/src/types/modal.ts b/src/types/modal.ts index 8b02e86a5eafbd7386e551cab127450f432d28c1..4b07798e6639e505c71231e9ca5d3bcd258cabda 100644 --- a/src/types/modal.ts +++ b/src/types/modal.ts @@ -7,6 +7,7 @@ export type ModalName = | 'license' | 'publications' | 'edit-overlay' + | 'edit-overlay-group' | 'error-report' | 'access-denied' | 'select-project'