From d4a78ea917ccc6de33eb18018a48882c3c97cb59 Mon Sep 17 00:00:00 2001 From: Piotr Gawron <p.gawron@atcomp.pl> Date: Fri, 14 Feb 2025 14:03:31 +0100 Subject: [PATCH] allow to edit/remove group overlay --- .../EditOverlayGroupModal.component.test.tsx | 297 ++++++++++++++++++ .../EditOverlayGroupModal.component.tsx | 76 +++++ .../hooks/useEditOverlayGroup.test.ts | 217 +++++++++++++ .../hooks/useEditOverlayGroup.ts | 101 ++++++ .../Modal/EditOverlayGroupModal/index.ts | 1 + .../EditOverlayModal.component.test.tsx | 8 + .../hooks/useEditOverlay.test.ts | 6 + ...eObjectEditFactoryModal.component.test.tsx | 2 + ...ImageObjectFactoryModal.component.test.tsx | 2 + .../LayerTextFactoryModal.component.test.tsx | 2 + .../FunctionalArea/Modal/Modal.component.tsx | 6 + .../UserOverlaysGroup.component.tsx | 30 +- src/redux/apiPath.ts | 4 + src/redux/modal/modal.constants.ts | 1 + src/redux/modal/modal.mock.ts | 1 + src/redux/modal/modal.reducers.ts | 16 +- src/redux/modal/modal.selector.ts | 5 + src/redux/modal/modal.slice.ts | 3 + src/redux/modal/modal.types.ts | 8 +- .../overlayGroup/overlayGroup.selectors.ts | 9 +- src/redux/overlayGroup/overlayGroup.thunks.ts | 60 +++- .../AccordionItemButton.component.tsx | 3 + src/types/modal.ts | 1 + 23 files changed, 852 insertions(+), 7 deletions(-) create mode 100644 src/components/FunctionalArea/Modal/EditOverlayGroupModal/EditOverlayGroupModal.component.test.tsx create mode 100644 src/components/FunctionalArea/Modal/EditOverlayGroupModal/EditOverlayGroupModal.component.tsx create mode 100644 src/components/FunctionalArea/Modal/EditOverlayGroupModal/hooks/useEditOverlayGroup.test.ts create mode 100644 src/components/FunctionalArea/Modal/EditOverlayGroupModal/hooks/useEditOverlayGroup.ts create mode 100644 src/components/FunctionalArea/Modal/EditOverlayGroupModal/index.ts 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 00000000..e6a7c263 --- /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 00000000..f339e89d --- /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 00000000..e5a05139 --- /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 00000000..3a8147f4 --- /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 00000000..78625bea --- /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 c7f3f156..824b1503 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 ac20ca6d..5bcb2c10 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 0c0973f2..775e9c5d 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 0d0de1fe..159cae33 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 c739fb38..4f57d684 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 6cc342ee..43b135cd 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 7e6053a2..872cf584 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 d42b790a..0c35f8da 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 f858a91b..5d9f7748 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 85f6e5bd..59ce90ba 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 1b903069..1667a059 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 4f5c8fb0..0576f26a 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 bf01d86a..c046894e 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 20f7a998..05b0f40c 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 bd26e08f..309ecc8a 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 711bb8ef..63d8c2a8 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 fb22d501..74aff60f 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 8b02e86a..4b07798e 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' -- GitLab