import { useQuery } from "@tanstack/react-query"; import { Button } from "@workspace/ui/components/button"; import { Drawer, DrawerContent, DrawerFooter, DrawerHeader, DrawerTitle, } from "@workspace/ui/components/drawer"; import { Input } from "@workspace/ui/components/input"; import { Label } from "@workspace/ui/components/label"; import { ScrollArea } from "@workspace/ui/components/scroll-area"; import { ConfirmButton } from "@workspace/ui/composed/confirm-button"; import { Icon } from "@workspace/ui/composed/icon"; import { ProTable, type ProTableActions, } from "@workspace/ui/composed/pro-table/pro-table"; import { cn } from "@workspace/ui/lib/utils"; import { createTicketFollow, getTicket, getTicketList, updateTicketStatus, } from "@workspace/ui/services/admin/ticket"; import { getUserDetail, updateUserBasicInfo, } from "@workspace/ui/services/admin/user"; import { useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { toast } from "sonner"; import { formatDate } from "@/utils/common"; import { UserDetail } from "../user/user-detail"; export default function Page() { const { t } = useTranslation("ticket"); // i18n status declarations for extraction // t("status.0", "Status") // t("status.1", "Pending Follow-up") // t("status.2", "Pending Reply") // t("status.3", "Resolved") // t("status.4", "Closed") const [ticketId, setTicketId] = useState(null); const [message, setMessage] = useState(""); const [loadingId, setLoadingId] = useState(null); const { data: ticket, refetch: refetchTicket } = useQuery({ queryKey: ["getTicket", ticketId], queryFn: async () => { const { data } = await getTicket({ id: ticketId, }); return data.data as API.Ticket; }, enabled: !!ticketId, refetchInterval: 5000, }); const scrollRef = useRef(null); useEffect(() => { setTimeout(() => { if (scrollRef.current) { scrollRef.current.children[1]?.scrollTo({ top: scrollRef.current.children[1].scrollHeight, behavior: "smooth", }); } }, 66); }, [ticket?.follow?.length]); const ref = useRef(null); return ( <> action={ref} actions={{ render(row) { if (row.status === 1) { return [ { setLoadingId(row.id); try { await updateTicketStatus({ id: row.id, status: 3, }); toast.success("已取消打款并关闭工单"); ref.current?.refresh(); } catch (e) { console.error("Cancel payout failed", e); toast.error("取消打款失败"); } setLoadingId(null); }} title="确认取消打款?" trigger={ } />,

请确保您已完成线下打款操作。点击确认后,系统将自动扣除用户对应的佣金并关闭此工单,此操作不可撤销。

※ USDT 提现需手续费 1 USDT

※ 支付宝/微信提现手续费 5%

} key="colse" onConfirm={async () => { setLoadingId(row.id); try { const titleStr = row.title || ""; const parts = titleStr.split("-"); if (parts.length > 1) { const deductAmount = Number.parseFloat( parts?.[1] ?? "0" ); if (!Number.isNaN(deductAmount)) { const res = await getUserDetail({ id: row.user_id }); const user = res?.data?.data; if (user && typeof user.commission !== "undefined") { const deductAmountCent = deductAmount * 100; if (Number(user.commission) < deductAmountCent) { toast.error("用户佣金余额不足,无法完成打款扣费"); setLoadingId(null); return; } const newCommission = Number(user.commission) - deductAmountCent; await updateUserBasicInfo({ user_id: row.user_id, commission: newCommission, } as any); } } } } catch (e) { console.error("Update commission failed", e); } await updateTicketStatus({ id: row.id, status: 4, }); toast.success(t("closeSuccess", "Closed successfully")); ref.current?.refresh(); setLoadingId(null); }} title="确认已完成打款?" trigger={ } />, { setLoadingId(row.id); try { await updateTicketStatus({ id: row.id, status: 4, }); toast.success("已关闭工单"); ref.current?.refresh(); } catch (e) { console.error("Cancel payout failed", e); toast.error("关闭工单失败"); } setLoadingId(null); }} title="确认关闭订单?" trigger={ } />, ]; } return []; }, }} columns={[ { id: "withdrawal_type", header: "提现类型", cell: ({ row }) => { const title = row.original.title || ""; return title.split("-")[0] || title; }, }, { id: "withdrawal_amount", header: "提现金额", cell: ({ row }) => { const title = row.original.title || ""; const parts = title.split("-"); if (parts.length < 2) return ""; return ( ${parts[1]} ); }, }, { id: "withdrawal_method", header: "提现方式", cell: ({ row }) => { const desc = row.original.description || ""; return desc.split("-")[0] || ""; }, }, { id: "withdrawal_content", header: "提现内容", cell: ({ row }) => { const desc = row.original.description || ""; const parts = desc.split("-"); if (parts.length < 2) return ""; const content = parts[1]; if (content?.startsWith("data:image/")) { return ( withdrawal { const win = window.open(); win?.document.write(``); }} src={content} width={40} /> ); } return {content}; }, }, { accessorKey: "user_id", header: t("user", "User"), cell: ({ row }) => , }, { accessorKey: "status", header: t("status.0", "Status"), cell: ({ row }) => ( {t(`status.${row.original.status}`)} ), }, { accessorKey: "updated_at", header: t("updatedAt", "Updated At"), cell: ({ row }) => formatDate(row.getValue("updated_at")), }, ]} header={{ title: t("ticketList", "Ticket List"), }} params={[ { key: "status", placeholder: t("status.0", "Status"), options: [ { label: t("close", "Close"), value: "4", }, ], }, ]} request={async (pagination, filters) => { const { data } = await getTicketList({ ...pagination, ...filters, }); const list = (data.data?.list || []) as API.Ticket[]; // Client-side ordering to improve triage efficiency: // - Put "Pending Follow-up" (status=1) before "Pending Reply" (status=2) // - Within each group, sort by updated_at desc const statusPriority = (status: number) => { if (status === 1) return 0; if (status === 2) return 1; return 2; }; const toTime = (value: any) => { const t = new Date(value).getTime(); return Number.isFinite(t) ? t : 0; }; list.sort((a, b) => { const pa = statusPriority(a.status); const pb = statusPriority(b.status); if (pa !== pb) return pa - pb; return toTime(b.updated_at) - toTime(a.updated_at); }); return { list, total: data.data?.total || 0, }; }} /> { if (!open) setTicketId(null); }} open={!!ticketId} > {ticket?.title}
{/* 显示工单描述作为第一条用户消息 */} {ticket?.description && (

{formatDate(ticket.created_at)}

{ticket.description}

)} {/* 显示后续跟进消息 */} {ticket?.follow?.map((item) => (

{formatDate(item.created_at)}

{item.type === 1 && item.content} {item.type === 2 && ( // eslint-disable-next-line @next/next/no-img-element attachment )}

))}
{ticket?.status !== 4 && (
{ event.preventDefault(); if (message) { await createTicketFollow({ ticket_id: ticketId, from: "System", type: 1, content: message, }); refetchTicket(); setMessage(""); } }} > setMessage(e.target.value)} placeholder={t( "inputPlaceholder", "Please enter your question, we will reply as soon as possible." )} value={message} />
)}
); }