Skip to content

Commit

Permalink
refactor: manage instructors buttons in class page
Browse files Browse the repository at this point in the history
  • Loading branch information
01001110J committed Nov 22, 2024
1 parent ba9ce7b commit 17bfa3a
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 25 deletions.
15 changes: 8 additions & 7 deletions src/features/Classes/Class/ClassPage/Actions.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,6 @@ const Actions = ({ previousPage }) => {
const [isOpenEditModal, openEditModal, closeEditModal] = useToggle(false);
const [isEnrollModalOpen, setIsEnrollModalOpen] = useState(false);

const handleEnrollStudentModal = () => setIsEnrollModalOpen(!isEnrollModalOpen);
const addQueryParam = useInstitutionIdQueryParam();

const handleManageButton = () => {
history.push(addQueryParam(`/manage-instructors/${courseId}/${classId}?previous=${previousPage}`));
};

let instructorText = 'Assign instructor';

if (classInfo?.instructors?.length > 1) {
Expand All @@ -66,6 +59,14 @@ const Actions = ({ previousPage }) => {
instructorText = 'Manage instructor';
}

const addQueryParam = useInstitutionIdQueryParam();

const handleEnrollStudentModal = () => setIsEnrollModalOpen(!isEnrollModalOpen);

const handleManageButton = () => {
history.push(addQueryParam(`/manage-instructors/${courseId}/${classId}?previous=${previousPage}`));
};

const handleResetDeletion = () => {
setDeletionState(initialDeletionClassState);
dispatch(resetClassState());
Expand Down
69 changes: 57 additions & 12 deletions src/features/Classes/InstructorCard/__test__/index.test.jsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
import React from 'react';
import { renderWithProviders } from 'test-utils';
import '@testing-library/jest-dom/extend-expect';
import { MemoryRouter, Route } from 'react-router-dom';

import InstructorCard from 'features/Classes/InstructorCard';
import { renderWithProviders } from 'test-utils';

jest.mock('react-router-dom', () => ({
useParams: jest.fn(() => ({
classId: 'ccx-v1',
})),
}));
import InstructorCard from 'features/Classes/InstructorCard';

jest.mock('@edx/frontend-platform/logging', () => ({
logError: jest.fn(),
Expand Down Expand Up @@ -45,7 +41,11 @@ const stateMock = {
describe('InstructorCard', () => {
test('Should render with correct elements', () => {
const { getByText } = renderWithProviders(
<InstructorCard isOpen onClose={() => { }} />,
<MemoryRouter initialEntries={[`/courses/${encodeURIComponent('course-v1:XXX+YYY+2023')}/${encodeURIComponent('ccx-v1')}`]}>
<Route path="/courses/:courseId/:classId">
<InstructorCard isOpen onClose={() => { }} />
</Route>
</MemoryRouter>,
{ preloadedState: stateMock },
);

Expand All @@ -57,7 +57,11 @@ describe('InstructorCard', () => {

test('Should render multiple instructors', () => {
const { getByText } = renderWithProviders(
<InstructorCard isOpen onClose={() => { }} />,
<MemoryRouter initialEntries={[`/courses/${encodeURIComponent('course-v1:XXX+YYY+2023')}/${encodeURIComponent('ccx-v1')}`]}>
<Route path="/courses/:courseId/:classId">
<InstructorCard isOpen onClose={() => { }} />
</Route>
</MemoryRouter>,
{
preloadedState: {
instructors: {
Expand Down Expand Up @@ -101,11 +105,44 @@ describe('InstructorCard', () => {
);

expect(getByText(/more\.\.\./)).toBeInTheDocument();
expect(getByText(/Manage instructors/)).toBeInTheDocument();
});

test('Should render assign instructor button if the instructor is not present', () => {
const { getByText } = renderWithProviders(
<MemoryRouter initialEntries={[`/courses/${encodeURIComponent('course-v1:XXX+YYY+2023')}/${encodeURIComponent('ccx-v1')}`]}>
<Route path="/courses/:courseId/:classId">
<InstructorCard isOpen onClose={() => { }} />
</Route>
</MemoryRouter>,
{
preloadedState: {
classes: {
allClasses: {
data: [
{
...stateMock.classes.allClasses.data[0],
startDate: '2024-02-13T17:42:22Z',
endDate: '2025-02-13T17:42:22Z',
instructors: [],
},
],
},
},
},
},
);

expect(getByText('Assign instructor')).toBeInTheDocument();
});

test('Should render both dates when the duration is to long', () => {
const { getByText } = renderWithProviders(
<InstructorCard isOpen onClose={() => { }} />,
<MemoryRouter initialEntries={[`/courses/${encodeURIComponent('course-v1:XXX+YYY+2023')}/${encodeURIComponent('ccx-v1')}`]}>
<Route path="/courses/:courseId/:classId">
<InstructorCard isOpen onClose={() => { }} />
</Route>
</MemoryRouter>,
{
preloadedState: {
classes: {
Expand All @@ -128,7 +165,11 @@ describe('InstructorCard', () => {

test('Should render the date when the course take couple months', () => {
const { getByText } = renderWithProviders(
<InstructorCard isOpen onClose={() => { }} />,
<MemoryRouter initialEntries={[`/courses/${encodeURIComponent('course-v1:XXX+YYY+2023')}/${encodeURIComponent('ccx-v1')}`]}>
<Route path="/courses/:courseId/:classId">
<InstructorCard isOpen onClose={() => { }} />
</Route>
</MemoryRouter>,
{
preloadedState: {
classes: {
Expand All @@ -151,7 +192,11 @@ describe('InstructorCard', () => {

test('Should render the date when the course take one single month', () => {
const { getByText } = renderWithProviders(
<InstructorCard isOpen onClose={() => { }} />,
<MemoryRouter initialEntries={[`/courses/${encodeURIComponent('course-v1:XXX+YYY+2023')}/${encodeURIComponent('ccx-v1')}`]}>
<Route path="/courses/:courseId/:classId">
<InstructorCard isOpen onClose={() => { }} />
</Route>
</MemoryRouter>,
{
preloadedState: {
classes: {
Expand Down
50 changes: 44 additions & 6 deletions src/features/Classes/InstructorCard/index.jsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,37 @@
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import { Spinner } from '@edx/paragon';
import { Button } from 'react-paragon-topaz';
import { useSelector, useDispatch } from 'react-redux';
import { useParams } from 'react-router-dom';
import { useParams, useHistory } from 'react-router-dom';

import { formatDateRange } from 'helpers';
import { useInstitutionIdQueryParam } from 'hooks';
import { initialPage, RequestStatus } from 'features/constants';
import { resetInstructorOptions } from 'features/Instructors/data/slice';
import { fetchInstructorsOptionsData } from 'features/Instructors/data/thunks';

import InstructorAvatar from 'features/Classes/InstructorAvatar';
import { Spinner } from '@edx/paragon';

import 'features/Classes/InstructorCard/index.scss';

const INSTRUCTORS_NUMBER = 3;

const InstructorCard = () => {
const InstructorCard = ({ previousPage }) => {
const dispatch = useDispatch();
const { classId } = useParams();
const { classId, courseId } = useParams();
const history = useHistory();
const institution = useSelector((state) => state.main.selectedInstitution);
const instructors = useSelector((state) => state.instructors.selectOptions.data);
const classes = useSelector((state) => state.classes.allClasses);
const classIdDecoded = decodeURIComponent(classId);

const addQueryParam = useInstitutionIdQueryParam();

const handleManageInstructorButton = () => {
history.push(addQueryParam(`/manage-instructors/${courseId}/${classId}?previous=${previousPage}`));
};

const isLoadingClasses = classes.status === RequestStatus.LOADING;

const [classInfo] = classes.data.filter(
Expand Down Expand Up @@ -77,7 +87,7 @@ const InstructorCard = () => {
</div>
<div className="separator" />
<div className="instructor-details">
<h4 className="text-color text-uppercase mb-3 h5">Instructor{instructors.length > 1 && 's'}</h4>
<h4 className="text-color text-uppercase mb-3 h5">Instructor{instructors?.length > 1 && 's'}</h4>
{isLoadingClasses && (
<div className="w-100 h-100 d-flex justify-content-center align-items-center">
<Spinner
Expand All @@ -89,7 +99,16 @@ const InstructorCard = () => {
)}
{!isLoadingClasses && (
<div className="d-flex align-items-center flex-wrap">
{classInfo?.instructors.length === 0 && <span>No instructor assigned</span>}
{classInfo?.instructors?.length === 0 && (
<Button
variant="outline-primary"
className="text-decoration-none text-primary bg-white p-2 px-3"
onClick={handleManageInstructorButton}
>
Assign instructor
</Button>
)}

{
classInfo?.instructors?.slice(0, INSTRUCTORS_NUMBER)?.map((instructor) => {
const instructorInfo = instructors.find((user) => user.instructorUsername === instructor);
Expand All @@ -107,16 +126,35 @@ const InstructorCard = () => {
}
</div>
)}

{classInfo?.instructors?.length > INSTRUCTORS_NUMBER && (
<div className="mt-2">
+
<span className="mx-1">{classInfo.instructors.slice(INSTRUCTORS_NUMBER).length}</span>
more...
</div>
)}

{classInfo?.instructors?.length > 0 && (
<Button
variant="tertiary"
className="text-decoration-underline text-primary bg-white p-2 px-0"
onClick={handleManageInstructorButton}
>
Manage instructor{instructors?.length > 1 && 's'}
</Button>
)}
</div>
</article>
);
};

InstructorCard.propTypes = {
previousPage: PropTypes.string,
};

InstructorCard.defaultProps = {
previousPage: 'courses',
};

export default InstructorCard;
1 change: 1 addition & 0 deletions src/features/Main/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ header.vertical-nav {
}
}

.text-decoration-underline,
a {
color: $hyperlink-color;
text-decoration: underline;
Expand Down

0 comments on commit 17bfa3a

Please sign in to comment.