feat: split test detail into employee view and author view
- /tests/:id — employee view, answers shown without correct/incorrect markers - /tests/:id/edit — author view, correct answers highlighted + edit button (disabled until Sprint 4) - TestList: add 'Изменить' button linking to author view Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@ import { BrowserRouter, Route, Routes } from 'react-router-dom'
|
|||||||
import AttemptResult from './pages/AttemptResult'
|
import AttemptResult from './pages/AttemptResult'
|
||||||
import TestCreate from './pages/TestCreate'
|
import TestCreate from './pages/TestCreate'
|
||||||
import TestDetail from './pages/TestDetail'
|
import TestDetail from './pages/TestDetail'
|
||||||
|
import TestEdit from './pages/TestEdit'
|
||||||
import TestList from './pages/TestList'
|
import TestList from './pages/TestList'
|
||||||
import TestTake from './pages/TestTake'
|
import TestTake from './pages/TestTake'
|
||||||
|
|
||||||
@@ -20,6 +21,7 @@ export default function App() {
|
|||||||
<Route path="/" element={<TestList />} />
|
<Route path="/" element={<TestList />} />
|
||||||
<Route path="/tests/create" element={<TestCreate />} />
|
<Route path="/tests/create" element={<TestCreate />} />
|
||||||
<Route path="/tests/:id" element={<TestDetail />} />
|
<Route path="/tests/:id" element={<TestDetail />} />
|
||||||
|
<Route path="/tests/:id/edit" element={<TestEdit />} />
|
||||||
<Route path="/tests/:testId/take" element={<TestTake />} />
|
<Route path="/tests/:testId/take" element={<TestTake />} />
|
||||||
<Route path="/attempts/:attemptId/result" element={<AttemptResult />} />
|
<Route path="/attempts/:attemptId/result" element={<AttemptResult />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ArrowLeftOutlined, CheckCircleTwoTone, CloseCircleTwoTone, PlayCircleOutlined } from '@ant-design/icons'
|
import { ArrowLeftOutlined, PlayCircleOutlined } from '@ant-design/icons'
|
||||||
import { useQuery } from '@tanstack/react-query'
|
import { useQuery } from '@tanstack/react-query'
|
||||||
import { Button, Card, Descriptions, List, Space, Spin, Tag, Typography } from 'antd'
|
import { Button, Card, Descriptions, List, Space, Spin, Tag, Typography } from 'antd'
|
||||||
import { useNavigate, useParams } from 'react-router-dom'
|
import { useNavigate, useParams } from 'react-router-dom'
|
||||||
@@ -78,14 +78,7 @@ export default function TestDetail() {
|
|||||||
dataSource={question.answers}
|
dataSource={question.answers}
|
||||||
renderItem={(answer: Answer) => (
|
renderItem={(answer: Answer) => (
|
||||||
<List.Item style={{ padding: '4px 0' }}>
|
<List.Item style={{ padding: '4px 0' }}>
|
||||||
<Space>
|
<Text>{answer.text}</Text>
|
||||||
{answer.is_correct ? (
|
|
||||||
<CheckCircleTwoTone twoToneColor="#52c41a" />
|
|
||||||
) : (
|
|
||||||
<CloseCircleTwoTone twoToneColor="#d9d9d9" />
|
|
||||||
)}
|
|
||||||
<Text>{answer.text}</Text>
|
|
||||||
</Space>
|
|
||||||
</List.Item>
|
</List.Item>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -0,0 +1,118 @@
|
|||||||
|
import {
|
||||||
|
ArrowLeftOutlined,
|
||||||
|
CheckCircleTwoTone,
|
||||||
|
CloseCircleTwoTone,
|
||||||
|
EditOutlined,
|
||||||
|
PlayCircleOutlined,
|
||||||
|
} from '@ant-design/icons'
|
||||||
|
import { useQuery } from '@tanstack/react-query'
|
||||||
|
import { Alert, Button, Card, Descriptions, List, Space, Spin, Tag, Typography } from 'antd'
|
||||||
|
import { useNavigate, useParams } from 'react-router-dom'
|
||||||
|
|
||||||
|
import { Answer, testsApi } from '../../api/tests'
|
||||||
|
|
||||||
|
const { Title, Text } = Typography
|
||||||
|
|
||||||
|
export default function TestEdit() {
|
||||||
|
const { id } = useParams<{ id: string }>()
|
||||||
|
const navigate = useNavigate()
|
||||||
|
|
||||||
|
const { data: test, isLoading } = useQuery({
|
||||||
|
queryKey: ['tests', id],
|
||||||
|
queryFn: () => testsApi.get(Number(id)).then((r) => r.data),
|
||||||
|
})
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return <Spin size="large" style={{ display: 'block', margin: '48px auto' }} />
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!test) return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ maxWidth: 820, margin: '0 auto', padding: 24 }}>
|
||||||
|
<Space style={{ width: '100%', justifyContent: 'space-between', marginBottom: 16 }}>
|
||||||
|
<Button icon={<ArrowLeftOutlined />} onClick={() => navigate('/')}>
|
||||||
|
К списку тестов
|
||||||
|
</Button>
|
||||||
|
<Space>
|
||||||
|
<Button
|
||||||
|
icon={<EditOutlined />}
|
||||||
|
disabled
|
||||||
|
title="Редактирование будет доступно в следующем спринте"
|
||||||
|
>
|
||||||
|
Редактировать
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
icon={<PlayCircleOutlined />}
|
||||||
|
onClick={() => navigate(`/tests/${test.id}/take`)}
|
||||||
|
>
|
||||||
|
Пройти тест
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
</Space>
|
||||||
|
|
||||||
|
<Alert
|
||||||
|
type="warning"
|
||||||
|
showIcon
|
||||||
|
message="Вид автора — правильные ответы отмечены"
|
||||||
|
style={{ marginBottom: 16 }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Title level={2}>{test.title}</Title>
|
||||||
|
|
||||||
|
{test.description && (
|
||||||
|
<Text type="secondary" style={{ display: 'block', marginBottom: 16 }}>
|
||||||
|
{test.description}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Card style={{ marginBottom: 24 }}>
|
||||||
|
<Descriptions column={3}>
|
||||||
|
<Descriptions.Item label="Вопросов">{test.questions.length}</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="Порог зачёта">{test.passing_score}%</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="Таймер">
|
||||||
|
{test.time_limit ? `${test.time_limit} мин` : 'Без ограничений'}
|
||||||
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="Возврат к вопросу">
|
||||||
|
{test.allow_navigation_back ? (
|
||||||
|
<Tag color="green">Разрешён</Tag>
|
||||||
|
) : (
|
||||||
|
<Tag color="red">Запрещён</Tag>
|
||||||
|
)}
|
||||||
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="Версия">{test.version}</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="Создан">
|
||||||
|
{new Date(test.created_at).toLocaleDateString('ru-RU')}
|
||||||
|
</Descriptions.Item>
|
||||||
|
</Descriptions>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Title level={3}>Вопросы ({test.questions.length})</Title>
|
||||||
|
|
||||||
|
{test.questions.map((question, index) => (
|
||||||
|
<Card key={question.id} style={{ marginBottom: 12 }}>
|
||||||
|
<Text strong>
|
||||||
|
{index + 1}. {question.text}
|
||||||
|
</Text>
|
||||||
|
<List
|
||||||
|
style={{ marginTop: 10 }}
|
||||||
|
dataSource={question.answers}
|
||||||
|
renderItem={(answer: Answer) => (
|
||||||
|
<List.Item style={{ padding: '4px 0' }}>
|
||||||
|
<Space>
|
||||||
|
{answer.is_correct ? (
|
||||||
|
<CheckCircleTwoTone twoToneColor="#52c41a" />
|
||||||
|
) : (
|
||||||
|
<CloseCircleTwoTone twoToneColor="#d9d9d9" />
|
||||||
|
)}
|
||||||
|
<Text>{answer.text}</Text>
|
||||||
|
</Space>
|
||||||
|
</List.Item>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { PlayCircleOutlined, PlusOutlined } from '@ant-design/icons'
|
import { EditOutlined, PlayCircleOutlined, PlusOutlined } from '@ant-design/icons'
|
||||||
import { useQuery } from '@tanstack/react-query'
|
import { useQuery } from '@tanstack/react-query'
|
||||||
import { Button, Space, Spin, Table, Tag, Typography } from 'antd'
|
import { Button, Space, Spin, Table, Tag, Typography } from 'antd'
|
||||||
import type { ColumnsType } from 'antd/es/table'
|
import type { ColumnsType } from 'antd/es/table'
|
||||||
@@ -64,6 +64,13 @@ export default function TestList() {
|
|||||||
<Button size="small" onClick={() => navigate(`/tests/${record.id}`)}>
|
<Button size="small" onClick={() => navigate(`/tests/${record.id}`)}>
|
||||||
Открыть
|
Открыть
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
icon={<EditOutlined />}
|
||||||
|
onClick={() => navigate(`/tests/${record.id}/edit`)}
|
||||||
|
>
|
||||||
|
Изменить
|
||||||
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
size="small"
|
size="small"
|
||||||
type="primary"
|
type="primary"
|
||||||
|
|||||||
Reference in New Issue
Block a user