Skip to content

Commit

Permalink
✨(frontend) build page teacher dashboard classrooms
Browse files Browse the repository at this point in the history
  • Loading branch information
rlecellier committed May 30, 2023
1 parent d0f1071 commit dc048de
Show file tree
Hide file tree
Showing 26 changed files with 378 additions and 40 deletions.
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;
Empty file.
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

0 comments on commit dc048de

Please sign in to comment.