Enhance localization support by integrating i18next for translations across various components and pages. Update UI elements to utilize translated strings for improved user experience in both English and Russian. Additionally, refactor the Toaster component to support a context-based approach for toast notifications.

This commit is contained in:
Primakov Alexandr Alexandrovich
2025-11-04 10:25:12 +03:00
parent daa44521b9
commit 44a7ac2bfd
19 changed files with 892 additions and 293 deletions

View File

@@ -1,5 +1,6 @@
import React, { useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { useTranslation } from 'react-i18next'
import {
Box,
Heading,
@@ -9,9 +10,7 @@ import {
Input,
HStack,
Text,
IconButton,
Badge,
createListCollection,
} from '@chakra-ui/react'
import { useGetTasksQuery, useDeleteTaskMutation } from '../../__data__/api/api'
import { URLs } from '../../__data__/urls'
@@ -24,6 +23,7 @@ import { toaster } from '../../components/ui/toaster'
export const TasksListPage: React.FC = () => {
const navigate = useNavigate()
const { t } = useTranslation()
const { data: tasks, isLoading, error, refetch } = useGetTasksQuery()
const [deleteTask, { isLoading: isDeleting }] = useDeleteTaskMutation()
@@ -36,26 +36,26 @@ export const TasksListPage: React.FC = () => {
try {
await deleteTask(taskToDelete.id).unwrap()
toaster.create({
title: 'Успешно',
description: 'Задание удалено',
title: t('challenge.admin.common.success'),
description: t('challenge.admin.tasks.deleted'),
type: 'success',
})
setTaskToDelete(null)
} catch (err) {
} catch (_err) {
toaster.create({
title: 'Ошибка',
description: 'Не удалось удалить задание',
title: t('challenge.admin.common.error'),
description: t('challenge.admin.tasks.delete.error'),
type: 'error',
})
}
}
if (isLoading) {
return <LoadingSpinner message="Загрузка заданий..." />
return <LoadingSpinner message={t('challenge.admin.tasks.list.loading')} />
}
if (error || !tasks) {
return <ErrorAlert message="Не удалось загрузить список заданий" onRetry={refetch} />
return <ErrorAlert message={t('challenge.admin.tasks.list.load.error')} onRetry={refetch} />
}
const filteredTasks = tasks.filter((task) =>
@@ -73,16 +73,16 @@ export const TasksListPage: React.FC = () => {
return (
<Box>
<Flex justify="space-between" align="center" mb={6}>
<Heading>Задания</Heading>
<Heading>{t('challenge.admin.tasks.list.title')}</Heading>
<Button colorPalette="teal" onClick={() => navigate(URLs.taskNew)}>
+ Создать задание
{t('challenge.admin.tasks.list.create.button')}
</Button>
</Flex>
{tasks.length > 0 && (
<Box mb={4}>
<Input
placeholder="Поиск по названию..."
placeholder={t('challenge.admin.tasks.list.search.placeholder')}
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
maxW="400px"
@@ -92,26 +92,26 @@ export const TasksListPage: React.FC = () => {
{filteredTasks.length === 0 && tasks.length === 0 ? (
<EmptyState
title="Нет заданий"
description="Создайте первое задание для начала работы"
actionLabel="Создать задание"
title={t('challenge.admin.tasks.list.empty.title')}
description={t('challenge.admin.tasks.list.empty.description')}
actionLabel={t('challenge.admin.tasks.list.empty.action')}
onAction={() => navigate(URLs.taskNew)}
/>
) : filteredTasks.length === 0 ? (
<EmptyState
title="Ничего не найдено"
description={`По запросу "${searchQuery}" ничего не найдено`}
title={t('challenge.admin.common.not.found')}
description={t('challenge.admin.tasks.list.search.empty', { query: searchQuery })}
/>
) : (
<Box bg="white" borderRadius="lg" boxShadow="sm" borderWidth="1px" borderColor="gray.200" overflowX="auto">
<Table.Root size="sm">
<Table.Header>
<Table.Row>
<Table.ColumnHeader>Название</Table.ColumnHeader>
<Table.ColumnHeader>Создатель</Table.ColumnHeader>
<Table.ColumnHeader>Дата создания</Table.ColumnHeader>
<Table.ColumnHeader>Скрытые инструкции</Table.ColumnHeader>
<Table.ColumnHeader textAlign="right">Действия</Table.ColumnHeader>
<Table.ColumnHeader>{t('challenge.admin.tasks.list.table.title')}</Table.ColumnHeader>
<Table.ColumnHeader>{t('challenge.admin.tasks.list.table.creator')}</Table.ColumnHeader>
<Table.ColumnHeader>{t('challenge.admin.tasks.list.table.created')}</Table.ColumnHeader>
<Table.ColumnHeader>{t('challenge.admin.tasks.list.table.hidden.instructions')}</Table.ColumnHeader>
<Table.ColumnHeader textAlign="right">{t('challenge.admin.tasks.list.table.actions')}</Table.ColumnHeader>
</Table.Row>
</Table.Header>
<Table.Body>
@@ -131,7 +131,7 @@ export const TasksListPage: React.FC = () => {
<Table.Cell>
{task.hiddenInstructions ? (
<Badge colorPalette="purple" variant="subtle">
🔒 Есть
{t('challenge.admin.tasks.list.badge.has.instructions')}
</Badge>
) : (
<Text fontSize="sm" color="gray.400">
@@ -146,7 +146,7 @@ export const TasksListPage: React.FC = () => {
variant="ghost"
onClick={() => navigate(URLs.taskEdit(task.id))}
>
Редактировать
{t('challenge.admin.tasks.list.button.edit')}
</Button>
<Button
size="sm"
@@ -154,7 +154,7 @@ export const TasksListPage: React.FC = () => {
colorPalette="red"
onClick={() => setTaskToDelete(task)}
>
Удалить
{t('challenge.admin.tasks.list.button.delete')}
</Button>
</HStack>
</Table.Cell>
@@ -169,9 +169,9 @@ export const TasksListPage: React.FC = () => {
isOpen={!!taskToDelete}
onClose={() => setTaskToDelete(null)}
onConfirm={handleDeleteTask}
title="Удалить задание"
message={`Вы уверены, что хотите удалить задание "${taskToDelete?.title}"? Это действие нельзя отменить.`}
confirmLabel="Удалить"
title={t('challenge.admin.tasks.delete.confirm.title')}
message={t('challenge.admin.tasks.delete.confirm.message', { title: taskToDelete?.title })}
confirmLabel={t('challenge.admin.tasks.delete.confirm.button')}
isLoading={isDeleting}
/>
</Box>