init + api use

This commit is contained in:
Primakov Alexandr Alexandrovich
2025-11-03 17:59:08 +03:00
commit e777b57991
52 changed files with 20725 additions and 0 deletions

View File

@@ -0,0 +1,166 @@
import React, { useState } from 'react'
import { useNavigate } from 'react-router-dom'
import {
Box,
Heading,
Button,
Table,
Flex,
Input,
HStack,
Text,
Badge,
} from '@chakra-ui/react'
import { useGetChainsQuery, useDeleteChainMutation } 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 { ChallengeChain } from '../../types/challenge'
import { toaster } from '../../components/ui/toaster'
export const ChainsListPage: React.FC = () => {
const navigate = useNavigate()
const { data: chains, isLoading, error, refetch } = useGetChainsQuery()
const [deleteChain, { isLoading: isDeleting }] = useDeleteChainMutation()
const [searchQuery, setSearchQuery] = useState('')
const [chainToDelete, setChainToDelete] = useState<ChallengeChain | null>(null)
const handleDeleteChain = async () => {
if (!chainToDelete) return
try {
await deleteChain(chainToDelete.id).unwrap()
toaster.create({
title: 'Успешно',
description: 'Цепочка удалена',
type: 'success',
})
setChainToDelete(null)
} catch (err) {
toaster.create({
title: 'Ошибка',
description: 'Не удалось удалить цепочку',
type: 'error',
})
}
}
if (isLoading) {
return <LoadingSpinner message="Загрузка цепочек..." />
}
if (error || !chains) {
return <ErrorAlert message="Не удалось загрузить список цепочек" onRetry={refetch} />
}
const filteredChains = chains.filter((chain) =>
chain.name.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.chainNew)}>
+ Создать цепочку
</Button>
</Flex>
{chains.length > 0 && (
<Box mb={4}>
<Input
placeholder="Поиск по названию..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
maxW="400px"
/>
</Box>
)}
{filteredChains.length === 0 && chains.length === 0 ? (
<EmptyState
title="Нет цепочек"
description="Создайте первую цепочку заданий"
actionLabel="Создать цепочку"
onAction={() => navigate(URLs.chainNew)}
/>
) : filteredChains.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 textAlign="right">Действия</Table.ColumnHeader>
</Table.Row>
</Table.Header>
<Table.Body>
{filteredChains.map((chain) => (
<Table.Row key={chain.id}>
<Table.Cell fontWeight="medium">{chain.name}</Table.Cell>
<Table.Cell>
<Badge colorPalette="teal" variant="subtle">
{chain.tasks.length} заданий
</Badge>
</Table.Cell>
<Table.Cell>
<Text fontSize="sm" color="gray.600">
{formatDate(chain.createdAt)}
</Text>
</Table.Cell>
<Table.Cell textAlign="right">
<HStack gap={2} justify="flex-end">
<Button
size="sm"
variant="ghost"
onClick={() => navigate(URLs.chainEdit(chain.id))}
>
Редактировать
</Button>
<Button
size="sm"
variant="ghost"
colorPalette="red"
onClick={() => setChainToDelete(chain)}
>
Удалить
</Button>
</HStack>
</Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table.Root>
</Box>
)}
<ConfirmDialog
isOpen={!!chainToDelete}
onClose={() => setChainToDelete(null)}
onConfirm={handleDeleteChain}
title="Удалить цепочку"
message={`Вы уверены, что хотите удалить цепочку "${chainToDelete?.name}"? Это действие нельзя отменить.`}
confirmLabel="Удалить"
isLoading={isDeleting}
/>
</Box>
)
}