Browse Source

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>
master
Aleksey Razorvin 1 week ago
parent
commit
4f788a9cf6
  1. 2
      frontend/src/App.tsx
  2. 11
      frontend/src/pages/TestDetail/index.tsx
  3. 118
      frontend/src/pages/TestEdit/index.tsx
  4. 9
      frontend/src/pages/TestList/index.tsx

2
frontend/src/App.tsx

@ -6,6 +6,7 @@ import { BrowserRouter, Route, Routes } from 'react-router-dom'
import AttemptResult from './pages/AttemptResult'
import TestCreate from './pages/TestCreate'
import TestDetail from './pages/TestDetail'
import TestEdit from './pages/TestEdit'
import TestList from './pages/TestList'
import TestTake from './pages/TestTake'
@ -20,6 +21,7 @@ export default function App() {
<Route path="/" element={<TestList />} />
<Route path="/tests/create" element={<TestCreate />} />
<Route path="/tests/:id" element={<TestDetail />} />
<Route path="/tests/:id/edit" element={<TestEdit />} />
<Route path="/tests/:testId/take" element={<TestTake />} />
<Route path="/attempts/:attemptId/result" element={<AttemptResult />} />
</Routes>

11
frontend/src/pages/TestDetail/index.tsx

@ -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 { Button, Card, Descriptions, List, Space, Spin, Tag, Typography } from 'antd'
import { useNavigate, useParams } from 'react-router-dom'
@ -78,14 +78,7 @@ export default function TestDetail() {
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>
<Text>{answer.text}</Text>
</List.Item>
)}
/>

118
frontend/src/pages/TestEdit/index.tsx

@ -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>
)
}

9
frontend/src/pages/TestList/index.tsx

@ -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 { Button, Space, Spin, Table, Tag, Typography } from 'antd'
import type { ColumnsType } from 'antd/es/table'
@ -64,6 +64,13 @@ export default function TestList() {
<Button size="small" onClick={() => navigate(`/tests/${record.id}`)}>
Открыть
</Button>
<Button
size="small"
icon={<EditOutlined />}
onClick={() => navigate(`/tests/${record.id}/edit`)}
>
Изменить
</Button>
<Button
size="small"
type="primary"

Loading…
Cancel
Save