Добавлено состояние для отслеживания пульсации в компоненте LessonDetail при изменении количества студентов. Реализована функция для определения цвета на основе посещаемости. Обновлен компонент LessonList для отображения статистики посещаемости с использованием новых стилей и анимаций.

This commit is contained in:
2025-03-23 22:19:43 +03:00
parent 5885124630
commit e50fb4fd82
2 changed files with 251 additions and 38 deletions
+177 -33
View File
@@ -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>
</>