feat: add version activation — choose which version is active

Backend:
- POST /api/tests/{id}/activate — deactivates all versions in chain, activates selected
- GET /api/tests — simplified to is_active=True only (no parent_id subquery)
- GET/PUT /api/tests/{id} — removed is_active filter, any version accessible by id
- PUT /api/tests/{id} — new version auto-activates, parent deactivates

Frontend:
- Version history table: status column (Активная/Неактивная), 'Сделать активной' button

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Aleksey Razorvin
2026-03-21 13:46:42 +05:00
parent 8df6077798
commit 1103201ee3
3 changed files with 92 additions and 30 deletions
+1
View File
@@ -70,4 +70,5 @@ export const testsApi = {
update: (id: number, data: CreateTestDto) =>
client.put<UpdateTestResponse>(`/tests/${id}`, data),
versions: (id: number) => client.get<TestListItem[]>(`/tests/${id}/versions`),
activate: (id: number) => client.post<Test>(`/tests/${id}/activate`),
}
+43 -14
View File
@@ -33,6 +33,17 @@ export default function TestEdit() {
enabled: !editMode,
})
const { mutate: activateVersion, isPending: isActivating } = useMutation({
mutationFn: (versionId: number) => testsApi.activate(versionId).then((r) => r.data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['tests'] })
queryClient.invalidateQueries({ queryKey: ['tests', id, 'versions'] })
queryClient.invalidateQueries({ queryKey: ['tests', id] })
message.success('Активная версия изменена')
},
onError: () => message.error('Не удалось изменить активную версию'),
})
const { mutate: updateTest, isPending } = useMutation({
mutationFn: (data: CreateTestDto) => testsApi.update(Number(id), data).then((r) => r.data),
onSuccess: (result) => {
@@ -173,13 +184,19 @@ export default function TestEdit() {
{
title: 'Версия',
dataIndex: 'version',
width: 90,
render: (v: number, record: TestListItem) => (
<Space>
<Tag color={record.id === test.id ? 'blue' : 'default'}>v{v}</Tag>
{record.id === test.id && <Text type="secondary">(текущая)</Text>}
</Space>
),
width: 70,
render: (v: number) => <Tag color="default">v{v}</Tag>,
},
{
title: 'Статус',
dataIndex: 'is_active',
width: 130,
render: (active: boolean) =>
active ? (
<Tag color="green">Активная</Tag>
) : (
<Tag color="default">Неактивная</Tag>
),
},
{
title: 'Дата',
@@ -203,13 +220,25 @@ export default function TestEdit() {
{
title: '',
key: 'action',
width: 100,
render: (_: unknown, record: TestListItem) =>
record.id !== test.id ? (
<Button size="small" onClick={() => navigate(`/tests/${record.id}/edit`)}>
Открыть
</Button>
) : null,
render: (_: unknown, record: TestListItem) => (
<Space>
{record.id !== test.id && (
<Button size="small" onClick={() => navigate(`/tests/${record.id}/edit`)}>
Открыть
</Button>
)}
{!record.is_active && (
<Button
size="small"
type="primary"
loading={isActivating}
onClick={() => activateVersion(record.id)}
>
Сделать активной
</Button>
)}
</Space>
),
},
] as ColumnsType<TestListItem>
}