You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
145 lines
4.0 KiB
145 lines
4.0 KiB
import { CheckCircleTwoTone, CloseCircleTwoTone } from '@ant-design/icons' |
|
import { useQuery } from '@tanstack/react-query' |
|
import { Button, DatePicker, Select, Space, Table, Tag, Typography } from 'antd' |
|
import type { ColumnsType } from 'antd/es/table' |
|
import dayjs, { Dayjs } from 'dayjs' |
|
import { useState } from 'react' |
|
|
|
import { AttemptListItem, attemptsApi } from '../../api/attempts' |
|
import { testsApi } from '../../api/tests' |
|
|
|
const { Title } = Typography |
|
const { RangePicker } = DatePicker |
|
|
|
export default function Tracker() { |
|
const [testId, setTestId] = useState<number | undefined>() |
|
const [dateRange, setDateRange] = useState<[Dayjs, Dayjs] | null>(null) |
|
const [page, setPage] = useState(1) |
|
const pageSize = 20 |
|
|
|
const params = { |
|
test_id: testId, |
|
date_from: dateRange?.[0].startOf('day').toISOString(), |
|
date_to: dateRange?.[1].endOf('day').toISOString(), |
|
page, |
|
page_size: pageSize, |
|
} |
|
|
|
const { data, isLoading } = useQuery({ |
|
queryKey: ['attempts', params], |
|
queryFn: () => attemptsApi.list(params).then((r) => r.data), |
|
}) |
|
|
|
const { data: testsData } = useQuery({ |
|
queryKey: ['tests'], |
|
queryFn: () => testsApi.list().then((r) => r.data), |
|
}) |
|
|
|
const handleReset = () => { |
|
setTestId(undefined) |
|
setDateRange(null) |
|
setPage(1) |
|
} |
|
|
|
const columns: ColumnsType<AttemptListItem> = [ |
|
{ |
|
title: 'Сотрудник', |
|
dataIndex: 'user_name', |
|
width: 120, |
|
}, |
|
{ |
|
title: 'Тест', |
|
key: 'test', |
|
render: (_, r) => ( |
|
<span> |
|
{r.test_title}{' '} |
|
<Tag color="default" style={{ fontSize: 11 }}> |
|
v{r.test_version} |
|
</Tag> |
|
</span> |
|
), |
|
}, |
|
{ |
|
title: 'Начало', |
|
dataIndex: 'started_at', |
|
width: 160, |
|
render: (v: string) => dayjs(v).format('DD.MM.YYYY HH:mm'), |
|
}, |
|
{ |
|
title: 'Завершение', |
|
dataIndex: 'finished_at', |
|
width: 160, |
|
render: (v: string | null) => (v ? dayjs(v).format('DD.MM.YYYY HH:mm') : '—'), |
|
}, |
|
{ |
|
title: 'Результат', |
|
key: 'result', |
|
width: 140, |
|
render: (_, r) => |
|
r.correct_count != null && r.total_count != null |
|
? `${r.correct_count} / ${r.total_count} (${r.score?.toFixed(1)}%)` |
|
: '—', |
|
}, |
|
{ |
|
title: 'Зачёт', |
|
dataIndex: 'passed', |
|
width: 90, |
|
render: (passed: boolean | null) => { |
|
if (passed == null) return '—' |
|
return passed ? ( |
|
<Space size={4}> |
|
<CheckCircleTwoTone twoToneColor="#52c41a" /> |
|
<Tag color="success">Сдал</Tag> |
|
</Space> |
|
) : ( |
|
<Space size={4}> |
|
<CloseCircleTwoTone twoToneColor="#ff4d4f" /> |
|
<Tag color="error">Не сдал</Tag> |
|
</Space> |
|
) |
|
}, |
|
}, |
|
] |
|
|
|
return ( |
|
<div style={{ maxWidth: 1000, margin: '32px auto', padding: '0 24px' }}> |
|
<Title level={2}>Трекер результатов</Title> |
|
|
|
{/* Фильтры */} |
|
<Space wrap style={{ marginBottom: 16 }}> |
|
<Select |
|
allowClear |
|
placeholder="Все тесты" |
|
style={{ width: 260 }} |
|
value={testId} |
|
onChange={(v) => { setTestId(v); setPage(1) }} |
|
options={(testsData ?? []).map((t) => ({ |
|
value: t.id, |
|
label: `${t.title} (v${t.version})`, |
|
}))} |
|
/> |
|
<RangePicker |
|
value={dateRange} |
|
onChange={(v) => { setDateRange(v as [Dayjs, Dayjs] | null); setPage(1) }} |
|
format="DD.MM.YYYY" |
|
placeholder={['Дата от', 'Дата до']} |
|
/> |
|
<Button onClick={handleReset}>Сбросить</Button> |
|
</Space> |
|
|
|
<Table |
|
rowKey="id" |
|
columns={columns} |
|
dataSource={data?.items ?? []} |
|
loading={isLoading} |
|
pagination={{ |
|
current: page, |
|
pageSize, |
|
total: data?.total ?? 0, |
|
onChange: setPage, |
|
showTotal: (total) => `Всего: ${total}`, |
|
}} |
|
/> |
|
</div> |
|
) |
|
}
|
|
|