From 56e07bc2ef867df7d9e6458bb72899a8eae74faa Mon Sep 17 00:00:00 2001 From: Primakov Alexandr Alexandrovich Date: Wed, 6 Nov 2024 12:35:55 +0300 Subject: [PATCH] attendance table --- ijl.config.js | 3 +- src/components/error-boundary/index.tsx | 26 ++++ src/dashboard.tsx | 37 +++-- src/pages/attendance/attendance.tsx | 89 ++++++++++++ src/pages/attendance/index.ts | 3 + src/pages/course-list/course-card.tsx | 176 +++++++++++++---------- src/pages/course-list/course-details.tsx | 167 +++++++++++---------- src/pages/index.ts | 1 + src/pages/lesson-list/lesson-list.tsx | 18 ++- 9 files changed, 341 insertions(+), 179 deletions(-) create mode 100644 src/components/error-boundary/index.tsx create mode 100644 src/pages/attendance/attendance.tsx create mode 100644 src/pages/attendance/index.ts diff --git a/ijl.config.js b/ijl.config.js index 950a255..75d6bbd 100644 --- a/ijl.config.js +++ b/ijl.config.js @@ -10,7 +10,8 @@ module.exports = { navigations: { 'journal.main': '/journal.pl', 'exam.main': '/exam', - 'link.exam.details': '/details/:courseId/:examId' + 'link.exam.details': '/details/:courseId/:examId', + 'link.journal.attendance': '/attendance/:courseId', }, features: { journal: { diff --git a/src/components/error-boundary/index.tsx b/src/components/error-boundary/index.tsx new file mode 100644 index 0000000..1f72b55 --- /dev/null +++ b/src/components/error-boundary/index.tsx @@ -0,0 +1,26 @@ +import { Alert } from '@chakra-ui/react' +import React from 'react' + +export class ErrorBoundary extends React.Component< + React.PropsWithChildren, + { hasError: boolean, error?: string } +> { + state = { hasError: false, error: null } + + static getDerivedStateFromError(error: Error) { + return { hasError: true, error: error.message } + } + + render() { + if (this.state.hasError) { + return ( + + Что-то пошло не так
+ {this.state.error && {this.state.error}} +
+ ) + } + + return this.props.children + } +} diff --git a/src/dashboard.tsx b/src/dashboard.tsx index 0e4cf04..773c548 100644 --- a/src/dashboard.tsx +++ b/src/dashboard.tsx @@ -9,23 +9,28 @@ import { LessonDetailsPage, LessonListPage, UserPage, + AttendancePage, } from './pages' +import { ErrorBoundary } from './components/error-boundary' const Wrapper = ({ children }: { children: React.ReactElement }) => ( - - - - - + + + + + + + + + } > {children} @@ -67,6 +72,14 @@ export const Dashboard = ({ store }) => ( } /> + + + + } + /> ) diff --git a/src/pages/attendance/attendance.tsx b/src/pages/attendance/attendance.tsx new file mode 100644 index 0000000..a8807a0 --- /dev/null +++ b/src/pages/attendance/attendance.tsx @@ -0,0 +1,89 @@ +import React, { useMemo } from 'react' +import { useParams } from 'react-router-dom' +import styled from '@emotion/styled' + +import { api } from '../../__data__/api/api' +import { PageLoader } from '../../components/page-loader/page-loader' +import { Box, Container, Heading } from '@chakra-ui/react' +import dayjs from 'dayjs' + +export const Attendance = () => { + const { courseId } = useParams() + const { data: attendance, isLoading } = api.useLessonListQuery(courseId, { + selectFromResult: ({ data, isLoading }) => ({ + data: data?.body, + isLoading, + }), + }) + const { data: courseInfo, isLoading: courseInfoIssLoading } = + api.useGetCourseByIdQuery(courseId) + + const data = useMemo(() => { + if (!attendance) return null + + const studentsMap = new Map() + + attendance.forEach((lesson) => { + lesson.students.forEach((student) => { + studentsMap.set(student.sub, { + ...student, + value: + student.family_name && student.given_name + ? `${student.family_name} ${student.given_name}` + : student.name || student.email, + }) + }) + }) + const compare = Intl.Collator('ru').compare + + const students = [...studentsMap.values()] + students.sort(({ family_name: name }, { family_name: nname }) => + compare(name, nname), + ) + return { + students, + } + }, [attendance]) + + if (!data || isLoading || courseInfoIssLoading) { + return + } + + return ( + + + {courseInfo.name} + + + + + + + + {data.students.map((student) => ( + + ))} + + + + {attendance.map((lesson, index) => ( + + + + {data.students.map((st) => { + const wasThere = + lesson.students.findIndex((u) => u.sub === st.sub) !== -1 + return + })} + + ))} + +
ДатаНазвание занятия{student.name}
{dayjs(lesson.date).format('DD.MM.YYYY')}{lesson.name}{wasThere ? '+' : '-'}
+
+ {/*
{JSON.stringify(attendance, null, 2)}
*/} +
+ ) +} diff --git a/src/pages/attendance/index.ts b/src/pages/attendance/index.ts new file mode 100644 index 0000000..89790d1 --- /dev/null +++ b/src/pages/attendance/index.ts @@ -0,0 +1,3 @@ +import { Attendance } from './attendance' + +export default Attendance diff --git a/src/pages/course-list/course-card.tsx b/src/pages/course-list/course-card.tsx index 81e6c6a..1641baa 100644 --- a/src/pages/course-list/course-card.tsx +++ b/src/pages/course-list/course-card.tsx @@ -1,20 +1,20 @@ import React, { useCallback, useEffect, useState } from 'react' import dayjs from 'dayjs' -import { Link as ConnectedLink } from 'react-router-dom' +import { Link as ConnectedLink, generatePath } from 'react-router-dom' import { getNavigationsValue } from '@brojs/cli' import { - Box, - CardHeader, - CardBody, - CardFooter, - ButtonGroup, - Stack, - StackDivider, - Button, - Card, - Heading, - Tooltip, - Spinner, + Box, + CardHeader, + CardBody, + CardFooter, + ButtonGroup, + Stack, + StackDivider, + Button, + Card, + Heading, + Tooltip, + Spinner, } from '@chakra-ui/react' import { api } from '../../__data__/api/api' @@ -22,72 +22,94 @@ import { ArrowUpIcon, LinkIcon } from '@chakra-ui/icons' import { Course } from '../../__data__/model' import { CourseDetails } from './course-details' -export const CourseCard = ({ - course, -}: { - course: Course -}) => { - const [getLessonList, populatedCourse] = api.useLazyGetCourseByIdQuery() - const [isOpened, setIsOpened] = useState(false) - useEffect(() => { - if (isOpened) { - getLessonList(course.id, true) - } - }, [isOpened]) +export const CourseCard = ({ course }: { course: Course }) => { + const [getLessonList, populatedCourse] = api.useLazyGetCourseByIdQuery() + const [isOpened, setIsOpened] = useState(false) + useEffect(() => { + if (isOpened) { + getLessonList(course.id, true) + } + }, [isOpened]) - const handleToggleOpene = useCallback(() => { - setIsOpened(opened => !opened) - }, [setIsOpened]) + const handleToggleOpene = useCallback(() => { + setIsOpened((opened) => !opened) + }, [setIsOpened]) - return ( - - - - {course.name} - - - {isOpened && ( - - } spacing="8px"> - - {`Дата начала курса - ${dayjs(course.startDt).format('DD MMMM YYYYг.')}`} - - - Количество занятий - {course.lessons.length} - + return ( + + + + {course.name} + + + {isOpened && ( + + } spacing="8px"> + + {`Дата начала курса - ${dayjs(course.startDt).format('DD MMMM YYYYг.')}`} + + + Количество занятий - {course.lessons.length} + - {populatedCourse.isFetching && } - {!populatedCourse.isFetching && populatedCourse.isSuccess && } - - + {populatedCourse.isFetching && } + {!populatedCourse.isFetching && populatedCourse.isSuccess && ( + )} - - - - - - - - - - - - ) + + + + + + + )} + + + + + + + + + + + + ) } diff --git a/src/pages/course-list/course-details.tsx b/src/pages/course-list/course-details.tsx index 675f679..e2d6ae4 100644 --- a/src/pages/course-list/course-details.tsx +++ b/src/pages/course-list/course-details.tsx @@ -2,14 +2,7 @@ import React from 'react' import dayjs from 'dayjs' import { Link as ConnectedLink } from 'react-router-dom' import { getNavigationsValue, getHistory } from '@brojs/cli' -import { - Stack, - Heading, - Link, - Button, - Tooltip, - Box, -} from '@chakra-ui/react' +import { Stack, Heading, Link, Button, Tooltip, Box } from '@chakra-ui/react' import { useAppSelector } from '../../__data__/store' import { isTeacher } from '../../utils/user' @@ -18,82 +11,98 @@ import { api } from '../../__data__/api/api' import { LinkIcon } from '@chakra-ui/icons' type CourseDetailsProps = { - populatedCourse: PopulatedCourse; + populatedCourse: PopulatedCourse } const history = getHistory() export const CourseDetails = ({ populatedCourse }: CourseDetailsProps) => { - const user = useAppSelector((s) => s.user) - const exam = populatedCourse.examWithJury - const [toggleExamWithJury, examWithJuryRequest] = api.useToggleExamWithJuryMutation() + const user = useAppSelector((s) => s.user) + const exam = populatedCourse.examWithJury + const [toggleExamWithJury, examWithJuryRequest] = + api.useToggleExamWithJuryMutation() - return ( + return ( + <> + {isTeacher(user) && ( + + Экзамен: {exam?.name}{' '} + {exam && ( + + + + )} + + )} + {!Boolean(exam) && ( <> - - Экзамен: {exam?.name} {exam && - - } - - {!Boolean(exam) && ( - <> - - Не задан - - - - - - - - )} - {Boolean(exam) && ( - <> - - Количество членов жюри: - - - {populatedCourse.examWithJury.jury.length} - - - )} - - Список занятий: - - - {populatedCourse?.lessons?.map((lesson) => ( - - {lesson.name} - - ))} - + + Не задан + + + + + + - ) -} \ No newline at end of file + )} + {Boolean(exam) && ( + <> + + Количество членов жюри: + + + {populatedCourse.examWithJury.jury.length} + + + )} + + Список занятий: + + + {populatedCourse?.lessons?.map((lesson) => ( + + {lesson.name} + + ))} + + + ) +} diff --git a/src/pages/index.ts b/src/pages/index.ts index 640cc52..c4b1092 100644 --- a/src/pages/index.ts +++ b/src/pages/index.ts @@ -4,3 +4,4 @@ export const CourseListPage = lazy(() => import(/* webpackChunkName: "course-lis export const LessonDetailsPage = lazy(() => import(/* webpackChunkName: "lesson-details" */ './lesson-details')); export const LessonListPage = lazy(() => import(/* webpackChunkName: "lesson-list" */ './lesson-list')); export const UserPage = lazy(() => import(/* webpackChunkName: "user-page" */ './user-page')); +export const AttendancePage = lazy(() => import(/* webpackChunkName: "attendance-page" */ './attendance')); diff --git a/src/pages/lesson-list/lesson-list.tsx b/src/pages/lesson-list/lesson-list.tsx index d5ddd3d..1ae080e 100644 --- a/src/pages/lesson-list/lesson-list.tsx +++ b/src/pages/lesson-list/lesson-list.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useMemo, useRef, useState } from 'react' import dayjs from 'dayjs' -import { Link, useParams } from 'react-router-dom' +import { generatePath, Link, useParams } from 'react-router-dom' import { getNavigationsValue, getFeatures } from '@brojs/cli' import { Breadcrumb, @@ -246,15 +246,13 @@ const LessonList = () => { nameButton={editLesson ? 'Редактировать' : 'Создать'} /> ) : ( - - - + )} )}