init + api use
This commit is contained in:
180
src/pages/tasks/TasksListPage.tsx
Normal file
180
src/pages/tasks/TasksListPage.tsx
Normal file
@@ -0,0 +1,180 @@
|
||||
import React, { useState } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import {
|
||||
Box,
|
||||
Heading,
|
||||
Button,
|
||||
Table,
|
||||
Flex,
|
||||
Input,
|
||||
HStack,
|
||||
Text,
|
||||
IconButton,
|
||||
Badge,
|
||||
createListCollection,
|
||||
} from '@chakra-ui/react'
|
||||
import { useGetTasksQuery, useDeleteTaskMutation } from '../../__data__/api/api'
|
||||
import { URLs } from '../../__data__/urls'
|
||||
import { LoadingSpinner } from '../../components/LoadingSpinner'
|
||||
import { ErrorAlert } from '../../components/ErrorAlert'
|
||||
import { EmptyState } from '../../components/EmptyState'
|
||||
import { ConfirmDialog } from '../../components/ConfirmDialog'
|
||||
import type { ChallengeTask } from '../../types/challenge'
|
||||
import { toaster } from '../../components/ui/toaster'
|
||||
|
||||
export const TasksListPage: React.FC = () => {
|
||||
const navigate = useNavigate()
|
||||
const { data: tasks, isLoading, error, refetch } = useGetTasksQuery()
|
||||
const [deleteTask, { isLoading: isDeleting }] = useDeleteTaskMutation()
|
||||
|
||||
const [searchQuery, setSearchQuery] = useState('')
|
||||
const [taskToDelete, setTaskToDelete] = useState<ChallengeTask | null>(null)
|
||||
|
||||
const handleDeleteTask = async () => {
|
||||
if (!taskToDelete) return
|
||||
|
||||
try {
|
||||
await deleteTask(taskToDelete.id).unwrap()
|
||||
toaster.create({
|
||||
title: 'Успешно',
|
||||
description: 'Задание удалено',
|
||||
type: 'success',
|
||||
})
|
||||
setTaskToDelete(null)
|
||||
} catch (err) {
|
||||
toaster.create({
|
||||
title: 'Ошибка',
|
||||
description: 'Не удалось удалить задание',
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
return <LoadingSpinner message="Загрузка заданий..." />
|
||||
}
|
||||
|
||||
if (error || !tasks) {
|
||||
return <ErrorAlert message="Не удалось загрузить список заданий" onRetry={refetch} />
|
||||
}
|
||||
|
||||
const filteredTasks = tasks.filter((task) =>
|
||||
task.title.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
)
|
||||
|
||||
const formatDate = (dateStr: string) => {
|
||||
return new Date(dateStr).toLocaleDateString('ru-RU', {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Flex justify="space-between" align="center" mb={6}>
|
||||
<Heading>Задания</Heading>
|
||||
<Button colorPalette="teal" onClick={() => navigate(URLs.taskNew)}>
|
||||
+ Создать задание
|
||||
</Button>
|
||||
</Flex>
|
||||
|
||||
{tasks.length > 0 && (
|
||||
<Box mb={4}>
|
||||
<Input
|
||||
placeholder="Поиск по названию..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
maxW="400px"
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{filteredTasks.length === 0 && tasks.length === 0 ? (
|
||||
<EmptyState
|
||||
title="Нет заданий"
|
||||
description="Создайте первое задание для начала работы"
|
||||
actionLabel="Создать задание"
|
||||
onAction={() => navigate(URLs.taskNew)}
|
||||
/>
|
||||
) : filteredTasks.length === 0 ? (
|
||||
<EmptyState
|
||||
title="Ничего не найдено"
|
||||
description={`По запросу "${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.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{filteredTasks.map((task) => (
|
||||
<Table.Row key={task.id}>
|
||||
<Table.Cell fontWeight="medium">{task.title}</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Text fontSize="sm" color="gray.600">
|
||||
{task.creator?.preferred_username || 'N/A'}
|
||||
</Text>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Text fontSize="sm" color="gray.600">
|
||||
{formatDate(task.createdAt)}
|
||||
</Text>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
{task.hiddenInstructions ? (
|
||||
<Badge colorPalette="purple" variant="subtle">
|
||||
🔒 Есть
|
||||
</Badge>
|
||||
) : (
|
||||
<Text fontSize="sm" color="gray.400">
|
||||
—
|
||||
</Text>
|
||||
)}
|
||||
</Table.Cell>
|
||||
<Table.Cell textAlign="right">
|
||||
<HStack gap={2} justify="flex-end">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={() => navigate(URLs.taskEdit(task.id))}
|
||||
>
|
||||
Редактировать
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
colorPalette="red"
|
||||
onClick={() => setTaskToDelete(task)}
|
||||
>
|
||||
Удалить
|
||||
</Button>
|
||||
</HStack>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
))}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<ConfirmDialog
|
||||
isOpen={!!taskToDelete}
|
||||
onClose={() => setTaskToDelete(null)}
|
||||
onConfirm={handleDeleteTask}
|
||||
title="Удалить задание"
|
||||
message={`Вы уверены, что хотите удалить задание "${taskToDelete?.title}"? Это действие нельзя отменить.`}
|
||||
confirmLabel="Удалить"
|
||||
isLoading={isDeleting}
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user