import { useEffect, useState } from 'react'; import { Button, Descriptions, Drawer, Form, Input, Modal, Select, Space, Table, Tag, Typography, message, } from 'antd'; import type { ColumnsType } from 'antd/es/table'; import { SUPPORT_TICKET_STATUS_LABELS, SUPPORT_TICKET_TYPE_LABELS, type SupportTicketDto, type SupportTicketStatusDto, type SupportTicketTypeDto, } from '@dukang/shared-types'; import { request, type HqProfile } from '../lib/api'; import { fmtTime } from '../lib/constants'; import { useAdminList } from '../lib/useAdminList'; const STATUS_COLOR: Record = { PENDING_REVIEW: 'orange', REJECTED: 'red', DEVELOPING: 'blue', TESTING: 'purple', PASSED: 'green', }; const TYPE_OPTIONS = (Object.keys(SUPPORT_TICKET_TYPE_LABELS) as SupportTicketTypeDto[]).map( (value) => ({ value, label: SUPPORT_TICKET_TYPE_LABELS[value] }), ); const STATUS_OPTIONS = (Object.keys(SUPPORT_TICKET_STATUS_LABELS) as SupportTicketStatusDto[]).map( (value) => ({ value, label: SUPPORT_TICKET_STATUS_LABELS[value] }), ); export default function SupportTicketsPage() { const [profile, setProfile] = useState(null); const [filters, setFilters] = useState>({}); const { data, loading, page, pageSize, setPage, setPageSize, reload } = useAdminList('/admin/support-tickets', () => { const qs = new URLSearchParams(); if (filters.ticketType) qs.set('ticketType', filters.ticketType); if (filters.status) qs.set('status', filters.status); return qs; }, [filters]); const [detail, setDetail] = useState(null); const [drawerOpen, setDrawerOpen] = useState(false); const [createOpen, setCreateOpen] = useState(false); const [creating, setCreating] = useState(false); const [acting, setActing] = useState(false); const [rejectOpen, setRejectOpen] = useState(false); const [createForm] = Form.useForm<{ ticketType: SupportTicketTypeDto; title: string; content?: string; remark?: string; }>(); const [rejectForm] = Form.useForm<{ rejectReason: string }>(); const isSuperAdmin = profile?.adminRole === 'SUPER_ADMIN'; useEffect(() => { request('/admin/auth/me').then(setProfile).catch(() => {}); }, []); async function openDetail(id: string) { setDetail(await request(`/admin/support-tickets/${id}`)); setDrawerOpen(true); } async function submitCreate() { const values = await createForm.validateFields(); setCreating(true); try { await request('/admin/support-tickets', { method: 'POST', body: JSON.stringify({ ticketType: values.ticketType, title: values.title.trim(), content: values.content?.trim() || undefined, remark: values.remark?.trim() || undefined, }), }); message.success('技术支持工单已创建,等待最高管理员评审'); setCreateOpen(false); createForm.resetFields(); reload(); } catch (e) { message.error(e instanceof Error ? e.message : '创建失败'); } finally { setCreating(false); } } async function approve() { if (!detail) return; setActing(true); try { await request(`/admin/support-tickets/${detail.id}/approve`, { method: 'POST', body: JSON.stringify({}), }); message.success('评审通过,已进入开发'); setDrawerOpen(false); reload(); } catch (e) { message.error(e instanceof Error ? e.message : '操作失败'); } finally { setActing(false); } } async function submitReject() { if (!detail) return; const values = await rejectForm.validateFields(); setActing(true); try { await request(`/admin/support-tickets/${detail.id}/reject`, { method: 'POST', body: JSON.stringify({ rejectReason: values.rejectReason.trim() }), }); message.success('已驳回'); setRejectOpen(false); rejectForm.resetFields(); setDrawerOpen(false); reload(); } catch (e) { message.error(e instanceof Error ? e.message : '操作失败'); } finally { setActing(false); } } async function startTesting() { if (!detail) return; setActing(true); try { await request(`/admin/support-tickets/${detail.id}/start-testing`, { method: 'POST', body: JSON.stringify({}), }); message.success('已转入测试'); setDrawerOpen(false); reload(); } catch (e) { message.error(e instanceof Error ? e.message : '操作失败'); } finally { setActing(false); } } async function pass() { if (!detail) return; setActing(true); try { await request(`/admin/support-tickets/${detail.id}/pass`, { method: 'POST', body: JSON.stringify({}), }); message.success('测试通过'); setDrawerOpen(false); reload(); } catch (e) { message.error(e instanceof Error ? e.message : '操作失败'); } finally { setActing(false); } } const columns: ColumnsType = [ { title: '工单号', dataIndex: 'ticketNo', width: 180 }, { title: '类型', dataIndex: 'ticketType', width: 90, render: (t: SupportTicketTypeDto) => SUPPORT_TICKET_TYPE_LABELS[t] ?? t, }, { title: '状态', dataIndex: 'status', width: 100, render: (s: SupportTicketStatusDto) => ( {SUPPORT_TICKET_STATUS_LABELS[s] ?? s} ), }, { title: '标题', dataIndex: 'title', ellipsis: true }, { title: '创建人', dataIndex: 'creatorName', width: 100 }, { title: '时间', dataIndex: 'createdAt', width: 160, render: fmtTime }, { title: '操作', width: 80, render: (_, row) => ( ), }, ]; const drawerExtra = (() => { if (!detail) return null; if (detail.status === 'PENDING_REVIEW' && isSuperAdmin) { return ( ); } if (detail.status === 'DEVELOPING') { return ( ); } if (detail.status === 'TESTING') { return ( ); } return null; })(); return (
技术支持
{ setFilters(v); setPage(1); }} >
{ setPage(p); setPageSize(ps); }, }} /> setDrawerOpen(false)} extra={drawerExtra} > {detail && ( {detail.ticketNo} {SUPPORT_TICKET_TYPE_LABELS[detail.ticketType] ?? detail.ticketType} {SUPPORT_TICKET_STATUS_LABELS[detail.status] ?? detail.status} {detail.title}
{detail.content || '—'}
{detail.creatorName} {fmtTime(detail.createdAt)} {detail.reviewerName || '—'} {detail.reviewedAt ? fmtTime(detail.reviewedAt) : '—'} {detail.rejectReason || '—'} {detail.remark || '—'}
)}
setCreateOpen(false)} onOk={() => void submitCreate()} confirmLoading={creating} destroyOnClose okText="提交" >
setRejectOpen(false)} onOk={() => void submitReject()} confirmLoading={acting} destroyOnClose okText="确认驳回" okButtonProps={{ danger: true }} >
); }