diff --git a/package.json b/package.json index 87c8866..4f3e9c9 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "inno-js platform journal ui repo", "main": "./src/index.tsx", "scripts": { - "start": "start chrome http://ift-b1.kc.inno-js.test/journal.pl && ijl-cli server --port=80", + "start": "start chrome http://ift-b1.kc.inno-js.test/journal.pl & ijl-cli server --port=80", "build": "npm run clean && ijl-cli build --dev", "build:prod": "npm run clean && ijl-cli build", "clean": "rimraf dist", diff --git a/src/__data__/model.ts b/src/__data__/model.ts index 759d77f..208af5c 100644 --- a/src/__data__/model.ts +++ b/src/__data__/model.ts @@ -74,6 +74,7 @@ export interface User { given_name: string; family_name: string; email: string; + picture?: string; } export interface Course { diff --git a/src/app.tsx b/src/app.tsx index ebe2ca9..b85ff1e 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -45,7 +45,7 @@ const App = ({ store }) => { /* rgba(255, 255, 255, 0) 65% */ ); height: 100%; - font-family: KiyosunaSans, Montserrat, RFKrabuler, sans-serif; + /* font-family: KiyosunaSans, Montserrat, RFKrabuler, sans-serif; */ font-weight: 600; } #app { diff --git a/src/components/user-card/index.tsx b/src/components/user-card/index.tsx new file mode 100644 index 0000000..bfb7c6d --- /dev/null +++ b/src/components/user-card/index.tsx @@ -0,0 +1 @@ +export { UserCard } from './user-card' \ No newline at end of file diff --git a/src/components/user-card/style.ts b/src/components/user-card/style.ts new file mode 100644 index 0000000..0616908 --- /dev/null +++ b/src/components/user-card/style.ts @@ -0,0 +1,53 @@ +import styled from '@emotion/styled' +import { css, keyframes } from '@emotion/react' + +export const Avatar = styled.img` + width: 96px; + height: 96px; + margin: 0 auto; + border-radius: 6px; +` + +export const Wrapper = styled.div<{ warn?: boolean; width?: string | number }>` + list-style: none; + background-color: #ffffff; + padding: 16px; + border-radius: 12px; + box-shadow: 2px 2px 6px #0000005c; + transition: all 0.5; + position: relative; + width: 180px; + min-height: 190px; + max-height: 200px; + margin-right: 12px; + padding-bottom: 22px; + ${({ width }) => + width + ? css` + width: ${width}; + ` + : ''} + + ${(props) => + props.warn + ? css` + background-color: #000000; + opacity: 0.7; + color: #e4e4e4; + ` + : ''} +` + +export const AddMissedButton = styled.button` + position: absolute; + bottom: 8px; + right: 12px; + border: none; + background-color: #00000000; + opacity: 0.2; + + :hover { + cursor: pointer; + opacity: 1; + } +` diff --git a/src/components/user-card/user-card.tsx b/src/components/user-card/user-card.tsx new file mode 100644 index 0000000..130c772 --- /dev/null +++ b/src/components/user-card/user-card.tsx @@ -0,0 +1,47 @@ +import React from 'react' +import { sha256 } from 'js-sha256' + +import { User } from '../../__data__/model' + +import { AddMissedButton, Avatar, Wrapper } from './style' + +export function getGravatarURL(email, user) { + if (!email) return void 0 + const address = String(email).trim().toLowerCase() + const hash = sha256(address) + + return `https://www.gravatar.com/avatar/${hash}?d=robohash` +} + +export const UserCard = ({ + student, + present, + onAddUser, + wrapperAS, + width +}: { + student: User + present: boolean + width?: string | number + onAddUser?: (user: User) => void + wrapperAS?: React.ElementType; +}) => { + return ( + + +

+ {student.name || student.preferred_username}{' '} +

+ {onAddUser && !present && ( + onAddUser(student)}> + add + + )} +
+ ) +} + +UserCard.defaultProps = { + wrapperAS: 'div', + onAddUser: void 0, +} diff --git a/src/dashboard.tsx b/src/dashboard.tsx index 1927709..64637fa 100644 --- a/src/dashboard.tsx +++ b/src/dashboard.tsx @@ -1,6 +1,8 @@ import React, { useEffect, Suspense } from 'react' import { Routes, Route, useNavigate } from 'react-router-dom' import { Provider } from 'react-redux' +import { getNavigationsValue } from '@ijl/cli' +import { Box, Container, Spinner, VStack } from '@chakra-ui/react' import { CourseListPage, @@ -8,20 +10,8 @@ import { LessonListPage, UserPage, } from './pages' -import { getNavigationsValue } from '@ijl/cli' -import { Box, Container, Spinner, VStack } from '@chakra-ui/react' -const Redirect = ({ path }) => { - const navigate = useNavigate() - - useEffect(() => { - navigate(path) - }, []) - - return null -} - -const Wrapper = ({ children }) => ( +const Wrapper = ({ children }: { children: React.ReactElement }) => ( diff --git a/src/pages/course-list.tsx b/src/pages/course-list.tsx index cc738cb..6c0ade1 100644 --- a/src/pages/course-list.tsx +++ b/src/pages/course-list.tsx @@ -29,7 +29,7 @@ import { } from '@chakra-ui/react' import { useForm, Controller } from 'react-hook-form' -import { ErrorSpan, MainWrapper } from './style' +import { ErrorSpan } from './style' import { useAppSelector } from '../__data__/store' import { api } from '../__data__/api/api' diff --git a/src/pages/lesson-details.tsx b/src/pages/lesson-details.tsx index 2853848..35daac4 100644 --- a/src/pages/lesson-details.tsx +++ b/src/pages/lesson-details.tsx @@ -2,6 +2,7 @@ import React, { useEffect, useState, useRef, useMemo } from 'react' import { useParams, Link } from 'react-router-dom' import dayjs from 'dayjs' import QRCode from 'qrcode' +import { sha256 } from 'js-sha256' import { getConfigValue, getNavigationsValue } from '@ijl/cli' import { Box, @@ -9,32 +10,38 @@ import { BreadcrumbItem, BreadcrumbLink, Container, - HStack, VStack, Heading, + Stack, } from '@chakra-ui/react' +import { api } from '../__data__/api/api' +import { User } from '../__data__/model' +import { UserCard } from '../components/user-card' + import { QRCanvas, - LessonItem, - Lessonname, - AddMissedButton, - UnorderList, + StudentList, BreadcrumbsWrapper, } from './style' -import { api } from '../__data__/api/api' -import { User } from '../__data__/model' + +export function getGravatarURL(email, user) { + if (!email) return void 0 + const address = String(email).trim().toLowerCase() + const hash = sha256(address) + + // Grab the actual image URL + return `https://www.gravatar.com/avatar/${hash}?d=robohash` +} const LessonDetail = () => { const { lessonId, courseId } = useParams() const canvRef = useRef(null) - const [lesson, setLesson] = useState(null) const { isFetching, - isLoading, data: accessCode, - error, isSuccess, + refetch, } = api.useCreateAccessCodeQuery( { lessonId }, { @@ -49,6 +56,13 @@ const LessonDetail = () => { () => `${location.origin}/journal/u/${lessonId}/${accessCode?.body?._id}`, [accessCode, lessonId], ) + + useEffect(() => { + if (manualAddRqst.isSuccess) { + refetch() + } + }, [manualAddRqst.isSuccess]) + useEffect(() => { if (!isFetching && isSuccess) { QRCode.toCanvas( @@ -83,7 +97,7 @@ const LessonDetail = () => { } } - return allStudents + return allStudents.sort((a, b) => (a.present ? -1 : 1)) }, [accessCode?.body, AllStudents.data]) return ( @@ -110,7 +124,7 @@ const LessonDetail = () => { - + Тема занятия: @@ -125,27 +139,22 @@ const LessonDetail = () => { человек - + - + {studentsArr.map((student) => ( - - - {student.name || student.preferred_username}{' '} - {!student.present && ( - manualAdd({ lessonId, user: student })} - > - add - - )} - - + manualAdd({ lessonId, user })} + /> ))} - - + + ) diff --git a/src/pages/style.ts b/src/pages/style.ts index 7f7355c..10f558a 100644 --- a/src/pages/style.ts +++ b/src/pages/style.ts @@ -1,63 +1,8 @@ import styled from '@emotion/styled' -import { css, keyframes } from '@emotion/react' -import { - Card -} from '@chakra-ui/react' +import { keyframes } from '@emotion/react' export const BreadcrumbsWrapper = styled.div` padding: 12px; -`; - -export const MainWrapper = styled.main` - display: flex; - justify-content: center; - height: 100%; -` - -export const InputWrapper = styled.div` - position: relative; - padding: 12px; - display: flex; - align-items: center; - - @media screen and (max-width: 600px) { - flex-direction: column; - } -` -export const InputLabel = styled.label` - position: absolute; - top: -8px; - left: 24px; - z-index: 2; -` -export const InputElement = styled.input` - border: 1px solid #ccc; - padding: 12px; - font-size: 24px; - border-radius: 8px; - color: #117623; - max-width: 90vw; - box-shadow: inset 7px 8px 20px 8px #4990db12; -` - -export const StyledCard = styled(Card)` - box-shadow: 2px 2px 6px #0000005c; - padding: 16px; - border-radius: 12px; - min-width: 400px; -` - -export const ArrowImg = styled.img` - width: 48px; - height: 48px; -` - -export const IconButton = styled.button` - border: none; - background-color: rgba(0, 0, 0, 0); - display: flex; - align-items: center; - height: 100%; ` const reveal = keyframes` @@ -70,77 +15,15 @@ const reveal = keyframes` } ` -export const StartWrapper = styled.div` - animation: ${reveal} 0.4s ease forwards; - height: calc(100vh - 300px); - position: relative; -` - -export const Wrapper = styled.div` - display: flex; - flex-direction: row; - gap: 20px; - width: auto; -` - -export const UnorderList = styled.ul` +export const StudentList = styled.ul` padding-left: 0px; height: 600px; - overflow: auto; + justify-content: space-evenly; padding-right: 20px; -` - -export const Svg = styled.svg` - position: absolute; - top: 50%; - left: 50%; - transform-origin: 50% 50%; - transform: translate(-50%, -50%); -` - -export const Papper = styled.div` - position: relative; - background-color: #ffffff; - border-radius: 12px; - padding: 32px 16px 16px; - box-shadow: 2px 2px 6px #0000005c; -` - -export const LessonItem = styled.li<{ warn?: boolean }>` - list-style: none; - background-color: #ffffff; - padding: 16px; - border-radius: 12px; - box-shadow: 2px 2px 6px #0000005c; - margin-bottom: 12px; - transition: all 0.5; - - ${(props) => - props.warn - ? css` - background-color: #fde3c5; - color: #919191; - box-shadow: inset 3px 2px 7px #c9b5a9; - ` - : ''} -` - -export const AddMissedButton = styled.button` - float: right; - border: none; - background-color: #00000000; - opacity: 0.1; - - :hover { - cursor: pointer; - opacity: 1; - } -` - -export const Lessonname = styled.span` - display: inline-box; - margin-right: 12px; - margin-bottom: 20px; + display: flex; + flex-direction: row; + flex-wrap: wrap; + gap: 8px; ` export const QRCanvas = styled.canvas` @@ -154,29 +37,3 @@ export const ErrorSpan = styled.span` background-color: #d32f0b; border-radius: 11px; ` - -export const Cross = styled.button` - position: absolute; - right: 19px; - top: 14px; - font-size: 24px; - padding: 7px; - cursor: pointer; - background-color: #fff; - border: none; - - :hover { - background-color: #d7181812; - border-radius: 20px; - } -` - -export const AddButton = styled.button` - background-color: transparent; - border: none; - cursor: pointer; - - :hover { - box-shadow: 3px 2px 5px #00000038; - } -` diff --git a/src/pages/user-page.tsx b/src/pages/user-page.tsx index 35dabf2..d163de3 100644 --- a/src/pages/user-page.tsx +++ b/src/pages/user-page.tsx @@ -1,15 +1,20 @@ import React from 'react' import { useParams } from 'react-router-dom' -import { - ErrorSpan, - LessonItem, - Lessonname, - MainWrapper, - StartWrapper, -} from './style' import { api } from '../__data__/api/api' import dayjs from 'dayjs' +import { + Alert, + AlertIcon, + Box, + Center, + Container, + Heading, + Spinner, + Text, + Stack, +} from '@chakra-ui/react' +import { UserCard } from '../components/user-card' const UserPage = () => { const { lessonId, accessId } = useParams() @@ -20,38 +25,67 @@ const UserPage = () => { skipPollingIfUnfocused: true, }) + if (acc.isLoading) { + return ( + +
+ +
+
+ ) + } + return ( - - - {acc.isLoading &&

Отправляем запрос

} - {acc.isSuccess &&

Успешно

} + + {acc.isLoading &&

Отправляем запрос

} + {acc.isSuccess &&

Успешно

} + + {acc.error && ( + + + + {(acc as any).error?.data?.body?.errorMessage === + 'Code is expired' ? ( + 'Не удалось активировать код доступа. Попробуйте отсканировать код ещё раз' + ) : ( +
{JSON.stringify(acc.error, null, 4)}
+ )} +
+
+ )} + + + + Тема занятия: {ls.data?.body?.name} + - {acc.error && ( -
- - {(acc as any).error?.data?.body?.errorMessage === - 'Code is expired' ? ( - 'Не удалось активировать код доступа. Попробуйте отсканировать код ещё раз' - ) : ( -
{JSON.stringify(acc.error, null, 4)}
- )} -
-
- )} -

Тема занятия - {ls.data?.body?.name}

{dayjs(ls.data?.body?.date).format('DD MMMM YYYYг.')} +
-
    - {ls.data?.body?.students?.map((student, index) => ( - - - {student.name || student.preferred_username} - - - ))} -
-
-
+ + {ls.data?.body?.students?.map((student) => ( + + ))} + +
) } diff --git a/stubs/mocks/lessons/byid/success.json b/stubs/mocks/lessons/byid/success.json index eb16cbf..09af9b4 100644 --- a/stubs/mocks/lessons/byid/success.json +++ b/stubs/mocks/lessons/byid/success.json @@ -17,7 +17,145 @@ "given_name": "Александр", "family_name": "Примаков", "email": "primakovpro@gmail.com" - } + },{ + "sub": "fcde3f22-d9ba-412a-a572-c59e515a290f", + "email_verified": true, + "name": "Мария Капитанова", + "preferred_username": "maryaKapitan@gmail.com", + "given_name": "Мария", + "family_name": "Капитанова", + "email": "maryaKapitan@gmail.com", + "picture": "https://lh3.googleusercontent.com/a/ACg8ocJgIjjOFD2YUSyRF5kH4jaysE6X5p-kq0Cg0CFncfMi=s96-c" + }, + { + "sub": "5b072deb-33ee-443e-9718-3b5720a3dfb7", + "email_verified": true, + "name": "Евгений Кореной", + "preferred_username": "koren@gmail.com", + "given_name": "Кореной", + "family_name": "Евгений", + "email": "koren@gmail.com", + "picture": "https://lh3.googleusercontent.com/a/ACg8ocJpVhDeG-Rpjjm2Un6r8ACz_s_injuIFKpzXf3qmyCn3Cg=s96-c" + }, + { + "sub": "7adf0cd1-cf07-4079-88d8-1a5c9b8f42c2", + "email_verified": true, + "name": "Ирина Игнатьева", + "preferred_username": "irign@gmailcom", + "given_name": "Ирина", + "family_name": "Игнатьева", + "email": "irign@gmailcom", + "picture": "https://lh3.googleusercontent.com/a/ACg8ocL45E4Gt8D5oyIl3ipkcGsv4ShWGs3bdlwEMA_1rzGZ=s96-c" + }, + { + "sub": "95ccc005-95b9-4305-9447-364a32033911", + "email_verified": true, + "name": "Иван Петров", + "preferred_username": "petrov@mail.ru", + "given_name": "Иван", + "family_name": "Петров", + "email": "petrov@mail.ru", + "picture": "https://lh3.googleusercontent.com/a/ACg8ocIgQn5mfDAh2djx-3ofG9z1Em26ZyuUgVPd-6rDOl6z=s96-c" + }, + { + "sub": "ede1ef2c-6ecf-484a-8fb8-282a77e1caa1", + "email_verified": true, + "name": "Константин Тимуров", + "preferred_username": "konstantK@gmail.com", + "given_name": "Константин", + "family_name": "Тимуров", + "email": "konstantK@gmail.com", + "picture": "https://lh3.googleusercontent.com/a/ACg8ocJjnOfqaoAU_D4STrJPN9fPOeJ8tv60WbWVZu2ZWcHs=s96-c" + }, + { + "sub": "92cc6a15-805c-4439-b592-b23f32d6d208", + "email_verified": true, + "name": "Александра Питерская", + "preferred_username": "piteralex@gmail.com", + "given_name": "Александра", + "family_name": "Питерская", + "email": "piteralex@gmail.com", + "picture": "https://lh3.googleusercontent.com/a/ACg8ocKhbCbWvBBc_m7bjU5sLCE-dQ-KygBk-aUCSR8XaYtq=s96-c" + }, + { + "sub": "4a3ba8b8-4120-4877-a160-be9ba4d5b3e3", + "email_verified": true, + "name": "Анастасия Светлых", + "preferred_username": "anastasya@gmail.ocm", + "given_name": "Анастасия", + "family_name": "Светлых", + "email": "anastasya@gmail.ocm", + "picture": "https://lh3.googleusercontent.com/a/ACg8ocJnsM8UGhbH806yLVgWZ17g3-gJFVcG0Uz5kvqT7dvC=s96-c" + }, + { + "sub": "b4634921-00b3-4082-9284-8ac47f269394", + "email_verified": true, + "name": "Эмилия Снежко", + "preferred_username": "emi@mail.ru", + "given_name": "Эмилия", + "family_name": "Снежко", + "email": "emi@mail.ru", + "picture": "https://lh3.googleusercontent.com/a/ACg8ocI98dzSFQDPr2LXMPFEUX8KLY6bY2m08O_aAj2B5KVNKg=s96-c" + }, + { + "sub": "bf1a95aa-39a2-4528-9b8d-319409995df5", + "email_verified": true, + "name": "Юлия Бобова", + "preferred_username": "bobova@gmail.com", + "given_name": "Юлия", + "family_name": "Бобова", + "email": "bobova@gmail.com", + "picture": "https://lh3.googleusercontent.com/a/ACg8ocJ_Ud4iI-jgqcJ3QJcWpESbRLX_C1BnB8_7uTTC-4Dn=s96-c" + }, + { + "sub": "c273a3e3-f7ba-4057-8c57-a1f43b6174a5", + "email_verified": true, + "name": "Анна Самоварова", + "preferred_username": "samovar@gmail.com", + "given_name": "Анна", + "family_name": "Самоварова", + "email": "samovar@gmail.com", + "picture": "https://lh3.googleusercontent.com/a/ACg8ocJOhIMdQkXPd55wTMgTTkUCnqbsu4EncgEPm67iz_mK=s96-c" + }, + { + "sub": "8555885b-715c-4dee-a7c5-9563a6a05211", + "email_verified": true, + "name": "Евгения Жужова", + "preferred_username": "zhuzhova@gmail.com", + "given_name": "Евгения", + "family_name": "Жужова", + "email": "zhuzhova@gmail.com", + "picture": "https://lh3.googleusercontent.com/a/ACg8ocJUtJBAVBm642AxoGpMDDMV8CPu3MEoLjU3hmO7oisG=s96-c" + }, + { + "sub": "12dee54f-64e9-4be3-9cb0-02ff07ab24fe", + "email_verified": true, + "name": "Эдгар Петренко", + "preferred_username": "petrenk@mail.ru", + "given_name": "Эдгар", + "family_name": "Петренко", + "email": "petrenk@mail.ru", + "picture": "https://lh3.googleusercontent.com/a/ACg8ocLgKAZag32kpGVHMVbh_GsU-rX_MAtmeVIPoov0ZPBYIA=s96-c" + }, + { + "sub": "4082b72a-4730-4841-ad68-06a0e19263df", + "email_verified": true, + "name": "Елена Вавилон", + "preferred_username": "elenvavil@mail.ru", + "given_name": "Елена", + "family_name": "Вавилон", + "email": "elenvavil@mail.ru", + "picture": "https://lh3.googleusercontent.com/a/ACg8ocKXcmzcqRch2--j2Ge2m9e8MIOZ8y1MjsQ0cSEoXOmW=s96-c" + }, + { + "sub": "9e8a08d8-d76a-4f26-99c5-9a1d3c067104", + "email_verified": true, + "name": "Ольга Шарова", + "preferred_username": "julyashap", + "given_name": "Ольга", + "family_name": "Шарова", + "email": "sharova@mail.ru" + } ], "date": "2024-02-28T20:37:00.057Z", "created": "2024-02-28T20:37:00.057Z",