Добавлены новые зависимости: "react-select" и "@floating-ui/core". Реализована локализация с использованием i18next, добавлены переводы для английского и русского языков. Обновлены компоненты для поддержки локализации, включая AppHeader, Attendance, Dashboard и другие. Улучшена логика отображения данных и взаимодействия с пользователем.
This commit is contained in:
@@ -22,19 +22,11 @@ import {
|
||||
import { CopyIcon, ChevronDownIcon, ChevronUpIcon } from '@chakra-ui/icons'
|
||||
import { FaSmile, FaMeh, FaFrown, FaSadTear } from 'react-icons/fa'
|
||||
import dayjs from 'dayjs'
|
||||
import { sha256 } from 'js-sha256'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { getGravatarURL } from '../../../utils/gravatar'
|
||||
import { ShortText } from './ShortText'
|
||||
import { AttendanceData } from '../hooks'
|
||||
|
||||
// Функция для получения URL аватарки через Gravatar
|
||||
function getGravatarURL(email) {
|
||||
if (!email) return undefined
|
||||
const address = String(email).trim().toLowerCase()
|
||||
const hash = sha256(address)
|
||||
|
||||
return `https://www.gravatar.com/avatar/${hash}?d=robohash`
|
||||
}
|
||||
|
||||
interface AttendanceTableProps {
|
||||
data: AttendanceData
|
||||
}
|
||||
@@ -42,6 +34,7 @@ interface AttendanceTableProps {
|
||||
export const AttendanceTable: React.FC<AttendanceTableProps> = ({ data }) => {
|
||||
const { colorMode } = useColorMode()
|
||||
const toast = useToast()
|
||||
const { t } = useTranslation()
|
||||
const [showTable, setShowTable] = useState(false)
|
||||
|
||||
const getPresentColor = () => {
|
||||
@@ -60,25 +53,25 @@ export const AttendanceTable: React.FC<AttendanceTableProps> = ({ data }) => {
|
||||
return {
|
||||
icon: FaSmile,
|
||||
color: 'green.500',
|
||||
label: 'Отличная посещаемость'
|
||||
label: t('journal.pl.attendance.emojis.excellent')
|
||||
}
|
||||
} else if (attendanceRate >= 0.75) {
|
||||
return {
|
||||
icon: FaMeh,
|
||||
color: 'blue.400',
|
||||
label: 'Хорошая посещаемость'
|
||||
label: t('journal.pl.attendance.emojis.good')
|
||||
}
|
||||
} else if (attendanceRate >= 0.5) {
|
||||
return {
|
||||
icon: FaFrown,
|
||||
color: 'orange.400',
|
||||
label: 'Низкая посещаемость'
|
||||
label: t('journal.pl.attendance.emojis.poor')
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
icon: FaSadTear,
|
||||
color: 'red.500',
|
||||
label: 'Критически низкая посещаемость'
|
||||
label: t('journal.pl.attendance.emojis.none')
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -97,11 +90,11 @@ export const AttendanceTable: React.FC<AttendanceTableProps> = ({ data }) => {
|
||||
})
|
||||
|
||||
// Добавляем столбцы даты и названия занятия
|
||||
headerRow.push('Дата', 'Название занятия')
|
||||
headerRow.push(t('journal.pl.common.date'), t('journal.pl.common.lessonName'))
|
||||
|
||||
// Добавляем студентов
|
||||
data.students.forEach(student => {
|
||||
headerRow.push(student.name || student.value || 'Имя не определено')
|
||||
headerRow.push(student.name || student.value || t('journal.pl.common.name'))
|
||||
})
|
||||
|
||||
// Добавляем заголовок в таблицу
|
||||
@@ -139,8 +132,8 @@ export const AttendanceTable: React.FC<AttendanceTableProps> = ({ data }) => {
|
||||
navigator.clipboard.writeText(finalContent)
|
||||
.then(() => {
|
||||
toast({
|
||||
title: 'Скопировано в буфер обмена',
|
||||
description: 'Таблица успешно скопирована без сокращений',
|
||||
title: t('journal.pl.attendance.table.copySuccess'),
|
||||
description: t('journal.pl.attendance.table.copySuccessDescription'),
|
||||
status: 'success',
|
||||
duration: 3000,
|
||||
isClosable: true,
|
||||
@@ -148,8 +141,8 @@ export const AttendanceTable: React.FC<AttendanceTableProps> = ({ data }) => {
|
||||
})
|
||||
.catch(err => {
|
||||
toast({
|
||||
title: 'Ошибка копирования',
|
||||
description: 'Не удалось скопировать таблицу',
|
||||
title: t('journal.pl.attendance.table.copyError'),
|
||||
description: t('journal.pl.attendance.table.copyErrorDescription'),
|
||||
status: 'error',
|
||||
duration: 3000,
|
||||
isClosable: true,
|
||||
@@ -171,7 +164,7 @@ export const AttendanceTable: React.FC<AttendanceTableProps> = ({ data }) => {
|
||||
|
||||
return {
|
||||
student,
|
||||
name: student.name || student.value || 'Имя не определено',
|
||||
name: student.name || student.value || t('journal.pl.common.name'),
|
||||
email: student.email,
|
||||
picture: student.picture || getGravatarURL(student.email),
|
||||
attendedCount,
|
||||
@@ -182,7 +175,7 @@ export const AttendanceTable: React.FC<AttendanceTableProps> = ({ data }) => {
|
||||
}
|
||||
|
||||
if (!data.attendance?.length || !data.students?.length) {
|
||||
return <Box>Нет данных для отображения</Box>
|
||||
return <Box>{t('journal.pl.common.noData')}</Box>
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -199,7 +192,7 @@ export const AttendanceTable: React.FC<AttendanceTableProps> = ({ data }) => {
|
||||
onClick={copyTableData}
|
||||
mr={2}
|
||||
>
|
||||
Копировать таблицу
|
||||
{t('journal.pl.attendance.table.copy')}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
@@ -208,7 +201,7 @@ export const AttendanceTable: React.FC<AttendanceTableProps> = ({ data }) => {
|
||||
variant="outline"
|
||||
onClick={() => setShowTable(!showTable)}
|
||||
>
|
||||
{showTable ? 'Скрыть таблицу' : 'Показать таблицу'}
|
||||
{showTable ? t('journal.pl.attendance.table.hide') : t('journal.pl.attendance.table.show')}
|
||||
</Button>
|
||||
</Flex>
|
||||
|
||||
@@ -221,7 +214,7 @@ export const AttendanceTable: React.FC<AttendanceTableProps> = ({ data }) => {
|
||||
return (
|
||||
<Tooltip
|
||||
key={student.sub}
|
||||
label={`${emoji.label}: ${attendedCount} из ${totalLessons} занятий (${attendance.toFixed(0)}%)`}
|
||||
label={`${emoji.label}: ${attendedCount} ${t('journal.pl.common.of')} ${totalLessons} ${t('journal.pl.common.students')} (${attendance.toFixed(0)}%)`}
|
||||
hasArrow
|
||||
>
|
||||
<Box
|
||||
@@ -238,13 +231,13 @@ export const AttendanceTable: React.FC<AttendanceTableProps> = ({ data }) => {
|
||||
name={name}
|
||||
>
|
||||
<AvatarBadge boxSize='2em' bg={emoji.color}>
|
||||
<Icon as={emoji.icon} color="white" boxSize={6} />
|
||||
<Icon as={emoji.icon} color="white" boxSize={7} />
|
||||
</AvatarBadge>
|
||||
</Avatar>
|
||||
<Box>
|
||||
<Text fontSize="sm" fontWeight="medium" isTruncated maxW="110px">{name}</Text>
|
||||
<Text fontSize="xs" mt={1} color={colorMode === 'dark' ? 'gray.400' : 'gray.600'}>
|
||||
{attendedCount} из {totalLessons} ({attendance.toFixed(0)}%)
|
||||
{attendedCount} {t('journal.pl.common.of')} {totalLessons} ({attendance.toFixed(0)}%)
|
||||
</Text>
|
||||
</Box>
|
||||
</HStack>
|
||||
@@ -269,17 +262,17 @@ export const AttendanceTable: React.FC<AttendanceTableProps> = ({ data }) => {
|
||||
{data.teachers?.map(teacher => (
|
||||
<Th key={teacher.id}>{teacher.value}</Th>
|
||||
))}
|
||||
<Th>Дата</Th>
|
||||
<Th>Название занятия</Th>
|
||||
<Th>{t('journal.pl.common.date')}</Th>
|
||||
<Th>{t('journal.pl.common.lessonName')}</Th>
|
||||
{data.students.map((student) => (
|
||||
<Th key={student.sub}>
|
||||
<HStack>
|
||||
<Avatar
|
||||
size="xs"
|
||||
src={student.picture || getGravatarURL(student.email)}
|
||||
name={student.name || student.value || 'Имя не определено'}
|
||||
name={student.name || student.value || t('journal.pl.common.name')}
|
||||
/>
|
||||
<Text>{student.name || student.value || 'Имя не определено'}</Text>
|
||||
<Text>{student.name || student.value || t('journal.pl.common.name')}</Text>
|
||||
</HStack>
|
||||
</Th>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user