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 16, 2023
1 parent bc329b4 commit e340b88
Show file tree
Hide file tree
Showing 18 changed files with 251 additions and 28 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
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={
<h2 className="teacher-course-page__course-title">
<FormattedMessage {...messages.teacherListTitle} />
</h2>
}
expandable={false}
/>
);
};
export default StudentsSection;
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
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 TeachersSection = () => {
return (
<DashboardCard
header={
<h2 className="teacher-course-page__course-title">
<FormattedMessage {...messages.studentsListTitle} />
</h2>
}
expandable={false}
/>
);
};
export default TeachersSection;
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
import { useParams } from 'react-router-dom';

import { capitalize } from 'lodash-es';
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 { DashboardCard } from 'widgets/Dashboard/components/DashboardCard';
import { Icon, IconTypeEnum } from 'components/Icon';
import { CourseRun } from 'types/Joanie';
import SyllabusLink from 'widgets/Dashboard/components/SyllabusLink';
import { getCourseUrl } from 'widgets/Dashboard/utils/course';
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',
},
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',
},
});

export const TeacherCourseClassroomsDashboardLoader = () => {
const intl = useIntl();
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-courses-data">
<span id="loading-courses-data">
<FormattedMessage {...messages.loading} />
</span>
</Spinner>
) : (
<>
<DashboardLayout.Section>
<DashboardCard
header={
<h2 className="teacher-course-page__course-title">
<span className="teacher-course-page__course-title__text">
{capitalize(course.title)}
</span>

<SyllabusLink href={getCourseUrl(course.code, intl)}>
<FormattedMessage {...messages.syllabusLinkLabel} />
</SyllabusLink>
</h2>
}
expandable={false}
>
{courseRun && (
<>
<Icon name={IconTypeEnum.CAMERA} />
<FormattedMessage
{...messages.classroomPeriod}
values={{
from: intl.formatDate(new Date(courseRun.start)),
to: intl.formatDate(new Date(courseRun.end)),
}}
/>
</>
)}
</DashboardCard>
</DashboardLayout.Section>

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

<DashboardLayout.Section>
<TeachersSection />
</DashboardLayout.Section>
</>
)}
</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
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export const TeacherCourseDashboardLoader = () => {
expandable={false}
fullWidth
>
<CourseRunList courseRuns={course.course_runs} />
<CourseRunList courseCode={course.code} courseRuns={course.course_runs} />
</DashboardCard>
</div>
)}
Expand Down
4 changes: 2 additions & 2 deletions src/frontend/js/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const REACT_QUERY_SETTINGS = {
throttleTime: 500,
},
// Cache is garbage collected after this delay
cacheTime: 24 * 60 * 60 * 1000, // 24h in ms
cacheTime: 0, // 24 * 60 * 60 * 1000, // 24h in ms
// Data are considered as stale after this delay
staleTimes: {
default: 0, // Stale immediately
Expand All @@ -35,4 +35,4 @@ export const PAYMENT_SETTINGS = {
pollLimit: 30,
};

export const MOCK_SERVICE_WORKER_ENABLED = false;
export const MOCK_SERVICE_WORKER_ENABLED = true;
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ $sidebar-spacing-top: $dashboard-breadcrumb-height + $dashboard-filters-height +
}
}

&__section {
margin-top: rem-calc(20px);
&:first-child {
margin-top: 0;
}
}

&__filters {
min-height: $dashboard-filters-height;
margin-top: $dashboard-breadcrumb-filters-spacing;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,7 @@ export const DashboardLayout = ({ children, sidebar, filters }: DashboardLayoutP
</DashboardBreadcrumbsProvider>
);
};

DashboardLayout.Section = ({ children }: PropsWithChildren) => (
<div className="dashboard__section">{children}</div>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { PropsWithChildren } from 'react';
import { Icon, IconTypeEnum } from 'components/Icon';

interface SyllabusLinkProps extends PropsWithChildren {
href: string;
}

const SyllabusLink = ({ href, children }: SyllabusLinkProps) => (
<a className="syllabus-link" href={href}>
<Icon name={IconTypeEnum.LOGOUT_SQUARE} />
<span>{children}</span>
</a>
);

export default SyllabusLink;
Loading

0 comments on commit e340b88

Please sign in to comment.