diff --git a/package-lock.json b/package-lock.json
index 82c172c..364ee9d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,6 +9,7 @@
"version": "1.2.0",
"license": "MIT",
"dependencies": {
+ "@chakra-ui/icons": "^2.1.1",
"@chakra-ui/react": "^2.8.2",
"@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.0",
@@ -24,6 +25,7 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-helmet": "^6.1.0",
+ "react-hook-form": "^7.51.2",
"react-redux": "^9.1.0",
"react-router-dom": "^6.22.1",
"redux": "^5.0.1",
@@ -2213,6 +2215,18 @@
"react": ">=18"
}
},
+ "node_modules/@chakra-ui/icons": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/@chakra-ui/icons/-/icons-2.1.1.tgz",
+ "integrity": "sha512-3p30hdo4LlRZTT5CwoAJq3G9fHI0wDc0pBaMHj4SUn0yomO+RcDRlzhdXqdr5cVnzax44sqXJVnf3oQG0eI+4g==",
+ "dependencies": {
+ "@chakra-ui/icon": "3.2.0"
+ },
+ "peerDependencies": {
+ "@chakra-ui/system": ">=2.0.0",
+ "react": ">=18"
+ }
+ },
"node_modules/@chakra-ui/image": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@chakra-ui/image/-/image-2.1.0.tgz",
@@ -8546,6 +8560,21 @@
"react": ">=16.3.0"
}
},
+ "node_modules/react-hook-form": {
+ "version": "7.51.2",
+ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.51.2.tgz",
+ "integrity": "sha512-y++lwaWjtzDt/XNnyGDQy6goHskFualmDlf+jzEZvjvz6KWDf7EboL7pUvRCzPTJd0EOPpdekYaQLEvvG6m6HA==",
+ "engines": {
+ "node": ">=12.22.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/react-hook-form"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17 || ^18"
+ }
+ },
"node_modules/react-i18next": {
"version": "11.8.5",
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-11.8.5.tgz",
diff --git a/package.json b/package.json
index fc3daf7..4d87616 100644
--- a/package.json
+++ b/package.json
@@ -20,6 +20,7 @@
"@ijl/cli": "^5.0.3"
},
"dependencies": {
+ "@chakra-ui/icons": "^2.1.1",
"@chakra-ui/react": "^2.8.2",
"@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.0",
@@ -35,6 +36,7 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-helmet": "^6.1.0",
+ "react-hook-form": "^7.51.2",
"react-redux": "^9.1.0",
"react-router-dom": "^6.22.1",
"redux": "^5.0.1",
diff --git a/src/app.tsx b/src/app.tsx
index 40c795c..ebe2ca9 100644
--- a/src/app.tsx
+++ b/src/app.tsx
@@ -4,6 +4,7 @@ import { Global, css } from '@emotion/react'
import { BrowserRouter } from 'react-router-dom';
import ruLocale from 'dayjs/locale/ru';
import dayjs from 'dayjs';
+import { ChakraProvider } from '@chakra-ui/react'
import { Dashboard } from './dashboard';
@@ -11,72 +12,74 @@ dayjs.locale('ru', ruLocale);
const App = ({ store }) => {
return(
-
-
-
- Журнал
-
-
+
+
+
+ Журнал
+
+
-
-
+ @font-face {
+ font-family: 'KiyosunaSans';
+ src: url('${__webpack_public_path__ + '/remote-assets/KiyosunaSans/KiyosunaSans-B.otf'}');
+ font-weight: normal;
+ font-style: normal;
+ }
+ @font-face {
+ font-family: 'KiyosunaSans';
+ src: url('${__webpack_public_path__ + '/remote-assets/KiyosunaSans/KiyosunaSans-L.otf'}');
+ font-weight: lighter;
+ font-style: normal;
+ }
+ @font-face {
+ font-family: 'RFKrabuler';
+ src: url('${__webpack_public_path__ + '/remote-assets/RF-Krabuler/WEB/RFKrabuler-Regular.eot'}');
+ src:
+ url('${__webpack_public_path__ + '/remote-assets/RF-Krabuler/WEB/RFKrabuler-Regular.woff2'}') format('woff2'),
+ url('${__webpack_public_path__ + '/remote-assets/RF-Krabuler/WEB/RFKrabuler-Regular.woff'}') format('woff'),
+ url('${__webpack_public_path__ + '/remote-assets/RF-Krabuler/TTF/RF-Krabuler-Regular.ttf'}') format('truetype');
+ font-weight: normal;
+ font-style: normal;
+ }
+ `}
+ />
+
+
+
)
}
diff --git a/src/dashboard.tsx b/src/dashboard.tsx
index e5631d8..1927709 100644
--- a/src/dashboard.tsx
+++ b/src/dashboard.tsx
@@ -1,34 +1,82 @@
-import React, { useEffect, Suspense } from "react";
-import { Routes, Route, useNavigate } from "react-router-dom";
-import { Provider } from "react-redux";
+import React, { useEffect, Suspense } from 'react'
+import { Routes, Route, useNavigate } from 'react-router-dom'
+import { Provider } from 'react-redux'
import {
CourseListPage,
LessonDetailsPage,
LessonListPage,
- UserPage
+ UserPage,
} from './pages'
-import { getNavigationsValue } from "@ijl/cli";
+import { getNavigationsValue } from '@ijl/cli'
+import { Box, Container, Spinner, VStack } from '@chakra-ui/react'
const Redirect = ({ path }) => {
- const navigate = useNavigate();
+ const navigate = useNavigate()
useEffect(() => {
- navigate(path);
- }, []);
+ navigate(path)
+ }, [])
- return null;
-};
+ return null
+}
-const Wrapper = ({ children }) => {children}
+const Wrapper = ({ children }) => (
+
+
+
+
+
+
+ }
+ >
+ {children}
+
+)
export const Dashboard = ({ store }) => (
- } />
- } />
- } />
- } />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
-);
+)
diff --git a/src/pages/course-list.tsx b/src/pages/course-list.tsx
index ad94b89..6b0b1fa 100644
--- a/src/pages/course-list.tsx
+++ b/src/pages/course-list.tsx
@@ -1,8 +1,7 @@
import React, { useCallback, useEffect, useRef, useState } from 'react'
import dayjs from 'dayjs'
-import { Link } from 'react-router-dom'
-import { getConfigValue, getNavigationsValue } from '@ijl/cli'
-
+import { Link as ConnectedLink } from 'react-router-dom'
+import { getNavigationsValue } from '@ijl/cli'
import {
Box,
CardHeader,
@@ -12,155 +11,299 @@ import {
Stack,
StackDivider,
Button,
- UnorderedList,
+ Card,
Heading,
Tooltip,
+ Spinner,
+ Container,
+ VStack,
+ Link,
+ Input,
+ CloseButton,
+ FormControl,
+ FormLabel,
+ FormHelperText,
+ Center,
+ FormErrorMessage,
+ useToast,
} from '@chakra-ui/react'
+import { useForm, Controller } from 'react-hook-form'
-import {
- ArrowImg,
- IconButton,
- InputElement,
- InputLabel,
- InputWrapper,
- StartWrapper,
- Papper,
- ErrorSpan,
- Cross,
- AddButton,
- MainWrapper,
- StyledCard,
-} from './style'
+import { ErrorSpan, MainWrapper } from './style'
-import { linkOpen, moreDetails } from '../assets'
-
-import arrow from '../assets/36-arrow-right.svg'
-import { keycloak } from '../__data__/kc'
import { useAppSelector } from '../__data__/store'
import { api } from '../__data__/api/api'
import { isTeacher } from '../utils/user'
+import { AddIcon, ArrowDownIcon, ArrowUpIcon, LinkIcon } from '@chakra-ui/icons'
+
+interface NewCourseForm {
+ startDt: string
+ name: string
+}
const CoursesList = () => {
+ const toast = useToast()
const user = useAppSelector((s) => s.user)
- const { data, isLoading, error } = api.useCoursesListQuery()
- const [createCourse, crcQuery] = api.useCreateUpdateCourseMutation()
- const [value, setValue] = useState('')
+ const { data, isLoading } = api.useCoursesListQuery()
+ const [createUpdateCourse, crucQuery] = api.useCreateUpdateCourseMutation()
const [showForm, setShowForm] = useState(false)
- const [showDetails, setShowDetails] = useState(false)
+ const [courseDetailsOpenedId, setCourseDetailsOpenedId] = useState<
+ string | null
+ >(null)
+ const toastRef = useRef(null)
- const handleChange = useCallback(
- (event) => {
- setValue(event.target.value.toUpperCase())
+ const {
+ control,
+ handleSubmit,
+ reset,
+ formState: { errors },
+ getValues,
+ } = useForm({
+ defaultValues: {
+ startDt: '',
+ name: '',
},
- [setValue],
- )
- const handleSubmit = useCallback(
- (event) => {
- event.preventDefault()
- createCourse({ name: value })
- },
- [value],
- )
+ })
+
+ const onSubmit = ({ startDt, name }) => {
+ toastRef.current = toast({
+ title: 'Отправляем',
+ status: 'loading',
+ duration: 9000,
+ })
+ createUpdateCourse({ name, startDt })
+ }
useEffect(() => {
- if (crcQuery.isSuccess) {
- setValue('')
+ if (crucQuery.isSuccess) {
+ const values = getValues()
+ if (toastRef.current) {
+ toast.update(toastRef.current, {
+ title: 'Курс создан.',
+ description: `Курс ${values.name} успешно создан`,
+ status: 'success',
+ duration: 9000,
+ isClosable: true,
+ })
+ }
+ reset()
}
- }, [crcQuery.isSuccess])
+ }, [crucQuery.isSuccess])
+
+ if (isLoading) {
+ return (
+
+
+
+
+
+ )
+ }
return (
-
-
- {isTeacher(user) && (
- <>
- {showForm ? (
-
- setShowForm(false)}>
- X
-
-
-
+
+
+ ) : (
+
+ }
+ colorScheme="green"
+ onClick={() => setShowForm(true)}
+ >
+ Добавить
+
+
+ )}
+
+ )}
+
+ {data?.body?.map((c) => (
+
+ courseDetailsOpenedId === c._id
+ ? setCourseDetailsOpenedId(null)
+ : setCourseDetailsOpenedId(c._id)
+ }
+ />
+ ))}
+
+
+ )
+}
+
+const CourseCard = ({ course, isOpened, openDetails }) => {
+ const [getLessonList, lessonList] = api.useLazyLessonListQuery()
+ useEffect(() => {
+ if (isOpened) {
+ getLessonList(course._id, true)
+ }
+ }, [isOpened])
+ const user = useAppSelector((s) => s.user)
+
+ return (
+
+
+
+ {course.name}
+
+
+ {isOpened && (
+
+ } spacing="8px">
+
+ {`Дата начала курса - ${dayjs(course.startDt).format('DD MMMM YYYYг.')}`}
+
+
+ Количество занятий - {course.lessons.length}
+
+ {lessonList.isFetching ? (
+
) : (
- setShowForm(true)}>Добавить
- )}
- >
- )}
-
- {data?.body?.map((course) => (
-
-
-
- {course.name}
+ <>
+
+ Список занятий:
-
- {showDetails && (
-
- } spacing="8px">
-
- {`Дата начала курса - ${dayjs(course.startDt).format('DD MMMM YYYYг.')}`}
-
-
- Количество занятий - {course.lessons.length}
-
-
-
- )}
-
-
-
-
-
-
-
-
-
-
-
- ))}
-
-
-
+ {lesson.name}
+
+ ))}
+
+ >
+ )}
+
+
+ )}
+
+
+
+ }
+ as={ConnectedLink}
+ colorScheme="blue"
+ to={`${getNavigationsValue('journal.main')}/lessons-list/${course._id}`}
+ >
+ Открыть
+
+
+
+ : }
+ loadingText="Загрузка"
+ isLoading={lessonList.isFetching}
+ onClick={openDetails}
+ >
+ Просмотреть детали
+
+
+
+
+
)
}
diff --git a/src/pages/lesson-details.tsx b/src/pages/lesson-details.tsx
index 34c5d35..c88f250 100644
--- a/src/pages/lesson-details.tsx
+++ b/src/pages/lesson-details.tsx
@@ -84,18 +84,18 @@ const LessonDetail = () => {
-
+
Журнал
-
+
-
Курс
-
+
@@ -103,8 +103,10 @@ const LessonDetail = () => {
- Тема занятия - {accessCode?.body?.lesson?.name}
-
+
+ Тема занятия - {accessCode?.body?.lesson?.name}
+
+
{dayjs(accessCode?.body?.lesson?.date).format('DD MMMM YYYYг.')}{' '}
Отмечено - {accessCode?.body?.lesson?.students?.length}{' '}
{AllStudents.isSuccess ? `/ ${AllStudents?.data?.body?.length}` : ''}{' '}
diff --git a/src/pages/lesson-list.tsx b/src/pages/lesson-list.tsx
index f6e8562..a14eaab 100644
--- a/src/pages/lesson-list.tsx
+++ b/src/pages/lesson-list.tsx
@@ -63,7 +63,7 @@ const LessonList = () => {
- Журнал
+ Журнал
diff --git a/stubs/api/index.js b/stubs/api/index.js
index 40f2b46..0e91787 100644
--- a/stubs/api/index.js
+++ b/stubs/api/index.js
@@ -2,6 +2,13 @@ const router = require('express').Router()
const fs = require('node:fs')
const path = require('node:path')
+const timer =
+ (time = 1000) =>
+ (_req, _res, next) =>
+ setTimeout(next, time)
+
+router.use(timer())
+
router.get('/course/list', (req, res) => {
res.send(require('../mocks/courses/list/success.json'))
})
@@ -23,7 +30,9 @@ router.post('/lesson', (req, res) => {
})
router.post('/lesson/access-code', (req, res) => {
- const answer = fs.readFileSync(path.resolve(__dirname, '../mocks/lessons/access-code/create/success.json'))
+ const answer = fs.readFileSync(
+ path.resolve(__dirname, '../mocks/lessons/access-code/create/success.json'),
+ )
// res.send(require('../mocks/lessons/access-code/create/success.json'))
res.send(answer)
})
diff --git a/stubs/mocks/courses/list/success.json b/stubs/mocks/courses/list/success.json
index e6a6fa3..3f761e7 100644
--- a/stubs/mocks/courses/list/success.json
+++ b/stubs/mocks/courses/list/success.json
@@ -23,6 +23,29 @@
"startDt": "2024-03-02T15:37:05.907Z",
"created": "2024-03-02T15:37:05.908Z",
"__v": 2
+ },
+ {
+ "_id": "65e73c21ced789d2f679128z",
+ "name": "KFU-24-2",
+ "teachers": [],
+ "lessons": [
+ "65e2e5fbec37fec650f28489",
+ "65e301c4ec37fec650f2aafe",
+ "65e78bebced789d2f6791315",
+ "65e78c0fced789d2f679131b"
+ ],
+ "creator": {
+ "sub": "f62905b1-e223-40ca-910f-c8d84c6137c1",
+ "email_verified": true,
+ "name": "Александр Примаков",
+ "preferred_username": "primakov",
+ "given_name": "Александр",
+ "family_name": "Примаков",
+ "email": "primakovpro@gmail.com"
+ },
+ "startDt": "2024-03-02T15:37:05.907Z",
+ "created": "2024-03-02T15:37:05.908Z",
+ "__v": 2
}
]
}