Skip to content

Commit

Permalink
Merge pull request #102 from Pearson-Advance/vue/PADV-1265
Browse files Browse the repository at this point in the history
feat: delete class
  • Loading branch information
01001110J authored Jul 5, 2024
2 parents 66b85ed + c382773 commit e798f1f
Show file tree
Hide file tree
Showing 6 changed files with 206 additions and 53 deletions.
27 changes: 27 additions & 0 deletions src/features/Courses/CourseDetailTable/__test__/columns.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ import { MemoryRouter, Route } from 'react-router-dom';

import { renderWithProviders } from 'test-utils';
import { columns } from 'features/Courses/CourseDetailTable/columns';
import { RequestStatus } from 'features/constants';

jest.mock('@edx/frontend-platform/logging', () => ({
logError: jest.fn(),
}));

describe('columns', () => {
test('returns an array of columns with correct properties', () => {
Expand Down Expand Up @@ -126,6 +131,11 @@ describe('columns', () => {

const Component = () => columns[1].Cell(values);
const mockStore = {
courses: {
newClass: {
status: RequestStatus.INITIAL,
},
},
classes: {
table: {
data: [
Expand Down Expand Up @@ -180,6 +190,11 @@ describe('columns', () => {
const ComponentNoInstructor = () => columns[1].Cell(values);

const mockStore = {
courses: {
newClass: {
status: RequestStatus.INITIAL,
},
},
classes: {
table: {
data: [
Expand Down Expand Up @@ -234,11 +249,17 @@ describe('columns', () => {

const Component = () => columns[8].Cell(values);
const mockStore = {
courses: {
newClass: {
status: RequestStatus.INITIAL,
},
},
classes: {
table: {
data: [
{
masterCourseName: 'Demo MasterCourse 1',
classId: 'cxx-demo-id',
className: 'Demo Class 1',
startDate: '09/21/24',
endDate: null,
Expand Down Expand Up @@ -270,5 +291,11 @@ describe('columns', () => {
expect(component.getByText('View class content')).toBeInTheDocument();
expect(component.getByText('Manage Instructors')).toBeInTheDocument();
expect(component.getByText('Edit Class')).toBeInTheDocument();
expect(component.getByText('Delete Class')).toBeInTheDocument();

const deleteButton = component.getByText('Delete Class');

fireEvent.click(deleteButton);
expect(screen.getByText(/This action will permanently delete this class/i)).toBeInTheDocument();
});
});
179 changes: 127 additions & 52 deletions src/features/Courses/CourseDetailTable/columns.jsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,30 @@
/* eslint-disable react/prop-types */
import React from 'react';
import React, { useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useParams, Link } from 'react-router-dom';
import {
Dropdown, useToggle, IconButton, Icon,
Dropdown,
useToggle,
IconButton,
Icon,
Toast,
} from '@edx/paragon';
import { Badge } from 'react-paragon-topaz';
import { MoreHoriz } from '@edx/paragon/icons';
import { getConfig } from '@edx/frontend-platform';
import { logError } from '@edx/frontend-platform/logging';

import { formatUTCDate, setAssignStaffRole } from 'helpers';

import AddClass from 'features/Courses/AddClass';
import DeleteModal from 'features/Common/DeleteModal';

import { RequestStatus, initialPage } from 'features/constants';

import { deleteClass } from 'features/Courses/data/thunks';
import { fetchClassesData } from 'features/Classes/data/thunks';

import { resetClassState } from 'features/Courses/data/slice';

import 'assets/global.scss';

Expand Down Expand Up @@ -106,60 +120,121 @@ const columns = [
maxStudents,
} = row.original;

const [isOpenModal, openModal, closeModal] = useToggle(false);
const initialDeletionClassState = {
isModalOpen: false,
isRequestComplete: false,
};

const dispatch = useDispatch();
const institution = useSelector((state) => state.main.selectedInstitution);
const deletionState = useSelector((state) => state.courses.newClass.status);
const toastMessage = useSelector((state) => state.courses.notificationMessage);

const [isOpenEditModal, openModal, closeModal] = useToggle(false);
const [deletionClassState, setDeletionState] = useState(initialDeletionClassState);

const handleResetDeletion = () => {
setDeletionState(initialDeletionClassState);
dispatch(resetClassState());
};

const handleOpenDeleteModal = () => {
dispatch(resetClassState());
setDeletionState({ isModalOpen: true, isRequestComplete: false });
};

const handleDeleteClass = async (rowClassId) => {
try {
await dispatch(deleteClass(rowClassId));

setDeletionState({
isModalOpen: false,
isRequestComplete: true,
});

await dispatch(fetchClassesData(institution.id, initialPage, masterCourseName));
} catch (error) {
logError(error);
} finally {
setDeletionState((prevState) => ({
...prevState,
isRequestComplete: true,
}));
}
};

return (
<Dropdown className="dropdowntpz">
<Dropdown.Toggle
id="dropdown-toggle-with-iconbutton"
as={IconButton}
src={MoreHoriz}
iconAs={Icon}
variant="primary"
data-testid="droprown-action"
alt="menu for actions"
/>
<Dropdown.Menu>
<Dropdown.Item
target="_blank"
rel="noreferrer"
onClick={() => setAssignStaffRole(`${getConfig().LEARNING_MICROFRONTEND_URL}/course/${classId}/home`, classId)}
className="text-truncate text-decoration-none custom-text-black"
>
<i className="fa-regular fa-eye mr-2 mb-1" />
View class content
</Dropdown.Item>
<Dropdown.Item>
<Link
to={`/manage-instructors/${encodeURIComponent(masterCourseName)}/${encodeURIComponent(row.values.className)}?classId=${classId}`}
<>
<Dropdown className="dropdowntpz">
<Dropdown.Toggle
id="dropdown-toggle-with-iconbutton"
as={IconButton}
src={MoreHoriz}
iconAs={Icon}
variant="primary"
data-testid="droprown-action"
alt="menu for actions"
/>
<Dropdown.Menu>
<Dropdown.Item
target="_blank"
rel="noreferrer"
onClick={() => setAssignStaffRole(`${getConfig().LEARNING_MICROFRONTEND_URL}/course/${classId}/home`, classId)}
className="text-truncate text-decoration-none custom-text-black"
>
<i className="fa-regular fa-chalkboard-user mr-2 mb-1" />
Manage Instructors
</Link>
</Dropdown.Item>
<Dropdown.Item onClick={openModal}>
<i className="fa-solid fa-pencil mr-2 mb-1" />
Edit Class
</Dropdown.Item>
<AddClass
isOpen={isOpenModal}
onClose={closeModal}
courseInfo={{
masterCourseName,
masterCourseId,
classId,
className,
startDate,
endDate,
minStudents: minStudentsAllowed,
maxStudents,
}}
isCoursePage
isEditing
/>
</Dropdown.Menu>
</Dropdown>
<i className="fa-regular fa-eye mr-2 mb-1" />
View class content
</Dropdown.Item>
<Dropdown.Item>
<Link
to={`/manage-instructors/${encodeURIComponent(masterCourseName)}/${encodeURIComponent(row.values.className)}?classId=${classId}`}
className="text-truncate text-decoration-none custom-text-black"
>
<i className="fa-regular fa-chalkboard-user mr-2 mb-1" />
Manage Instructors
</Link>
</Dropdown.Item>
<Dropdown.Item onClick={openModal}>
<i className="fa-solid fa-pencil mr-2 mb-1" />
Edit Class
</Dropdown.Item>
<Dropdown.Item onClick={handleOpenDeleteModal} className="text-danger">
<i className="fa-regular fa-trash mr-2 mb-1" />
Delete Class
</Dropdown.Item>
<AddClass
isOpen={isOpenEditModal}
onClose={closeModal}
courseInfo={{
masterCourseName,
masterCourseId,
classId,
className,
startDate,
endDate,
minStudents: minStudentsAllowed,
maxStudents,
}}
isCoursePage
isEditing
/>
<DeleteModal
isLoading={deletionState === RequestStatus.LOADING}
isOpen={deletionClassState.isModalOpen}
onClose={handleResetDeletion}
handleDelete={() => { handleDeleteClass(classId); }}
title="Delete this class"
textModal="This action will permanently delete this class and cannot be undone. Booked seat in this class will not be affected by this action."
/>
</Dropdown.Menu>
</Dropdown>
<Toast
onClose={handleResetDeletion}
show={deletionClassState.isRequestComplete}
>
{toastMessage}
</Toast>
</>
);
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { waitFor } from '@testing-library/react';
import { MemoryRouter, Route } from 'react-router-dom';
import '@testing-library/jest-dom/extend-expect';

import { RequestStatus } from 'features/constants';

import { renderWithProviders } from 'test-utils';
import CoursesDetailPage from 'features/Courses/CoursesDetailPage';

Expand All @@ -25,6 +27,9 @@ const mockStore = {
},
},
courses: {
newClass: {
status: RequestStatus.INITIAL,
},
table: {
data: [
{
Expand Down
25 changes: 25 additions & 0 deletions src/features/Courses/data/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,32 @@ function handleEditClass(data) {
);
}

/**
* Deletes a class with the specified classId.
*
* This function constructs the API URL for deleting a class and
* sends a DELETE request using an authenticated HTTP client.
*
* @param {string} classId - The ID of the class to be deleted.
* @returns {Promise} - A promise that resolves with the response of the DELETE request.
*/
function handleDeleteClass(classId) {
const apiV2BaseUrl = getConfig().COURSE_OPERATIONS_API_V2_BASE_URL;
const data = JSON.stringify({ class_id: classId });

return getAuthenticatedHttpClient().delete(
`${apiV2BaseUrl}/classes/`,
{
headers: {
'Content-Type': 'application/json',
},
data,
},
);
}

export {
handleNewClass,
handleEditClass,
handleDeleteClass,
};
5 changes: 5 additions & 0 deletions src/features/Courses/data/slice.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ export const coursesSlice = createSlice({
updateFilters: (state, { payload }) => {
state.filters = payload;
},
resetClassState: (state) => {
state.newClass.status = RequestStatus.INITIAL;
state.notificationMessage = null;
},
newClassRequest: (state) => {
state.newClass.status = RequestStatus.LOADING;
},
Expand Down Expand Up @@ -77,6 +81,7 @@ export const {
newClassRequest,
newClassSuccess,
newClassFailed,
resetClassState,
updateNotificationMsg,
} = coursesSlice.actions;

Expand Down
18 changes: 17 additions & 1 deletion src/features/Courses/data/thunks.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
} from 'features/Courses/data/slice';
import { getCoursesByInstitution } from 'features/Common/data/api';
import { initialPage } from 'features/constants';
import { handleNewClass, handleEditClass } from 'features/Courses/data/api';
import { handleNewClass, handleEditClass, handleDeleteClass } from 'features/Courses/data/api';
import { assignInstructors } from 'features/Instructors/data';

function fetchCoursesData(id, currentPage, filtersData) {
Expand Down Expand Up @@ -77,7 +77,23 @@ function editClass(classData) {
};
}

function deleteClass(classId) {
return async (dispatch) => {
dispatch(newClassRequest());
try {
const response = await handleDeleteClass(classId);
dispatch(newClassSuccess(response.data));
dispatch(updateNotificationMsg('Class Deleted successfully'));
} catch (error) {
dispatch(newClassFailed());
logError(error);
dispatch(updateNotificationMsg('Class could not be deleted'));
}
};
}

export {
deleteClass,
fetchCoursesData,
fetchCoursesOptionsData,
addClass,
Expand Down

0 comments on commit e798f1f

Please sign in to comment.