admin-projects/src/pages/ComplianceAdmin.tsx
wangdl 5dd35a1c9f
All checks were successful
Deploy Admin Frontend / build-and-deploy (push) Successful in 9s
feat(ADMIN-INFO-015): add userName render to userId columns across 8 pages
- Billing, ChatLogs, ContentSafety, NotificationAdmin, SecurityEvents
- ComplianceAdmin (deletions + exports)
- MemberManagement (members + deletion requests)
- ComplianceAdmin status labels also fixed

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-19 13:07:59 +08:00

137 lines
8.7 KiB
TypeScript

import { useState } from 'react'
import { Table, Button, Modal, Form, Input, Tag, Typography, Tabs, DatePicker } from 'antd'
import { PlusOutlined, EditOutlined, SafetyOutlined, CheckOutlined } from '@ant-design/icons'
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { api } from '@/services/http-client'
import { complianceStatusLabels } from '@/constants/labels'
import { message } from 'antd'
import dayjs from 'dayjs'
const { Title } = Typography
const { TextArea } = Input
const statusColors: Record<string, string> = { pending: 'blue', approved: 'green', rejected: 'red', PENDING: 'blue', APPROVED: 'green', REJECTED: 'red' }
export default function ComplianceAdmin() {
const [modalOpen, setModalOpen] = useState(false)
const [modalType, setModalType] = useState('')
const [editing, setEditing] = useState<any>(null)
const [form] = Form.useForm()
const qc = useQueryClient()
const { data: policies, isLoading: pLoading } = useQuery({
queryKey: ['compliance', 'policies'],
queryFn: () => api.get<any[]>('/admin-api/compliance/privacy-policies').then(d => d ?? []),
})
const { data: agreements, isLoading: aLoading } = useQuery({
queryKey: ['compliance', 'agreements'],
queryFn: () => api.get<any[]>('/admin-api/compliance/user-agreements').then(d => d ?? []),
})
const { data: filings, isLoading: fLoading } = useQuery({
queryKey: ['compliance', 'filings'],
queryFn: () => api.get<any[]>('/admin-api/compliance/filings').then(d => d ?? []),
})
const { data: deletions, isLoading: dLoading } = useQuery({
queryKey: ['compliance', 'deletions'],
queryFn: () => api.get<any[]>('/admin-api/compliance/deletion-requests').then(d => d ?? []),
})
const { data: exports_, isLoading: eLoading } = useQuery({
queryKey: ['compliance', 'exports'],
queryFn: () => api.get<any[]>('/admin-api/compliance/export-requests').then(d => d ?? []),
})
const { data: securityEvents, isLoading: sLoading } = useQuery({
queryKey: ['compliance', 'security-events'],
queryFn: () => api.get<any[]>('/admin-api/compliance/security-events').then(d => d ?? []),
})
const saveMutation = useMutation({
mutationFn: (values: any) => {
if (modalType === 'policy') return editing
? api.patch(`/admin-api/compliance/privacy-policies/${editing.id}`, values)
: api.post('/admin-api/compliance/privacy-policies', values)
if (modalType === 'agreement') return editing
? api.patch(`/admin-api/compliance/user-agreements/${editing.id}`, values)
: api.post('/admin-api/compliance/user-agreements', values)
return api.post('/admin-api/compliance/filings', values)
},
onSuccess: () => {
message.success('已保存')
qc.invalidateQueries({ queryKey: ['compliance'] })
setModalOpen(false); setEditing(null)
},
})
const approveDeletion = useMutation({
mutationFn: (id: string) => api.post(`/admin-api/compliance/deletion-requests/${id}/approve`),
onSuccess: () => { message.success('已批准'); qc.invalidateQueries({ queryKey: ['compliance', 'deletions'] }) },
})
const openCreate = (type: string) => { setModalType(type); setEditing(null); form.resetFields(); setModalOpen(true) }
const docColumns = [
{ title: '版本', dataIndex: 'version', width: 80 },
{ title: '标题', dataIndex: 'title', width: 200, ellipsis: true },
{ title: '生效时间', dataIndex: 'effectiveAt', width: 110, render: (d: string) => d ? new Date(d).toLocaleDateString() : '-' },
{ title: '状态', dataIndex: 'published', width: 60, render: (p: boolean) => <Tag color={p ? 'green' : 'orange'}>{p ? '已发布' : '草稿'}</Tag> },
{ title: '操作', width: 60, render: (_: any, r: any) => <Button size="small" icon={<EditOutlined />} onClick={() => { setModalType(modalType); setEditing(r); form.setFieldsValue({ ...r, effectiveAt: r.effectiveAt ? dayjs(r.effectiveAt) : undefined }); setModalOpen(true) }} /> },
]
const filingColumns = [
{ title: '类型', dataIndex: 'type', width: 100 },
{ title: '标题', dataIndex: 'title', width: 200, ellipsis: true },
{ title: '状态', dataIndex: 'status', width: 70, render: (s: string) => <Tag color={statusColors[s] || 'default'}>{complianceStatusLabels[s] || s}</Tag> },
{ title: '备注', dataIndex: 'notes', width: 200, ellipsis: true, render: (n: string) => n || '-' },
{ title: '创建时间', dataIndex: 'createdAt', width: 110, render: (d: string) => new Date(d).toLocaleDateString() },
]
const deletionColumns = [
{ title: '用户', dataIndex: 'userId', width: 100, ellipsis: true , render: (_: any, r: any) => r.userName || r.userId },
{ title: '状态', dataIndex: 'status', width: 70, render: (s: string) => <Tag color={statusColors[s] || 'default'}>{complianceStatusLabels[s] || s}</Tag> },
{ title: '申请时间', dataIndex: 'requestedAt', width: 110, render: (d: string) => new Date(d).toLocaleDateString() },
{ title: '冷却结束', dataIndex: 'coolingEndsAt', width: 110, render: (d: string) => new Date(d).toLocaleDateString() },
{ title: '操作', width: 60, render: (_: any, r: any) => r.status === 'pending' ? <Button size="small" type="primary" icon={<CheckOutlined />} onClick={() => approveDeletion.mutate(r.id)}></Button> : null },
]
const securityColumns = [
{ title: '类型', dataIndex: 'eventType', width: 100 },
{ title: '严重程度', dataIndex: 'severity', width: 80, render: (s: string) => <Tag color={s === 'high' ? 'red' : s === 'medium' ? 'orange' : 'blue'}>{s}</Tag> },
{ title: '已处理', dataIndex: 'handled', width: 60, render: (h: boolean) => h ? '是' : '否' },
{ title: '时间', dataIndex: 'createdAt', width: 110, render: (d: string) => d ? new Date(d).toLocaleString() : '-' },
]
const tabs = [
{ key: 'policies', label: '隐私政策', children: <div><Button type="primary" icon={<PlusOutlined />} onClick={() => openCreate('policy')} style={{ marginBottom: 16 }}></Button><Table dataSource={policies || []} columns={docColumns} rowKey="id" loading={pLoading} size="small" /></div> },
{ key: 'agreements', label: '用户协议', children: <div><Button type="primary" icon={<PlusOutlined />} onClick={() => openCreate('agreement')} style={{ marginBottom: 16 }}></Button><Table dataSource={agreements || []} columns={docColumns} rowKey="id" loading={aLoading} size="small" /></div> },
{ key: 'filings', label: '备案台账', children: <div><Button type="primary" icon={<PlusOutlined />} onClick={() => openCreate('filing')} style={{ marginBottom: 16 }}></Button><Table dataSource={filings || []} columns={filingColumns} rowKey="id" loading={fLoading} size="small" /></div> },
{ key: 'deletions', label: '数据删除', children: <Table dataSource={deletions || []} columns={deletionColumns} rowKey="id" loading={dLoading} size="small" /> },
{ key: 'exports', label: '数据导出', children: <Table dataSource={exports_ || []} columns={[
{ title: '用户', dataIndex: 'userId', width: 100, ellipsis: true , render: (_: any, r: any) => r.userName || r.userId },
{ title: '状态', dataIndex: 'status', width: 70, render: (s: string) => <Tag color={statusColors[s] || 'default'}>{complianceStatusLabels[s] || s}</Tag> },
{ title: '创建时间', dataIndex: 'createdAt', width: 110, render: (d: string) => new Date(d).toLocaleDateString() },
]} rowKey="id" loading={eLoading} size="small" /> },
{ key: 'security', label: '安全事件', children: <Table dataSource={securityEvents || []} columns={securityColumns} rowKey="id" loading={sLoading} size="small" /> },
]
return (
<div>
<Title level={4}><SafetyOutlined /> </Title>
<Tabs items={tabs} />
<Modal title={editing ? '编辑' : '新建'} open={modalOpen} onCancel={() => { setModalOpen(false); setEditing(null) }} onOk={() => form.submit()} confirmLoading={saveMutation.isPending}>
<Form form={form} layout="vertical" onFinish={saveMutation.mutate}>
<Form.Item name="version" label="版本" rules={[{ required: modalType !== 'filing' }]}><Input placeholder="1.0" /></Form.Item>
<Form.Item name="title" label="标题" rules={[{ required: true }]}><Input placeholder="标题" /></Form.Item>
{modalType === 'filing' && <Form.Item name="type" label="类型" rules={[{ required: true }]}><Input placeholder="ICP备案/公安备案/..." /></Form.Item>}
{modalType !== 'filing' && <Form.Item name="content" label="内容"><TextArea rows={6} placeholder="文档内容" /></Form.Item>}
{modalType !== 'filing' && <Form.Item name="effectiveAt" label="生效时间"><DatePicker style={{ width: '100%' }} /></Form.Item>}
<Form.Item name="notes" label="备注"><TextArea rows={2} /></Form.Item>
</Form>
</Modal>
</div>
)
}