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

✨(frontend) build page teacher dashboard classrooms #1984

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
import { capitalize } from 'lodash-es';
import { CourseMock } from 'api/mocks/joanie/courses';
import { CourseRun } from 'types/Joanie';
import { DashboardCard } from 'widgets/Dashboard/components/DashboardCard';
import SyllabusLink from 'widgets/Dashboard/components/SyllabusLink';
import { getCourseUrl } from 'widgets/Dashboard/utils/course';
import CourseRunLabel, {
CourseRunLabelVariantEnum,
} from 'widgets/Dashboard/components/CourseRunLabel';

const messages = defineMessages({
syllabusLinkLabel: {
defaultMessage: 'Access the course',
description: 'Message displayed in classrooms page for the syllabus link label',
id: 'components.TeacherCourseClassroomsDashboardLoader.syllabusLinkLabel',
},
classroomPeriod: {
defaultMessage: 'Session from {from} to {to}',
description: 'Message displayed in classrooms page for classroom period',
id: 'components.TeacherCourseClassroomsDashboardLoader.classroomPeriod',
},
});

interface StudentsSectionProps {
course: CourseMock;
courseRun: CourseRun;
}

const CourseRunSection = ({ course, courseRun }: StudentsSectionProps) => {
const intl = useIntl();

return (
<DashboardCard
header={
<div className="teacher-course-page__course-title__container-small dashboard-card__header__left">
<h2 className="dashboard__title-h1 teacher-course-page__course-title dashboard-card__header__left">
{capitalize(course.title)}
</h2>
<SyllabusLink href={getCourseUrl(course.code, intl)}>
<FormattedMessage {...messages.syllabusLinkLabel} />
</SyllabusLink>
</div>
}
expandable={false}
>
{courseRun && (
<span>
<CourseRunLabel courseRun={courseRun} variant={CourseRunLabelVariantEnum.DATE} />
</span>
)}
</DashboardCard>
);
};
export default CourseRunSection;
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { FormattedMessage, defineMessages } from 'react-intl';
import { DashboardCard } from 'widgets/Dashboard/components/DashboardCard';

const messages = defineMessages({
loading: {
defaultMessage: 'Loading classrooms ...',
description: 'Message displayed while loading a classrooms',
id: 'components.TeacherCourseClassroomsDashboardLoader.TeachersSection.loading',
},
studentsListTitle: {
defaultMessage: 'Learners registered for training',
description: 'Message displayed in classrooms page as students section title',
id: 'components.TeacherCourseClassroomsDashboardLoader.TeachersSection.studentsListTitle',
},
});

const StudentsSection = () => {
return (
<DashboardCard
header={
<div className="teacher-course-page__course-title__container-small">
<h2 className="dashboard__title-h1 teacher-course-page__course-title ">
<FormattedMessage {...messages.studentsListTitle} />
</h2>
</div>
}
expandable={false}
/>
);
};
export default StudentsSection;
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { FormattedMessage, defineMessages } from 'react-intl';
import { DashboardCard } from 'widgets/Dashboard/components/DashboardCard';

const messages = defineMessages({
loading: {
defaultMessage: 'Loading classrooms ...',
description: 'Message displayed while loading a classrooms',
id: 'components.TeacherCourseClassroomsDashboardLoader.StudentsSection.loading',
},
teacherListTitle: {
defaultMessage: 'Educational team',
description: 'Message displayed in classrooms page as teacher section title',
id: 'components.TeacherCourseClassroomsDashboardLoader.teacherListTitle',
},
});

const StudentsSection = () => {
return (
<DashboardCard
header={
<div className="teacher-course-page__course-title__container-small">
<h2 className="dashboard__title-h1 teacher-course-page__course-title ">
<FormattedMessage {...messages.teacherListTitle} />
</h2>
</div>
}
expandable={false}
/>
);
};
export default StudentsSection;
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { FormattedMessage, defineMessages } from 'react-intl';
import { useParams } from 'react-router-dom';

import { useMemo } from 'react';
import { DashboardLayout } from 'widgets/Dashboard/components/DashboardLayout';
import { TeacherCourseDashboardSidebar } from 'widgets/Dashboard/components/TeacherCourseDashboardSidebar';
import { useCourse } from 'hooks/useCourses';
import { Spinner } from 'components/Spinner';
import { CourseRun } from 'types/Joanie';
import CourseRunSection from './CourseRunSection';
import StudentsSection from './StudentsSection';
import TeachersSection from './TeachersSection';

const messages = defineMessages({
loading: {
defaultMessage: 'Loading classrooms ...',
description: 'Message displayed while loading a classrooms',
id: 'components.TeacherCourseClassroomsDashboardLoader.loading',
},
noCourseRun: {
defaultMessage: "This course run does't exist",
description: "Message displayed when requested classroom's course run doesn't exist",
id: 'components.TeacherCourseClassroomsDashboardLoader.noCourseRun',
},
});

export const TeacherCourseClassroomsDashboardLoader = () => {
const { courseCode, courseRunId } = useParams<{ courseCode: string; courseRunId: string }>();
const {
item: course,
states: { fetching },
} = useCourse(courseCode!);
const courseRun: CourseRun | undefined = useMemo(
() => course?.course_runs.find((courseCourseRun) => courseCourseRun.id === courseRunId),
[course, courseRunId],
);

return (
<DashboardLayout sidebar={<TeacherCourseDashboardSidebar />}>
{fetching && (
<Spinner aria-labelledby="loading-teacher-course-classroom-data">
<span id="loading-teacher-course-classroom-data">
<FormattedMessage {...messages.loading} />
</span>
</Spinner>
)}

{!fetching && courseRun === undefined && (
<p>
<FormattedMessage {...messages.noCourseRun} />
</p>
)}

{!fetching && courseRun !== undefined && (
<div className="teacher-classroom-page">
<DashboardLayout.Section>
<CourseRunSection course={course} courseRun={courseRun} />
</DashboardLayout.Section>

<DashboardLayout.Section>
<StudentsSection />
</DashboardLayout.Section>

<DashboardLayout.Section>
<TeachersSection />
</DashboardLayout.Section>
</div>
)}
</DashboardLayout>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,22 @@ import { render, screen } from '@testing-library/react';
import { IntlProvider } from 'react-intl';
import { CunninghamProvider } from '@openfun/cunningham-react';
import { capitalize } from 'lodash-es';
import { CourseRunFactory } from 'utils/test/factories/joanie';
import { CourseFactory, CourseRunFactory } from 'utils/test/factories/joanie';
import CourseRunList from '.';

describe('pages/TeacherCourseDashboardLoader/CourseRunList', () => {
it('should render', () => {
const courseRuns = CourseRunFactory().many(2);
const course = CourseFactory({
course_runs: CourseRunFactory().many(2),
}).one();
render(
<IntlProvider locale="en">
<CunninghamProvider>
<CourseRunList courseRuns={courseRuns} />
<CourseRunList courseRuns={course.course_runs} courseCode={course.code} />
</CunninghamProvider>
</IntlProvider>,
);
const [courseRunOne, courseRunTwo] = courseRuns;
const [courseRunOne, courseRunTwo] = course.course_runs;
expect(screen.getByTitle(capitalize(courseRunOne.title))).toBeInTheDocument();
expect(screen.getByTitle(capitalize(courseRunTwo.title))).toBeInTheDocument();
expect(screen.getAllByRole('button', { name: 'go to classroom' }).length).toEqual(2);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,27 @@
import { useIntl } from 'react-intl';
import { DataList } from '@openfun/cunningham-react';
import { useNavigate } from 'react-router-dom';
import { CourseRun } from 'types/Joanie';

import { CourseMock } from 'api/mocks/joanie/courses';
import { buildCourseRunData } from './utils';

interface CourseRunListProps {
courseCode: CourseMock['code'];
courseRuns: CourseRun[];
}

const CourseRunList = ({ courseRuns }: CourseRunListProps) => {
const CourseRunList = ({ courseCode, courseRuns }: CourseRunListProps) => {
const intl = useIntl();
const navigate = useNavigate();
const columns = ['title', 'period', 'status', 'action'].map((field: string) => ({ field }));

return (
<div className="teacher-dashboard-course-run-list">
<DataList columns={columns} rows={buildCourseRunData(intl, courseRuns)} />
<DataList
columns={columns}
rows={buildCourseRunData(intl, navigate, courseCode, courseRuns)}
/>
</div>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import { IntlProvider, createIntl } from 'react-intl';
import { render, screen } from '@testing-library/react';
import { capitalize } from 'lodash-es';
import { NavigateFunction, To } from 'react-router-dom';
import { CourseRunFactory } from 'utils/test/factories/joanie';
import { CourseMock } from 'api/mocks/joanie/courses';
import { buildCourseRunData, messages } from './utils';

describe('pages/TeacherCourseDashboardLoader/CourseRunList/buildCourseRunData', () => {
const navigate: NavigateFunction = (to: To | number) => { }; // eslint-disable-line
const courseCode: CourseMock['code'] = 'akeuj';
it('should return the right keys', () => {
const courseRunList = CourseRunFactory().many(1);
const intl = createIntl({ locale: 'en' });
const listData = buildCourseRunData(intl, courseRunList);
const listData = buildCourseRunData(intl, navigate, courseCode, courseRunList);
expect(listData.length).toBe(1);

const listItem = listData[0];
Expand All @@ -17,7 +21,7 @@ describe('pages/TeacherCourseDashboardLoader/CourseRunList/buildCourseRunData',
it('should contain a valid title', () => {
const courseRun = CourseRunFactory().one();
const intl = createIntl({ locale: 'en' });
const listItem = buildCourseRunData(intl, [courseRun])[0];
const listItem = buildCourseRunData(intl, navigate, courseCode, [courseRun])[0];

render(listItem.title);
expect(screen.getByText(capitalize(courseRun.title), { exact: false })).toBeInTheDocument();
Expand All @@ -26,7 +30,7 @@ describe('pages/TeacherCourseDashboardLoader/CourseRunList/buildCourseRunData',
it('should contain a valid period', () => {
const courseRun = CourseRunFactory().one();
const intl = createIntl({ locale: 'en' });
const listItem = buildCourseRunData(intl, [courseRun])[0];
const listItem = buildCourseRunData(intl, navigate, courseCode, [courseRun])[0];

render(listItem.period);
expect(
Expand All @@ -39,15 +43,15 @@ describe('pages/TeacherCourseDashboardLoader/CourseRunList/buildCourseRunData',
it('should contain a valid status', () => {
const courseRun = CourseRunFactory().one();
const intl = createIntl({ locale: 'en' });
const listItem = buildCourseRunData(intl, [courseRun])[0];
const listItem = buildCourseRunData(intl, navigate, courseCode, [courseRun])[0];

render(listItem.status);
expect(screen.getByText(courseRun.state.text, { exact: false })).toBeInTheDocument();
});
it('should contain a valid action', () => {
const courseRun = CourseRunFactory().one();
const intl = createIntl({ locale: 'en' });
const listItem = buildCourseRunData(intl, [courseRun])[0];
const listItem = buildCourseRunData(intl, navigate, courseCode, [courseRun])[0];

render(<IntlProvider locale="en">{listItem.action}</IntlProvider>);
expect(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { FormattedMessage, IntlShape, defineMessages } from 'react-intl';
import { capitalize } from 'lodash-es';
import { Button } from '@openfun/cunningham-react';
import { NavigateFunction } from 'react-router-dom';
import { IconTypeEnum } from 'components/Icon';
import { CourseStateTextEnum, Priority } from 'types';
import { CourseRun } from 'types/Joanie';
import { getDashboardRoutePath } from 'widgets/Dashboard/utils/dashboardRoutes';
import { TeacherDashboardPaths } from 'widgets/Dashboard/utils/teacherRouteMessages';
import { CourseMock } from 'api/mocks/joanie/courses';
import CourseRunListCell from './CourseRunListCell';

export const messages = defineMessages({
Expand All @@ -19,7 +23,13 @@ export const messages = defineMessages({
},
});

export const buildCourseRunData = (intl: IntlShape, courseRuns: CourseRun[]) => {
export const buildCourseRunData = (
intl: IntlShape,
navigate: NavigateFunction,
courseCode: CourseMock['code'],
courseRuns: CourseRun[],
) => {
const getRoutePath = getDashboardRoutePath(intl);
const CourseStateIconMap: Record<CourseStateTextEnum, IconTypeEnum> = {
[CourseStateTextEnum.CLOSING_ON]: IconTypeEnum.MORE,
[CourseStateTextEnum.STARTING_ON]: IconTypeEnum.CHECK_ROUNDED,
Expand Down Expand Up @@ -67,7 +77,18 @@ export const buildCourseRunData = (intl: IntlShape, courseRuns: CourseRun[]) =>
),
action: (
<CourseRunListCell variant={CourseRunListCell.ALIGN_RIGHT}>
<Button size="small" color="secondary">
<Button
size="small"
color="secondary"
onClick={() =>
navigate(
getRoutePath(TeacherDashboardPaths.COURSE_CLASSROOMS, {
courseCode,
courseRunId: courseRun.id,
}),
)
}
>
<FormattedMessage {...messages.dataCourseRunLink} />
</Button>
</CourseRunListCell>
Expand Down
15 changes: 13 additions & 2 deletions src/frontend/js/pages/TeacherCourseDashboardLoader/_styles.scss
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
.teacher-course-page {
&__course-title {
color: r-color(secondary-900);
margin: rem-calc(17px) 0 rem-calc(22px);
font-size: rem-calc(18px);
font-weight: bold;
display: flex;
align-items: center;
margin: 0;

&__text {
margin-left: rem-calc(8px);
}

&__container,
&__container-small {
margin: rem-calc(17px) 0 rem-calc(22px);
display: flex;
align-items: center;
}

&__container-small {
margin: rem-calc(9px) 0;
}
}
}
Loading