From b858d70675db7ce5f3b676b09b5d9fce280b70b8 Mon Sep 17 00:00:00 2001 From: primakov Date: Thu, 28 Mar 2024 18:26:26 +0300 Subject: [PATCH 1/2] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB?= =?UTF-8?q?=20=D1=81=D0=BF=D0=B8=D0=BD=D0=BD=D0=B5=D1=80=20(#5)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app.tsx | 133 ++++++++------- src/dashboard.tsx | 80 +++++++-- src/pages/course-list.tsx | 344 ++++++++++++++++++++++++-------------- 3 files changed, 352 insertions(+), 205 deletions(-) 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..408d901 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,252 @@ import { Stack, StackDivider, Button, - UnorderedList, + Card, Heading, Tooltip, + Spinner, + Container, + VStack, + Link, + Input, + CloseButton, + FormControl, + FormLabel, + FormHelperText, + Center, + FormErrorMessage, } 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 user = useAppSelector((s) => s.user) const { data, isLoading, error } = api.useCoursesListQuery() - const [createCourse, crcQuery] = api.useCreateUpdateCourseMutation() + const [createUpdateCourse, crucQuery] = api.useCreateUpdateCourseMutation() const [value, setValue] = useState('') const [showForm, setShowForm] = useState(false) - const [showDetails, setShowDetails] = useState(false) + const [courseDetailsOpenedId, setCourseDetailsOpenedId] = useState< + string | null + >(null) - const handleChange = useCallback( - (event) => { - setValue(event.target.value.toUpperCase()) - }, - [setValue], - ) - const handleSubmit = useCallback( - (event) => { - event.preventDefault() - createCourse({ name: value }) - }, - [value], - ) + const { control, handleSubmit, reset } = useForm({ + mode: 'all', + }) + + const onSubmit = ({ startDt, name }) => { + createUpdateCourse({ name, startDt }) + } useEffect(() => { - if (crcQuery.isSuccess) { - setValue('') + if (crucQuery.isSuccess) { + reset() } - }, [crcQuery.isSuccess]) + }, [crucQuery.isSuccess]) + + if (isLoading) { + return ( + +
+ +
+
+ ) + } return ( - - - {isTeacher(user) && ( - <> - {showForm ? ( - - setShowForm(false)}> - X - -
- - - Название новой лекции: - - + {isTeacher(user) && ( + + {!showForm ? ( + + + + Создание курса + + setShowForm(false)} /> + + + + + ( + + Дата начала + + {fieldState.error ? ( + + {fieldState.error.message} + + ) : ( + + Укажите дату начала курса + + )} + + )} /> - - - - + + Название новой лекции: + + + + + + + + + {crcQuery?.error && ( {(crcQuery?.error as any).error} )}
-
+ + + ) : ( + + + + )} + + )} + + {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} + + ))} + + + )} + + + )} + + + + + + + + + + + ) } -- 2.45.2 From 97e940b87336fdb6222a222dcf52622cb3527818 Mon Sep 17 00:00:00 2001 From: primakov Date: Thu, 28 Mar 2024 18:59:16 +0300 Subject: [PATCH 2/2] =?UTF-8?q?=D0=A4=D0=BE=D1=80=D0=BC=D0=B0=20=D1=81?= =?UTF-8?q?=D0=BE=D0=B7=D0=B4=D0=B0=D0=BD=D0=B8=D1=8F=20=D0=BB=D0=B5=D0=BA?= =?UTF-8?q?=D1=86=D0=B8=D0=B8=20(#1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 29 +++++++++ package.json | 2 + src/pages/course-list.tsx | 89 ++++++++++++++++++++------- src/pages/lesson-details.tsx | 16 ++--- src/pages/lesson-list.tsx | 2 +- stubs/api/index.js | 11 +++- stubs/mocks/courses/list/success.json | 23 +++++++ 7 files changed, 142 insertions(+), 30 deletions(-) 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/pages/course-list.tsx b/src/pages/course-list.tsx index 408d901..6b0b1fa 100644 --- a/src/pages/course-list.tsx +++ b/src/pages/course-list.tsx @@ -25,6 +25,7 @@ import { FormHelperText, Center, FormErrorMessage, + useToast, } from '@chakra-ui/react' import { useForm, Controller } from 'react-hook-form' @@ -41,25 +42,50 @@ interface NewCourseForm { } const CoursesList = () => { + const toast = useToast() const user = useAppSelector((s) => s.user) - const { data, isLoading, error } = api.useCoursesListQuery() + const { data, isLoading } = api.useCoursesListQuery() const [createUpdateCourse, crucQuery] = api.useCreateUpdateCourseMutation() - const [value, setValue] = useState('') const [showForm, setShowForm] = useState(false) const [courseDetailsOpenedId, setCourseDetailsOpenedId] = useState< string | null >(null) + const toastRef = useRef(null) - const { control, handleSubmit, reset } = useForm({ - mode: 'all', + const { + control, + handleSubmit, + reset, + formState: { errors }, + getValues, + } = useForm({ + defaultValues: { + startDt: '', + name: '', + }, }) const onSubmit = ({ startDt, name }) => { + toastRef.current = toast({ + title: 'Отправляем', + status: 'loading', + duration: 9000, + }) createUpdateCourse({ name, startDt }) } useEffect(() => { if (crucQuery.isSuccess) { + const values = getValues() + if (toastRef.current) { + toast.update(toastRef.current, { + title: 'Курс создан.', + description: `Курс ${values.name} успешно создан`, + status: 'success', + duration: 9000, + isClosable: true, + }) + } reset() } }, [crucQuery.isSuccess]) @@ -84,7 +110,7 @@ const CoursesList = () => { {isTeacher(user) && ( - {!showForm ? ( + {showForm ? ( @@ -99,18 +125,22 @@ const CoursesList = () => { control={control} name="startDt" rules={{ required: 'Обязательное поле' }} - render={({ field, fieldState }) => ( - + render={({ field }) => ( + Дата начала - {fieldState.error ? ( + {errors.startDt ? ( - {fieldState.error.message} + {errors.startDt?.message} ) : ( @@ -120,20 +150,37 @@ const CoursesList = () => { )} /> - - Название новой лекции: - - - + ( + + Название новой лекции: + + {errors.name && ( + + {errors.name.message} + + )} + + )} + />