Добавлено состояние для отслеживания пульсации в компоненте LessonDetail при изменении количества студентов. Реализована функция для определения цвета на основе посещаемости. Обновлен компонент LessonList для отображения статистики посещаемости с использованием новых стилей и анимаций.
This commit is contained in:
@@ -26,8 +26,14 @@ import {
|
||||
AlertDialogHeader,
|
||||
AlertDialogOverlay,
|
||||
useBreakpointValue,
|
||||
Flex,
|
||||
Menu,
|
||||
MenuButton,
|
||||
MenuList,
|
||||
MenuItem,
|
||||
useColorMode,
|
||||
} from '@chakra-ui/react'
|
||||
import { AddIcon } from '@chakra-ui/icons'
|
||||
import { AddIcon, EditIcon } from '@chakra-ui/icons'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { useAppSelector } from '../../__data__/store'
|
||||
@@ -35,6 +41,7 @@ import { api } from '../../__data__/api/api'
|
||||
import { isTeacher } from '../../utils/user'
|
||||
import { Lesson } from '../../__data__/model'
|
||||
import { XlSpinner } from '../../components/xl-spinner'
|
||||
import { qrCode } from '../../assets'
|
||||
|
||||
import { LessonForm } from './components/lessons-form'
|
||||
import { Bar } from './components/bar'
|
||||
@@ -58,6 +65,7 @@ const LessonList = () => {
|
||||
error: errorGenerateLessons,
|
||||
isSuccess: isSuccessGenerateLessons
|
||||
}, ] = api.useGenerateLessonsMutation()
|
||||
const { colorMode } = useColorMode()
|
||||
|
||||
const [createLesson, crLQuery] = api.useCreateLessonMutation()
|
||||
const [deleteLesson, deletingRqst] = api.useDeleteLessonMutation()
|
||||
@@ -76,6 +84,23 @@ const LessonList = () => {
|
||||
[data, data?.body],
|
||||
)
|
||||
|
||||
// Найдем максимальное количество студентов среди всех уроков
|
||||
const maxStudents = useMemo(() => {
|
||||
if (!sorted || sorted.length === 0) return 1
|
||||
const max = Math.max(...sorted.map(lesson => lesson.students?.length || 0))
|
||||
return max > 0 ? max : 1 // Избегаем деления на ноль
|
||||
}, [sorted])
|
||||
|
||||
// Функция для определения цвета на основе посещаемости
|
||||
const getAttendanceColor = (attendance: number) => {
|
||||
const percentage = (attendance / maxStudents) * 100
|
||||
if (percentage > 80) return { bg: 'green.100', color: 'green.800', dark: { bg: 'green.800', color: 'green.100' } }
|
||||
if (percentage > 60) return { bg: 'teal.100', color: 'teal.800', dark: { bg: 'teal.800', color: 'teal.100' } }
|
||||
if (percentage > 40) return { bg: 'yellow.100', color: 'yellow.800', dark: { bg: 'yellow.800', color: 'yellow.100' } }
|
||||
if (percentage > 20) return { bg: 'orange.100', color: 'orange.800', dark: { bg: 'orange.800', color: 'orange.100' } }
|
||||
return { bg: 'red.100', color: 'red.800', dark: { bg: 'red.800', color: 'red.100' } }
|
||||
}
|
||||
|
||||
const lessonCalc = useMemo(() => {
|
||||
if (!isSuccess) {
|
||||
return []
|
||||
@@ -379,38 +404,157 @@ const LessonList = () => {
|
||||
))}
|
||||
</Box>
|
||||
) : (
|
||||
<TableContainer whiteSpace="wrap" pb={13}>
|
||||
<Table variant="striped" colorScheme="cyan">
|
||||
<Thead>
|
||||
<Tr>
|
||||
{isTeacher(user) && (
|
||||
<Th align="center" width={1}>
|
||||
{t('journal.pl.lesson.link')}
|
||||
</Th>
|
||||
)}
|
||||
<Th textAlign="center" width={1}>
|
||||
{groupByDate ? t('journal.pl.lesson.time') : t('journal.pl.common.date')}
|
||||
</Th>
|
||||
<Th width="100%">{t('journal.pl.common.name')}</Th>
|
||||
{isTeacher(user) && <Th>{t('journal.pl.lesson.action')}</Th>}
|
||||
<Th isNumeric>{t('journal.pl.common.marked')}</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{lessonCalc?.map(({ data: lessons, date }) => (
|
||||
<LessonItems
|
||||
courseId={courseId}
|
||||
date={date}
|
||||
isTeacher={isTeacher(user)}
|
||||
lessons={lessons}
|
||||
setlessonToDelete={setlessonToDelete}
|
||||
setEditLesson={handleEditLesson}
|
||||
key={date}
|
||||
/>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
<Box pb={13}>
|
||||
{lessonCalc?.map(({ data: lessons, date }) => (
|
||||
<Box key={date} mb={6}>
|
||||
{date && (
|
||||
<Box
|
||||
p={3}
|
||||
mb={4}
|
||||
bg="cyan.50"
|
||||
borderRadius="md"
|
||||
_dark={{ bg: "cyan.900" }}
|
||||
boxShadow="sm"
|
||||
>
|
||||
<Text fontWeight="bold" fontSize="lg">
|
||||
{formatDate(date, 'DD MMMM YYYY')}
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
<Box>
|
||||
{lessons.map((lesson, index) => (
|
||||
<Box
|
||||
key={lesson.id}
|
||||
borderRadius="lg"
|
||||
boxShadow="md"
|
||||
bg="white"
|
||||
_dark={{ bg: "gray.700" }}
|
||||
transition="all 0.3s"
|
||||
_hover={{
|
||||
transform: "translateX(5px)",
|
||||
boxShadow: "lg"
|
||||
}}
|
||||
overflow="hidden"
|
||||
position="relative"
|
||||
mb={4}
|
||||
animation={`slideIn 0.6s ease-out ${index * 0.15}s both`}
|
||||
sx={{
|
||||
'@keyframes slideIn': {
|
||||
'0%': {
|
||||
opacity: 0,
|
||||
transform: 'translateX(-30px)'
|
||||
},
|
||||
'100%': {
|
||||
opacity: 1,
|
||||
transform: 'translateX(0)'
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Flex direction={{ base: "column", sm: "row" }}>
|
||||
{/* QR код и ссылка - левая часть карточки */}
|
||||
{isTeacher(user) && (
|
||||
<Link
|
||||
to={`${getNavigationValue('journal.main')}/lesson/${courseId}/${lesson.id}`}
|
||||
>
|
||||
<Box
|
||||
p={4}
|
||||
bg="cyan.500"
|
||||
_dark={{ bg: "cyan.600" }}
|
||||
color="white"
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
transition="all 0.2s"
|
||||
_hover={{ bg: "cyan.600", _dark: { bg: "cyan.700" } }}
|
||||
height="100%"
|
||||
minW="150px"
|
||||
>
|
||||
<Box
|
||||
mr={0}
|
||||
bg="white"
|
||||
borderRadius="md"
|
||||
p={2}
|
||||
display="flex"
|
||||
>
|
||||
<img width={32} src={qrCode} alt="QR код" />
|
||||
</Box>
|
||||
</Box>
|
||||
</Link>
|
||||
)}
|
||||
|
||||
{/* Содержимое карточки */}
|
||||
<Box p={5} w="100%" display="flex" flexDirection="column" justifyContent="space-between">
|
||||
<Flex mb={3} justify="space-between" align="center">
|
||||
{/* Название урока */}
|
||||
<Text fontWeight="bold" fontSize="xl" lineHeight="1.4" flex="1">
|
||||
{lesson.name}
|
||||
</Text>
|
||||
|
||||
<Text fontSize="sm" color="gray.500" _dark={{ color: "gray.300" }} ml={3} whiteSpace="nowrap">
|
||||
{formatDate(lesson.date, groupByDate ? 'HH:mm' : 'HH:mm DD.MM.YY')}
|
||||
</Text>
|
||||
</Flex>
|
||||
|
||||
{/* Нижняя часть с метками и действиями */}
|
||||
<Flex justifyContent="space-between" alignItems="center" mt={1}>
|
||||
<Flex align="center">
|
||||
<Text fontSize="sm" mr={2}>
|
||||
{t('journal.pl.common.marked')}:
|
||||
</Text>
|
||||
<Text
|
||||
px={2}
|
||||
py={1}
|
||||
bg={getAttendanceColor(lesson.students.length).bg}
|
||||
color={getAttendanceColor(lesson.students.length).color}
|
||||
_dark={{
|
||||
bg: getAttendanceColor(lesson.students.length).dark.bg,
|
||||
color: getAttendanceColor(lesson.students.length).dark.color
|
||||
}}
|
||||
borderRadius="md"
|
||||
fontWeight="bold"
|
||||
fontSize="sm"
|
||||
>
|
||||
{lesson.students.length}
|
||||
</Text>
|
||||
</Flex>
|
||||
|
||||
{isTeacher(user) && (
|
||||
<Menu>
|
||||
<MenuButton
|
||||
as={Button}
|
||||
size="sm"
|
||||
colorScheme="cyan"
|
||||
variant="ghost"
|
||||
rightIcon={<EditIcon />}
|
||||
>
|
||||
{t('journal.pl.edit')}
|
||||
</MenuButton>
|
||||
<MenuList>
|
||||
<MenuItem
|
||||
onClick={() => handleEditLesson(lesson)}
|
||||
icon={<EditIcon />}
|
||||
>
|
||||
{t('journal.pl.edit')}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={() => setlessonToDelete(lesson)}
|
||||
color="red.500"
|
||||
>
|
||||
{t('journal.pl.delete')}
|
||||
</MenuItem>
|
||||
</MenuList>
|
||||
</Menu>
|
||||
)}
|
||||
</Flex>
|
||||
</Box>
|
||||
</Flex>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
</Container>
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user