29
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										29
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -9,6 +9,7 @@ | |||||||
|             "version": "1.2.0", |             "version": "1.2.0", | ||||||
|             "license": "MIT", |             "license": "MIT", | ||||||
|             "dependencies": { |             "dependencies": { | ||||||
|  |                 "@chakra-ui/icons": "^2.1.1", | ||||||
|                 "@chakra-ui/react": "^2.8.2", |                 "@chakra-ui/react": "^2.8.2", | ||||||
|                 "@emotion/react": "^11.11.4", |                 "@emotion/react": "^11.11.4", | ||||||
|                 "@emotion/styled": "^11.11.0", |                 "@emotion/styled": "^11.11.0", | ||||||
| @ -24,6 +25,7 @@ | |||||||
|                 "react": "^18.2.0", |                 "react": "^18.2.0", | ||||||
|                 "react-dom": "^18.2.0", |                 "react-dom": "^18.2.0", | ||||||
|                 "react-helmet": "^6.1.0", |                 "react-helmet": "^6.1.0", | ||||||
|  |                 "react-hook-form": "^7.51.2", | ||||||
|                 "react-redux": "^9.1.0", |                 "react-redux": "^9.1.0", | ||||||
|                 "react-router-dom": "^6.22.1", |                 "react-router-dom": "^6.22.1", | ||||||
|                 "redux": "^5.0.1", |                 "redux": "^5.0.1", | ||||||
| @ -2213,6 +2215,18 @@ | |||||||
|                 "react": ">=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": { |         "node_modules/@chakra-ui/image": { | ||||||
|             "version": "2.1.0", |             "version": "2.1.0", | ||||||
|             "resolved": "https://registry.npmjs.org/@chakra-ui/image/-/image-2.1.0.tgz", |             "resolved": "https://registry.npmjs.org/@chakra-ui/image/-/image-2.1.0.tgz", | ||||||
| @ -8546,6 +8560,21 @@ | |||||||
|                 "react": ">=16.3.0" |                 "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": { |         "node_modules/react-i18next": { | ||||||
|             "version": "11.8.5", |             "version": "11.8.5", | ||||||
|             "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-11.8.5.tgz", |             "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-11.8.5.tgz", | ||||||
|  | |||||||
| @ -20,6 +20,7 @@ | |||||||
|         "@ijl/cli": "^5.0.3" |         "@ijl/cli": "^5.0.3" | ||||||
|     }, |     }, | ||||||
|     "dependencies": { |     "dependencies": { | ||||||
|  |         "@chakra-ui/icons": "^2.1.1", | ||||||
|         "@chakra-ui/react": "^2.8.2", |         "@chakra-ui/react": "^2.8.2", | ||||||
|         "@emotion/react": "^11.11.4", |         "@emotion/react": "^11.11.4", | ||||||
|         "@emotion/styled": "^11.11.0", |         "@emotion/styled": "^11.11.0", | ||||||
| @ -35,6 +36,7 @@ | |||||||
|         "react": "^18.2.0", |         "react": "^18.2.0", | ||||||
|         "react-dom": "^18.2.0", |         "react-dom": "^18.2.0", | ||||||
|         "react-helmet": "^6.1.0", |         "react-helmet": "^6.1.0", | ||||||
|  |         "react-hook-form": "^7.51.2", | ||||||
|         "react-redux": "^9.1.0", |         "react-redux": "^9.1.0", | ||||||
|         "react-router-dom": "^6.22.1", |         "react-router-dom": "^6.22.1", | ||||||
|         "redux": "^5.0.1", |         "redux": "^5.0.1", | ||||||
|  | |||||||
							
								
								
									
										133
									
								
								src/app.tsx
									
									
									
									
									
								
							
							
						
						
									
										133
									
								
								src/app.tsx
									
									
									
									
									
								
							| @ -4,6 +4,7 @@ import { Global, css  } from '@emotion/react' | |||||||
| import { BrowserRouter } from 'react-router-dom'; | import { BrowserRouter } from 'react-router-dom'; | ||||||
| import ruLocale from 'dayjs/locale/ru'; | import ruLocale from 'dayjs/locale/ru'; | ||||||
| import dayjs from 'dayjs'; | import dayjs from 'dayjs'; | ||||||
|  | import { ChakraProvider } from '@chakra-ui/react' | ||||||
| 
 | 
 | ||||||
| import { Dashboard } from './dashboard'; | import { Dashboard } from './dashboard'; | ||||||
| 
 | 
 | ||||||
| @ -11,72 +12,74 @@ dayjs.locale('ru', ruLocale); | |||||||
| 
 | 
 | ||||||
| const App = ({ store }) => { | const App = ({ store }) => { | ||||||
|     return( |     return( | ||||||
|         <BrowserRouter> |       <ChakraProvider> | ||||||
|             <Helmet> |           <BrowserRouter> | ||||||
|                 <meta name="viewport" content="width=device-width, user-scalable=no" /> |               <Helmet> | ||||||
|                 <title>Журнал</title> |                   <meta name="viewport" content="width=device-width, user-scalable=no" /> | ||||||
|             </Helmet> |                   <title>Журнал</title> | ||||||
|             <Global |               </Helmet> | ||||||
|                 styles={css` |               <Global | ||||||
|                     * { box-sizing: border-box; } |                   styles={css` | ||||||
|                     html { |                       html { | ||||||
|                         height: 100%; |                           height: 100%; | ||||||
|                         max-width: 100%; |                           max-width: 100%; | ||||||
|                         overflow: hidden; |                           overflow: hidden; | ||||||
|                     } |                       } | ||||||
|                     body { |                       body { | ||||||
|                         color: #000; |                           color: #000; | ||||||
|                         /* background: radial-gradient(circle at top right, rgb(154 227 33), rgb(33 160 56)); */ |                           /* background: radial-gradient(circle at top right, rgb(154 227 33), rgb(33 160 56)); */ | ||||||
|                         background: radial-gradient( |                           background: radial-gradient( | ||||||
|                             farthest-side at bottom left, |                               farthest-side at bottom left, | ||||||
|                             rgba(35, 235, 4, 0.709),  |                               rgba(35, 235, 4, 0.709),  | ||||||
|                             rgba(255, 255, 255, 0) 65% |                               rgba(255, 255, 255, 0) 65% | ||||||
|                         ), |                           ), | ||||||
|                         radial-gradient( |                           radial-gradient( | ||||||
|                             farthest-corner at bottom center, |                               farthest-corner at bottom center, | ||||||
|                             rgba(244, 244, 8, 0.6),  |                               rgba(244, 244, 8, 0.6),  | ||||||
|                             rgba(255, 255, 255, 0) 40% |                               rgba(255, 255, 255, 0) 40% | ||||||
|                         ), |                           ), | ||||||
|                         radial-gradient( |                           radial-gradient( | ||||||
|                             farthest-side at bottom right, |                               farthest-side at bottom right, | ||||||
|                             rgba(0, 195, 255, 0.648),  |                               rgba(0, 195, 255, 0.648),  | ||||||
|                             rgba(255, 255, 255, 0) 65% |                               rgb(239 244 246) 65% | ||||||
|                         ); |                               /* rgba(255, 255, 255, 0) 65% */ | ||||||
|                         height: 100%; |                           ); | ||||||
|                         font-family: KiyosunaSans, Montserrat, RFKrabuler, sans-serif; |                           height: 100%; | ||||||
|                         font-weight: 600; |                           font-family: KiyosunaSans, Montserrat, RFKrabuler, sans-serif; | ||||||
|                     } |                           font-weight: 600; | ||||||
|                     #app { |                       } | ||||||
|                         height: 100%; |                       #app { | ||||||
|                         overflow-y: auto; |                           height: 100%; | ||||||
|                     } |                           overflow-y: auto; | ||||||
|  |                       } | ||||||
| 
 | 
 | ||||||
|                     @font-face { |                       @font-face { | ||||||
|                         font-family: 'KiyosunaSans'; |                           font-family: 'KiyosunaSans'; | ||||||
|                         src: url('${__webpack_public_path__ + '/remote-assets/KiyosunaSans/KiyosunaSans-B.otf'}'); |                           src: url('${__webpack_public_path__ + '/remote-assets/KiyosunaSans/KiyosunaSans-B.otf'}'); | ||||||
|                         font-weight: normal; |                           font-weight: normal; | ||||||
|                         font-style: normal; |                           font-style: normal; | ||||||
|                     } |                       } | ||||||
|                     @font-face { |                       @font-face { | ||||||
|                         font-family: 'KiyosunaSans'; |                           font-family: 'KiyosunaSans'; | ||||||
|                         src: url('${__webpack_public_path__ + '/remote-assets/KiyosunaSans/KiyosunaSans-L.otf'}'); |                           src: url('${__webpack_public_path__ + '/remote-assets/KiyosunaSans/KiyosunaSans-L.otf'}'); | ||||||
|                         font-weight: lighter; |                           font-weight: lighter; | ||||||
|                         font-style: normal; |                           font-style: normal; | ||||||
|                     } |                       } | ||||||
|                     @font-face { |                       @font-face { | ||||||
|                         font-family: 'RFKrabuler'; |                           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.eot'}'); | ||||||
|                         src: |                           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.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/WEB/RFKrabuler-Regular.woff'}') format('woff'), | ||||||
|                             url('${__webpack_public_path__ + '/remote-assets/RF-Krabuler/TTF/RF-Krabuler-Regular.ttf'}') format('truetype'); |                               url('${__webpack_public_path__ + '/remote-assets/RF-Krabuler/TTF/RF-Krabuler-Regular.ttf'}') format('truetype'); | ||||||
|                         font-weight: normal; |                           font-weight: normal; | ||||||
|                         font-style: normal; |                           font-style: normal; | ||||||
|                     } |                       } | ||||||
|                 `}
 |                   `}
 | ||||||
|             /> |               /> | ||||||
|             <Dashboard store={store} /> |               <Dashboard store={store} /> | ||||||
|         </BrowserRouter> |           </BrowserRouter> | ||||||
|  |         </ChakraProvider> | ||||||
|     ) |     ) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,34 +1,82 @@ | |||||||
| import React, { useEffect, Suspense } from "react"; | import React, { useEffect, Suspense } from 'react' | ||||||
| import { Routes, Route, useNavigate } from "react-router-dom"; | import { Routes, Route, useNavigate } from 'react-router-dom' | ||||||
| import { Provider } from "react-redux"; | import { Provider } from 'react-redux' | ||||||
| 
 | 
 | ||||||
| import { | import { | ||||||
|   CourseListPage, |   CourseListPage, | ||||||
|   LessonDetailsPage, |   LessonDetailsPage, | ||||||
|   LessonListPage, |   LessonListPage, | ||||||
|   UserPage |   UserPage, | ||||||
| } from './pages' | } 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 Redirect = ({ path }) => { | ||||||
|   const navigate = useNavigate(); |   const navigate = useNavigate() | ||||||
| 
 | 
 | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     navigate(path); |     navigate(path) | ||||||
|   }, []); |   }, []) | ||||||
| 
 | 
 | ||||||
|   return null; |   return null | ||||||
| }; | } | ||||||
| 
 | 
 | ||||||
| const Wrapper = ({ children }) => <Suspense fallback="...">{children}</Suspense> | const Wrapper = ({ children }) => ( | ||||||
|  |   <Suspense | ||||||
|  |     fallback={ | ||||||
|  |       <Container> | ||||||
|  |         <VStack> | ||||||
|  |           <Box mt="150"> | ||||||
|  |           <Spinner | ||||||
|  |             thickness="4px" | ||||||
|  |             speed="0.65s" | ||||||
|  |             emptyColor="gray.200" | ||||||
|  |             color="blue.500" | ||||||
|  |             size="xl" | ||||||
|  |           /></Box> | ||||||
|  |         </VStack> | ||||||
|  |       </Container> | ||||||
|  |     } | ||||||
|  |   > | ||||||
|  |     {children} | ||||||
|  |   </Suspense> | ||||||
|  | ) | ||||||
| 
 | 
 | ||||||
| export const Dashboard = ({ store }) => ( | export const Dashboard = ({ store }) => ( | ||||||
|   <Provider store={store}> |   <Provider store={store}> | ||||||
|     <Routes> |     <Routes> | ||||||
|       <Route path={getNavigationsValue('journal.main')} element={<Wrapper><CourseListPage /></Wrapper>} /> |       <Route | ||||||
|       <Route path={`${getNavigationsValue('journal.main')}/lessons-list/:courseId`} element={<Wrapper><LessonListPage /></Wrapper>} /> |         path={getNavigationsValue('journal.main')} | ||||||
|       <Route path={`${getNavigationsValue('journal.main')}/u/:lessonId/:accessId`} element={<Wrapper><UserPage /></Wrapper>} /> |         element={ | ||||||
|       <Route path={`${getNavigationsValue('journal.main')}/lesson/:courseId/:lessonId`} element={<Wrapper><LessonDetailsPage /></Wrapper>} /> |           <Wrapper> | ||||||
|  |             <CourseListPage /> | ||||||
|  |           </Wrapper> | ||||||
|  |         } | ||||||
|  |       /> | ||||||
|  |       <Route | ||||||
|  |         path={`${getNavigationsValue('journal.main')}/lessons-list/:courseId`} | ||||||
|  |         element={ | ||||||
|  |           <Wrapper> | ||||||
|  |             <LessonListPage /> | ||||||
|  |           </Wrapper> | ||||||
|  |         } | ||||||
|  |       /> | ||||||
|  |       <Route | ||||||
|  |         path={`${getNavigationsValue('journal.main')}/u/:lessonId/:accessId`} | ||||||
|  |         element={ | ||||||
|  |           <Wrapper> | ||||||
|  |             <UserPage /> | ||||||
|  |           </Wrapper> | ||||||
|  |         } | ||||||
|  |       /> | ||||||
|  |       <Route | ||||||
|  |         path={`${getNavigationsValue('journal.main')}/lesson/:courseId/:lessonId`} | ||||||
|  |         element={ | ||||||
|  |           <Wrapper> | ||||||
|  |             <LessonDetailsPage /> | ||||||
|  |           </Wrapper> | ||||||
|  |         } | ||||||
|  |       /> | ||||||
|     </Routes> |     </Routes> | ||||||
|   </Provider> |   </Provider> | ||||||
| ); | ) | ||||||
|  | |||||||
| @ -1,8 +1,7 @@ | |||||||
| import React, { useCallback, useEffect, useRef, useState } from 'react' | import React, { useCallback, useEffect, useRef, useState } from 'react' | ||||||
| import dayjs from 'dayjs' | import dayjs from 'dayjs' | ||||||
| import { Link } from 'react-router-dom' | import { Link as ConnectedLink } from 'react-router-dom' | ||||||
| import { getConfigValue, getNavigationsValue } from '@ijl/cli' | import { getNavigationsValue } from '@ijl/cli' | ||||||
| 
 |  | ||||||
| import { | import { | ||||||
|   Box, |   Box, | ||||||
|   CardHeader, |   CardHeader, | ||||||
| @ -12,155 +11,299 @@ import { | |||||||
|   Stack, |   Stack, | ||||||
|   StackDivider, |   StackDivider, | ||||||
|   Button, |   Button, | ||||||
|   UnorderedList, |   Card, | ||||||
|   Heading, |   Heading, | ||||||
|   Tooltip, |   Tooltip, | ||||||
|  |   Spinner, | ||||||
|  |   Container, | ||||||
|  |   VStack, | ||||||
|  |   Link, | ||||||
|  |   Input, | ||||||
|  |   CloseButton, | ||||||
|  |   FormControl, | ||||||
|  |   FormLabel, | ||||||
|  |   FormHelperText, | ||||||
|  |   Center, | ||||||
|  |   FormErrorMessage, | ||||||
|  |   useToast, | ||||||
| } from '@chakra-ui/react' | } from '@chakra-ui/react' | ||||||
|  | import { useForm, Controller } from 'react-hook-form' | ||||||
| 
 | 
 | ||||||
| import { | import { ErrorSpan, MainWrapper } from './style' | ||||||
|   ArrowImg, |  | ||||||
|   IconButton, |  | ||||||
|   InputElement, |  | ||||||
|   InputLabel, |  | ||||||
|   InputWrapper, |  | ||||||
|   StartWrapper, |  | ||||||
|   Papper, |  | ||||||
|   ErrorSpan, |  | ||||||
|   Cross, |  | ||||||
|   AddButton, |  | ||||||
|   MainWrapper, |  | ||||||
|   StyledCard, |  | ||||||
| } 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 { useAppSelector } from '../__data__/store' | ||||||
| import { api } from '../__data__/api/api' | import { api } from '../__data__/api/api' | ||||||
| import { isTeacher } from '../utils/user' | import { isTeacher } from '../utils/user' | ||||||
|  | import { AddIcon, ArrowDownIcon, ArrowUpIcon, LinkIcon } from '@chakra-ui/icons' | ||||||
|  | 
 | ||||||
|  | interface NewCourseForm { | ||||||
|  |   startDt: string | ||||||
|  |   name: string | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| const CoursesList = () => { | const CoursesList = () => { | ||||||
|  |   const toast = useToast() | ||||||
|   const user = useAppSelector((s) => s.user) |   const user = useAppSelector((s) => s.user) | ||||||
|   const { data, isLoading, error } = api.useCoursesListQuery() |   const { data, isLoading } = api.useCoursesListQuery() | ||||||
|   const [createCourse, crcQuery] = api.useCreateUpdateCourseMutation() |   const [createUpdateCourse, crucQuery] = api.useCreateUpdateCourseMutation() | ||||||
|   const [value, setValue] = useState('') |  | ||||||
|   const [showForm, setShowForm] = useState(false) |   const [showForm, setShowForm] = useState(false) | ||||||
|   const [showDetails, setShowDetails] = useState(false) |   const [courseDetailsOpenedId, setCourseDetailsOpenedId] = useState< | ||||||
|  |     string | null | ||||||
|  |   >(null) | ||||||
|  |   const toastRef = useRef(null) | ||||||
| 
 | 
 | ||||||
|   const handleChange = useCallback( |   const { | ||||||
|     (event) => { |     control, | ||||||
|       setValue(event.target.value.toUpperCase()) |     handleSubmit, | ||||||
|  |     reset, | ||||||
|  |     formState: { errors }, | ||||||
|  |     getValues, | ||||||
|  |   } = useForm<NewCourseForm>({ | ||||||
|  |     defaultValues: { | ||||||
|  |       startDt: '', | ||||||
|  |       name: '', | ||||||
|     }, |     }, | ||||||
|     [setValue], |   }) | ||||||
|   ) | 
 | ||||||
|   const handleSubmit = useCallback( |   const onSubmit = ({ startDt, name }) => { | ||||||
|     (event) => { |     toastRef.current = toast({ | ||||||
|       event.preventDefault() |       title: 'Отправляем', | ||||||
|       createCourse({ name: value }) |       status: 'loading', | ||||||
|     }, |       duration: 9000, | ||||||
|     [value], |     }) | ||||||
|   ) |     createUpdateCourse({ name, startDt }) | ||||||
|  |   } | ||||||
| 
 | 
 | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     if (crcQuery.isSuccess) { |     if (crucQuery.isSuccess) { | ||||||
|       setValue('') |       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 ( | ||||||
|  |       <Container maxW="container.xl"> | ||||||
|  |         <Center h="300px"> | ||||||
|  |           <Spinner | ||||||
|  |             thickness="4px" | ||||||
|  |             speed="0.65s" | ||||||
|  |             emptyColor="gray.200" | ||||||
|  |             color="blue.500" | ||||||
|  |             size="xl" | ||||||
|  |           /> | ||||||
|  |         </Center> | ||||||
|  |       </Container> | ||||||
|  |     ) | ||||||
|  |   } | ||||||
| 
 | 
 | ||||||
|   return ( |   return ( | ||||||
|     <MainWrapper> |     <Container maxW="container.xl"> | ||||||
|       <StartWrapper> |       {isTeacher(user) && ( | ||||||
|         {isTeacher(user) && ( |         <Box mt="15" mb="15"> | ||||||
|           <> |           {showForm ? ( | ||||||
|             {showForm ? ( |             <Card align="left"> | ||||||
|               <Papper> |               <CardHeader display="flex"> | ||||||
|                 <Cross role="button" onClick={() => setShowForm(false)}> |                 <Heading as="h2" mt="0"> | ||||||
|                   X |                   Создание курса | ||||||
|                 </Cross> |                 </Heading> | ||||||
|                 <form onSubmit={handleSubmit}> |                 <CloseButton ml="auto" onClick={() => setShowForm(false)} /> | ||||||
|                   <InputWrapper> |               </CardHeader> | ||||||
|                     <InputLabel htmlFor="input"> |               <CardBody> | ||||||
|                       Название новой лекции: |                 <form onSubmit={handleSubmit(onSubmit)}> | ||||||
|                     </InputLabel> |                   <VStack spacing={10} align="left"> | ||||||
|                     <InputElement |                     <Controller | ||||||
|                       value={value} |                       control={control} | ||||||
|                       onChange={handleChange} |                       name="startDt" | ||||||
|                       id="input" |                       rules={{ required: 'Обязательное поле' }} | ||||||
|                       type="text" |                       render={({ field }) => ( | ||||||
|                       autoComplete="off" |                         <FormControl | ||||||
|  |                           isRequired | ||||||
|  |                           isInvalid={Boolean(errors.startDt)} | ||||||
|  |                         > | ||||||
|  |                           <FormLabel>Дата начала</FormLabel> | ||||||
|  |                           <Input | ||||||
|  |                             {...field} | ||||||
|  |                             required={false} | ||||||
|  |                             placeholder="Select Date and Time" | ||||||
|  |                             size="md" | ||||||
|  |                             type="datetime-local" | ||||||
|  |                           /> | ||||||
|  |                           {errors.startDt ? ( | ||||||
|  |                             <FormErrorMessage> | ||||||
|  |                               {errors.startDt?.message} | ||||||
|  |                             </FormErrorMessage> | ||||||
|  |                           ) : ( | ||||||
|  |                             <FormHelperText> | ||||||
|  |                               Укажите дату начала курса | ||||||
|  |                             </FormHelperText> | ||||||
|  |                           )} | ||||||
|  |                         </FormControl> | ||||||
|  |                       )} | ||||||
|                     /> |                     /> | ||||||
|                     <IconButton type="submit"> |                     <Controller | ||||||
|                       <ArrowImg src={arrow} /> |                       control={control} | ||||||
|                     </IconButton> |                       name="name" | ||||||
|                   </InputWrapper> |                       rules={{ | ||||||
|                   {crcQuery?.error && ( |                         required: 'Обязательное поле', | ||||||
|                     <ErrorSpan>{(crcQuery?.error as any).error}</ErrorSpan> |                       }} | ||||||
|  |                       render={({ field }) => ( | ||||||
|  |                         <FormControl | ||||||
|  |                           isRequired | ||||||
|  |                           isInvalid={Boolean(errors.name)} | ||||||
|  |                         > | ||||||
|  |                           <FormLabel>Название новой лекции:</FormLabel> | ||||||
|  |                           <Input | ||||||
|  |                             {...field} | ||||||
|  |                             required={false} | ||||||
|  |                             placeholder="КФУ-24-2" | ||||||
|  |                             size="md" | ||||||
|  |                           /> | ||||||
|  |                           {errors.name && ( | ||||||
|  |                             <FormErrorMessage> | ||||||
|  |                               {errors.name.message} | ||||||
|  |                             </FormErrorMessage> | ||||||
|  |                           )} | ||||||
|  |                         </FormControl> | ||||||
|  |                       )} | ||||||
|  |                     /> | ||||||
|  | 
 | ||||||
|  |                     <Box mt={10}> | ||||||
|  |                       <Button | ||||||
|  |                         size="lg" | ||||||
|  |                         type="submit" | ||||||
|  |                         leftIcon={<AddIcon />} | ||||||
|  |                         colorScheme="blue" | ||||||
|  |                       > | ||||||
|  |                         Создать | ||||||
|  |                       </Button> | ||||||
|  |                     </Box> | ||||||
|  |                   </VStack> | ||||||
|  | 
 | ||||||
|  |                   {crucQuery?.error && ( | ||||||
|  |                     <ErrorSpan>{(crucQuery?.error as any).error}</ErrorSpan> | ||||||
|                   )} |                   )} | ||||||
|                 </form> |                 </form> | ||||||
|               </Papper> |               </CardBody> | ||||||
|  |             </Card> | ||||||
|  |           ) : ( | ||||||
|  |             <Box p="2" m="2"> | ||||||
|  |               <Button | ||||||
|  |                 leftIcon={<AddIcon />} | ||||||
|  |                 colorScheme="green" | ||||||
|  |                 onClick={() => setShowForm(true)} | ||||||
|  |               > | ||||||
|  |                 Добавить | ||||||
|  |               </Button> | ||||||
|  |             </Box> | ||||||
|  |           )} | ||||||
|  |         </Box> | ||||||
|  |       )} | ||||||
|  |       <VStack as="ul" align="stretch"> | ||||||
|  |         {data?.body?.map((c) => ( | ||||||
|  |           <CourseCard | ||||||
|  |             isOpened={courseDetailsOpenedId === c._id} | ||||||
|  |             key={c._id} | ||||||
|  |             course={c} | ||||||
|  |             openDetails={() => | ||||||
|  |               courseDetailsOpenedId === c._id | ||||||
|  |                 ? setCourseDetailsOpenedId(null) | ||||||
|  |                 : setCourseDetailsOpenedId(c._id) | ||||||
|  |             } | ||||||
|  |           /> | ||||||
|  |         ))} | ||||||
|  |       </VStack> | ||||||
|  |     </Container> | ||||||
|  |   ) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 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 ( | ||||||
|  |     <Card key={course._id} align="left"> | ||||||
|  |       <CardHeader> | ||||||
|  |         <Heading as="h2" mt="0"> | ||||||
|  |           {course.name} | ||||||
|  |         </Heading> | ||||||
|  |       </CardHeader> | ||||||
|  |       {isOpened && ( | ||||||
|  |         <CardBody mt="16px"> | ||||||
|  |           <Stack divider={<StackDivider />} spacing="8px"> | ||||||
|  |             <Box as="span" textAlign="left"> | ||||||
|  |               {`Дата начала курса - ${dayjs(course.startDt).format('DD MMMM YYYYг.')}`} | ||||||
|  |             </Box> | ||||||
|  |             <Box as="span" textAlign="left"> | ||||||
|  |               Количество занятий - {course.lessons.length} | ||||||
|  |             </Box> | ||||||
|  |             {lessonList.isFetching ? ( | ||||||
|  |               <Spinner /> | ||||||
|             ) : ( |             ) : ( | ||||||
|               <AddButton onClick={() => setShowForm(true)}>Добавить</AddButton> |               <> | ||||||
|             )} |                 <Heading as="h3" mt={4} mb={3} size="lg"> | ||||||
|           </> |                   Список занятий: | ||||||
|         )} |  | ||||||
|         <UnorderedList spacing={3}> |  | ||||||
|           {data?.body?.map((course) => ( |  | ||||||
|             <StyledCard key={course._id} align="left"> |  | ||||||
|               <CardHeader> |  | ||||||
|                 <Heading as="h2" mt="0"> |  | ||||||
|                   {course.name} |  | ||||||
|                 </Heading> |                 </Heading> | ||||||
|               </CardHeader> |                 <Stack> | ||||||
|               {showDetails && ( |                   {lessonList.data?.body?.map((lesson) => ( | ||||||
|                 <CardBody mt="16px"> |                     <Link | ||||||
|                   <Stack divider={<StackDivider />} spacing="8px"> |                       as={ConnectedLink} | ||||||
|                     <Box as="span" textAlign="left"> |                       to={ | ||||||
|                       {`Дата начала курса - ${dayjs(course.startDt).format('DD MMMM YYYYг.')}`} |                         isTeacher(user) | ||||||
|                     </Box> |                           ? `${getNavigationsValue('journal.main')}/lesson/${course._id}/${lesson._id}` | ||||||
|                     <Box as="span" textAlign="left"> |                           : '' | ||||||
|                       Количество занятий - {course.lessons.length} |                       } | ||||||
|                     </Box> |  | ||||||
|                   </Stack> |  | ||||||
|                 </CardBody> |  | ||||||
|               )} |  | ||||||
|               <CardFooter> |  | ||||||
|                 <ButtonGroup spacing="12" mt="16px"> |  | ||||||
|                   <Tooltip |  | ||||||
|                     label="На страницу с лекциями" |  | ||||||
|                     fontSize="12px" |  | ||||||
|                     top="16px" |  | ||||||
|                   > |  | ||||||
|                     <Button variant="ghost" border="none" bg="#ffffff"> |  | ||||||
|                       <Link |  | ||||||
|                         to={`${getNavigationsValue('journal.main')}/lessons-list/${course._id}`} |  | ||||||
|                       > |  | ||||||
|                         <img src={linkOpen} alt="Перейти к лекциям" /> |  | ||||||
|                       </Link> |  | ||||||
|                     </Button> |  | ||||||
|                   </Tooltip> |  | ||||||
|                   <Tooltip label="Детали" fontSize="12px" top="16px"> |  | ||||||
|                     <Button |  | ||||||
|                       variant="ghost" |  | ||||||
|                       border="none" |  | ||||||
|                       bg="#ffffff" |  | ||||||
|                       onClick={() => { |  | ||||||
|                         showDetails |  | ||||||
|                           ? setShowDetails(null) |  | ||||||
|                           : setShowDetails(true) |  | ||||||
|                       }} |  | ||||||
|                       cursor="pointer" |  | ||||||
|                     > |                     > | ||||||
|                       <img src={moreDetails} alt="Просмотреть детали" /> |                       {lesson.name} | ||||||
|                     </Button> |                     </Link> | ||||||
|                   </Tooltip> |                   ))} | ||||||
|                 </ButtonGroup> |                 </Stack> | ||||||
|               </CardFooter> |               </> | ||||||
|             </StyledCard> |             )} | ||||||
|           ))} |           </Stack> | ||||||
|         </UnorderedList> |         </CardBody> | ||||||
|       </StartWrapper> |       )} | ||||||
|     </MainWrapper> |       <CardFooter> | ||||||
|  |         <ButtonGroup spacing="4" mt="16px"> | ||||||
|  |           <Tooltip label="На страницу с лекциями" fontSize="12px" top="16px"> | ||||||
|  |             <Button | ||||||
|  |               leftIcon={<LinkIcon />} | ||||||
|  |               as={ConnectedLink} | ||||||
|  |               colorScheme="blue" | ||||||
|  |               to={`${getNavigationsValue('journal.main')}/lessons-list/${course._id}`} | ||||||
|  |             > | ||||||
|  |               Открыть | ||||||
|  |             </Button> | ||||||
|  |           </Tooltip> | ||||||
|  |           <Tooltip label="Детали" fontSize="12px" top="16px"> | ||||||
|  |             <Button | ||||||
|  |               colorScheme="blue" | ||||||
|  |               variant="outline" | ||||||
|  |               leftIcon={isOpened ? <ArrowUpIcon /> : <ArrowDownIcon />} | ||||||
|  |               loadingText="Загрузка" | ||||||
|  |               isLoading={lessonList.isFetching} | ||||||
|  |               onClick={openDetails} | ||||||
|  |             > | ||||||
|  |               Просмотреть детали | ||||||
|  |             </Button> | ||||||
|  |           </Tooltip> | ||||||
|  |         </ButtonGroup> | ||||||
|  |       </CardFooter> | ||||||
|  |     </Card> | ||||||
|   ) |   ) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -84,18 +84,18 @@ const LessonDetail = () => { | |||||||
|       <StartWrapper> |       <StartWrapper> | ||||||
|         <Breadcrumb> |         <Breadcrumb> | ||||||
|           <BreadcrumbItem> |           <BreadcrumbItem> | ||||||
|             <Link as={BreadcrumbLink} to={getNavigationsValue('journal.main')}> |             <BreadcrumbLink as={Link} to={getNavigationsValue('journal.main')}> | ||||||
|               Журнал |               Журнал | ||||||
|             </Link> |             </BreadcrumbLink> | ||||||
|           </BreadcrumbItem> |           </BreadcrumbItem> | ||||||
| 
 | 
 | ||||||
|           <BreadcrumbItem> |           <BreadcrumbItem> | ||||||
|             <Link |             <BreadcrumbLink | ||||||
|               as={BreadcrumbLink} |               as={Link} | ||||||
|               to={`${getNavigationsValue('journal.main')}/lessons-list/${courseId}`} |               to={`${getNavigationsValue('journal.main')}/lessons-list/${courseId}`} | ||||||
|             > |             > | ||||||
|               Курс |               Курс | ||||||
|             </Link> |             </BreadcrumbLink> | ||||||
|           </BreadcrumbItem> |           </BreadcrumbItem> | ||||||
| 
 | 
 | ||||||
|           <BreadcrumbItem isCurrentPage> |           <BreadcrumbItem isCurrentPage> | ||||||
| @ -103,8 +103,10 @@ const LessonDetail = () => { | |||||||
|           </BreadcrumbItem> |           </BreadcrumbItem> | ||||||
|         </Breadcrumb> |         </Breadcrumb> | ||||||
| 
 | 
 | ||||||
|         <h1 style={{ width: '70%' }} >Тема занятия - {accessCode?.body?.lesson?.name}</h1> |         <h1 style={{ width: '70%' }}> | ||||||
|         <span  style={{ display: 'flex' }}> |           Тема занятия - {accessCode?.body?.lesson?.name} | ||||||
|  |         </h1> | ||||||
|  |         <span style={{ display: 'flex' }}> | ||||||
|           {dayjs(accessCode?.body?.lesson?.date).format('DD MMMM YYYYг.')}{' '} |           {dayjs(accessCode?.body?.lesson?.date).format('DD MMMM YYYYг.')}{' '} | ||||||
|           Отмечено - {accessCode?.body?.lesson?.students?.length}{' '} |           Отмечено - {accessCode?.body?.lesson?.students?.length}{' '} | ||||||
|           {AllStudents.isSuccess ? `/ ${AllStudents?.data?.body?.length}` : ''}{' '} |           {AllStudents.isSuccess ? `/ ${AllStudents?.data?.body?.length}` : ''}{' '} | ||||||
|  | |||||||
| @ -63,7 +63,7 @@ const LessonList = () => { | |||||||
|       <StartWrapper> |       <StartWrapper> | ||||||
|         <Breadcrumb> |         <Breadcrumb> | ||||||
|           <BreadcrumbItem> |           <BreadcrumbItem> | ||||||
|             <Link as={BreadcrumbLink} to={getNavigationsValue('journal.main')}>Журнал</Link> |             <BreadcrumbLink as={Link} to={getNavigationsValue('journal.main')}>Журнал</BreadcrumbLink> | ||||||
|           </BreadcrumbItem> |           </BreadcrumbItem> | ||||||
| 
 | 
 | ||||||
|           <BreadcrumbItem isCurrentPage> |           <BreadcrumbItem isCurrentPage> | ||||||
|  | |||||||
| @ -2,6 +2,13 @@ const router = require('express').Router() | |||||||
| const fs = require('node:fs') | const fs = require('node:fs') | ||||||
| const path = require('node:path') | const path = require('node:path') | ||||||
| 
 | 
 | ||||||
|  | const timer = | ||||||
|  |   (time = 1000) => | ||||||
|  |   (_req, _res, next) => | ||||||
|  |     setTimeout(next, time) | ||||||
|  | 
 | ||||||
|  | router.use(timer()) | ||||||
|  | 
 | ||||||
| router.get('/course/list', (req, res) => { | router.get('/course/list', (req, res) => { | ||||||
|   res.send(require('../mocks/courses/list/success.json')) |   res.send(require('../mocks/courses/list/success.json')) | ||||||
| }) | }) | ||||||
| @ -23,7 +30,9 @@ router.post('/lesson', (req, res) => { | |||||||
| }) | }) | ||||||
| 
 | 
 | ||||||
| router.post('/lesson/access-code', (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(require('../mocks/lessons/access-code/create/success.json'))
 | ||||||
|   res.send(answer) |   res.send(answer) | ||||||
| }) | }) | ||||||
|  | |||||||
| @ -23,6 +23,29 @@ | |||||||
|             "startDt": "2024-03-02T15:37:05.907Z", |             "startDt": "2024-03-02T15:37:05.907Z", | ||||||
|             "created": "2024-03-02T15:37:05.908Z", |             "created": "2024-03-02T15:37:05.908Z", | ||||||
|             "__v": 2 |             "__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 | ||||||
|         } |         } | ||||||
|     ] |     ] | ||||||
| } | } | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user