admin-projects/src/pages/ReviewAdmin.tsx
WangDL cf1ea873e0
Some checks failed
Deploy Admin Frontend / build-and-deploy (push) Failing after 7s
refactor: replace any with proper types in admin page api calls
- Add CacheStats, NotificationTemplate, NotificationLog, ReviewCardItem to types/api.ts
- Use PaginatedResult<T> for ReviewAdmin pagination
- All queryFn now declare explicit Promise<T> return type

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-24 16:22:47 +08:00

89 lines
2.9 KiB
TypeScript

import { useState } from 'react'
import { Table, Tag, Space, Input, Select, Typography } from 'antd'
import { SearchOutlined } from '@ant-design/icons'
import { useQuery } from '@tanstack/react-query'
import { api } from '@/services/http-client'
import type { ReviewCardItem, PaginatedResult } from '@/types/api'
const { Title } = Typography
const statusColors: Record<string, string> = {
active: 'blue', suspended: 'orange', completed: 'green',
}
export default function ReviewAdmin() {
const [search, setSearch] = useState('')
const [statusFilter, setStatusFilter] = useState<string>()
const { data, isLoading } = useQuery({
queryKey: ['admin', 'reviews', search, statusFilter],
queryFn: (): Promise<PaginatedResult<ReviewCardItem>> => {
const params = new URLSearchParams()
if (search) params.set('search', search)
if (statusFilter) params.set('status', statusFilter)
return api.get(`/admin-api/reviews?${params.toString()}`)
},
refetchInterval: 30_000,
})
const columns = [
{ title: 'ID', dataIndex: 'id', width: 100, ellipsis: true },
{ title: '用户', dataIndex: 'userId', width: 100, ellipsis: true },
{ title: '正面', dataIndex: 'frontText', width: 200, ellipsis: true },
{ title: '难度', dataIndex: 'difficulty', width: 80 },
{
title: '状态', dataIndex: 'status', width: 80,
render: (s: string) => <Tag color={statusColors[s] || 'default'}>{s}</Tag>,
},
{
title: '调度', dataIndex: 'scheduleState', width: 80,
render: (s: string) => s || '-',
},
{ title: '间隔(天)', dataIndex: 'intervalDays', width: 80 },
{ title: '复习次数', dataIndex: 'repetitionCount', width: 80 },
{ title: '失误次数', dataIndex: 'lapseCount', width: 80 },
{
title: '下次复习', dataIndex: 'nextReviewAt', width: 120,
render: (d: string) => d ? new Date(d).toLocaleDateString() : '-',
},
]
return (
<div>
<Title level={4}></Title>
<Space style={{ marginBottom: 16 }}>
<Input
placeholder="搜索卡片内容"
prefix={<SearchOutlined />}
value={search}
onChange={e => setSearch(e.target.value)}
style={{ width: 240 }}
allowClear
/>
<Select
placeholder="状态筛选"
value={statusFilter}
onChange={setStatusFilter}
allowClear
style={{ width: 120 }}
options={[
{ label: '全部', value: undefined },
{ label: 'Active', value: 'active' },
{ label: 'Suspended', value: 'suspended' },
{ label: 'Completed', value: 'completed' },
]}
/>
</Space>
<Table
dataSource={data?.items || []}
columns={columns}
rowKey="id"
loading={isLoading}
pagination={{ total: data?.total || 0 }}
size="small"
scroll={{ x: 1100 }}
/>
</div>
)
}