Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Task/wg 243 manage project #313

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 0 additions & 10 deletions react/src/components/DeleteMapModal/DeleteMapModal.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -118,15 +118,5 @@ describe('DeleteMapModal', () => {
);
expect(errorMessage).toBeDefined();
});

it('should show success message when isSuccess is true', async () => {
(useDeleteProject as jest.Mock).mockReturnValue({
...mockHookReturn,
isSuccess: true,
});
await renderComponent();
const successMessage = screen.getByText('Succesfully deleted the map.');
expect(successMessage).toBeDefined();
});
});
});
31 changes: 25 additions & 6 deletions react/src/components/DeleteMapModal/DeleteMapModal.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import React, { useEffect } from 'react';
import { Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap';
import { Button, SectionMessage } from '@tacc/core-components';
import { useNavigate, useLocation } from 'react-router-dom';
import { Project } from '../../types';
import { useDeleteProject } from '../../hooks/projects/';

Expand All @@ -15,12 +16,35 @@ const DeleteMapModal = ({
close: parentToggle,
project,
}: DeleteMapModalProps) => {
const navigate = useNavigate();
const location = useLocation();
const {
mutate: deleteProject,
isPending: isDeletingProject,
isError,
isSuccess,
} = useDeleteProject();

// Effect to handle navigation on successful deletion
sophia-massie marked this conversation as resolved.
Show resolved Hide resolved
useEffect(() => {
if (isSuccess) {
parentToggle();
if (location.pathname.includes(`/project/${project.uuid}`)) {
// If on project page, navigate home with success state
navigate('/', {
replace: true,
state: { showDeleteSuccess: true },
});
} else {
// If not on project page, just navigate to current location with success state
navigate(location.pathname, {
replace: true,
state: { showDeleteSuccess: true },
});
}
}
}, [isSuccess, project.uuid, location.pathname, navigate, parentToggle]);

const handleClose = () => {
parentToggle();
};
Expand Down Expand Up @@ -63,11 +87,6 @@ const DeleteMapModal = ({
>
Delete
</Button>
{isSuccess && (
<SectionMessage type="success">
{'Succesfully deleted the map.'}
</SectionMessage>
)}
{isError && (
<SectionMessage type="error">
{'There was an error deleting your map.'}
Expand Down

This file was deleted.

1 change: 0 additions & 1 deletion react/src/components/ManageMapProjectModal/index.tsx

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import React from 'react';
import { screen, fireEvent } from '@testing-library/react';
import ManageMapProjectPanel from './ManageMapProjectPanel';
import { projectMock } from '@hazmapper/__fixtures__/projectFixtures';
import { renderInTest, testQueryClient } from '@hazmapper/test/testUtil';

// Mock the child components
jest.mock('./MapTabContent', () => {
return function MockMapTabContent() {
return <div data-testid="map-tab-content">Map Tab Content</div>;
};
});

jest.mock('./MembersTabContent', () => {
return function MockMembersTabContent() {
return <div data-testid="members-tab-content">Members Tab Content</div>;
};
});

jest.mock('./PublicTabContent', () => {
return function MockPublicTabContent() {
return <div data-testid="public-tab-content">Public Tab Content</div>;
};
});

jest.mock('./SaveTabContent', () => {
return function MockSaveTabContent() {
return <div data-testid="save-tab-content">Save Tab Content</div>;
};
});

describe('ManageMapProjectPanel', () => {
const defaultProps = {
project: projectMock,
onProjectUpdate: jest.fn(),
};

beforeEach(() => {
jest.clearAllMocks();
testQueryClient.clear();
});

it('renders all tab buttons', () => {
renderInTest(<ManageMapProjectPanel {...defaultProps} />);

expect(screen.getByRole('tab', { name: 'Map' })).toBeTruthy();
expect(screen.getByRole('tab', { name: 'Members' })).toBeTruthy();
expect(screen.getByRole('tab', { name: 'Public' })).toBeTruthy();
expect(screen.getByRole('tab', { name: 'Save' })).toBeTruthy();
});

it('renders map tab content by default', () => {
renderInTest(<ManageMapProjectPanel {...defaultProps} />);

expect(screen.getByTestId('map-tab-content')).toBeTruthy();
});
it('switches between tabs correctly', () => {
renderInTest(<ManageMapProjectPanel {...defaultProps} />);

// Click Members tab
const membersTab = screen.getByRole('tab', { name: 'Members' });
fireEvent.click(membersTab);
expect(screen.getByTestId('members-tab-content')).toBeTruthy();

// Click Public tab
const publicTab = screen.getByRole('tab', { name: 'Public' });
fireEvent.click(publicTab);
expect(screen.getByTestId('public-tab-content')).toBeTruthy();

// Click Save tab
const saveTab = screen.getByRole('tab', { name: 'Save' });
fireEvent.click(saveTab);
expect(screen.getByTestId('save-tab-content')).toBeTruthy();
});
});
133 changes: 133 additions & 0 deletions react/src/components/ManageMapProjectPanel/ManageMapProjectPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import React, { useState, useEffect } from 'react';
import styles from './ManageMapProjectPanel.module.css';
import type { TabsProps } from 'antd';
import { Flex, Tabs, notification, Card } from 'antd';
import { Project, ProjectRequest } from '@hazmapper/types';
import MapTabContent from './MapTabContent';
import MembersTabContent from './MembersTabContent';
import PublicTabContent from './PublicTabContent';
import { useUpdateProjectInfo } from '@hazmapper/hooks';
import SaveTabContent from './SaveTabContent';

interface ManageMapProjectModalProps {
project: Project;
onProjectUpdate?: (updatedProject: Project) => void;
}

const ManageMapProjectPanel: React.FC<ManageMapProjectModalProps> = ({
project: initialProject,
onProjectUpdate,
}) => {
const [activeKey, setActiveKey] = useState('1');
const [activeProject, setActiveProject] = useState(initialProject);
const [updateApi, contextHolder] = notification.useNotification();

const { mutate, isPending } = useUpdateProjectInfo();

// Update activeProject when initialProject changes
useEffect(() => {
setActiveProject(initialProject);
}, [initialProject]);

const handleProjectUpdate = (updateData: Partial<ProjectRequest>) => {
const newData: ProjectRequest = {
name: updateData.name ?? activeProject.name,
description: updateData.description ?? activeProject.description,
public: updateData.public ?? activeProject.public,
};

mutate(newData, {
onSuccess: (updatedProject) => {
setActiveProject((prev) => {
const newState = {
...prev,
name: updatedProject.name,
description: updatedProject.description,
public: updatedProject.public,
};
return newState;
});
onProjectUpdate?.(updatedProject);
updateApi.open({
type: 'success',
message: 'Success!',
description: 'Your project was successfully updated.',
placement: 'topRight',
closable: false,
});
},
onError: () => {
updateApi.open({
type: 'error',
message: 'Error!',
description: 'There was an error updating your project.',
placement: 'topRight',
closable: false,
});
},
});
};

const items: TabsProps['items'] = [
{
label: 'Map',
key: '1',
children: (
<Card title="Map Details" type="inner">
<MapTabContent
project={activeProject}
onProjectUpdate={handleProjectUpdate}
isPending={isPending}
/>
</Card>
),
},
{
label: 'Members',
key: '2',
children: (
<Card title="Members" type="inner">
<MembersTabContent project={activeProject} />
</Card>
),
},
{
label: 'Public',
key: '3',
children: (
<Card type="inner" title="Public Access">
<PublicTabContent
project={activeProject}
onProjectUpdate={handleProjectUpdate}
isPending={isPending}
/>
</Card>
),
},
{
label: 'Save',
key: '4',
children: (
<Card type="inner" title="Save Location">
<SaveTabContent project={activeProject}></SaveTabContent>
</Card>
),
},
];

return (
<Flex vertical className={styles.root}>
{contextHolder}
<Tabs
type="card"
size="small"
activeKey={activeKey}
onChange={setActiveKey}
items={items}
style={{ marginTop: 20, marginLeft: 30, marginRight: 30 }}
/>
</Flex>
);
};

export default ManageMapProjectPanel;
Loading
Loading