"use client"; import { useEffect, useState } from "react"; import { RefreshCcw, CheckCircle2, ExternalLink } from "lucide-react"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { opsApi } from "@/lib/ops-api"; import type { PaymentRuntimePayload, PaymentIncident, PaymentRecord } from "@/types/ops"; import { CHART_TOOLTIP_STYLE } from "@/lib/chart-utils"; import { PieChart, Pie, Cell, ResponsiveContainer, Tooltip, } from "recharts"; export function PaymentsPageClient() { const [loading, setLoading] = useState(true); const [runtime, setRuntime] = useState(null); const [incidents, setIncidents] = useState([]); const [payments, setPayments] = useState([]); const [resolving, setResolving] = useState>(new Set()); const load = async () => { setLoading(true); try { const [rt, inc, pay] = await Promise.all([ opsApi.paymentRuntime() as Promise, opsApi.incidents(50), opsApi.listPayments(50), ]); setRuntime(rt); setIncidents((inc as unknown as { incidents?: PaymentIncident[] }).incidents ?? []); setPayments((pay as unknown as { payments?: PaymentRecord[] }).payments ?? []); } catch { /* */ } setLoading(false); }; const handleResolve = async (id: number) => { setResolving((prev) => new Set(prev).add(id)); try { await opsApi.resolveIncident(id); setIncidents((prev) => prev.filter((i) => i.id !== id)); } catch { /* */ } setResolving((prev) => { const next = new Set(prev); next.delete(id); return next; }); }; useEffect(() => { void load(); }, []); if (loading) return
加载中...
; const reasonCounts: Record = {}; incidents.forEach((inc) => { const r = inc.reason || "unknown"; reasonCounts[r] = (reasonCounts[r] || 0) + 1; }); const incidentPieData = Object.entries(reasonCounts).map(([name, value]) => ({ name, value, })); const COLORS = ["#ef4444", "#f59e0b", "#3b82f6", "#10b981", "#a855f7", "#6366f1", "#ec4899"]; function paymentExplorerUrl(payment: PaymentRecord): string { const txHash = String(payment.tx_hash || "").trim(); if (!txHash) return ""; const chain = String(payment.chain || "").trim().toLowerCase(); const base = chain.includes("eth") ? "https://etherscan.io" : "https://polygonscan.com"; return `${base}/tx/${txHash}`; } return (

支付管理

支付运行时
{runtime ? Object.entries({ chain_id: runtime.chain_id, last_scanned_block: runtime.last_scanned_block, audit_events_count: runtime.audit_events_count, }).map(([k, v]) => (
{k}
{String(v ?? "—")}
)) : 无数据}
{runtime?.receiver_contract ? (
receiver_contract
{runtime.receiver_contract}
) : null}
异常原因分布 {incidentPieData.length === 0 ? ( 暂无异常数据 ) : (
{incidentPieData.map((d, i) => ( ))}
{incidentPieData.map((d, i) => (
{d.name} {d.value}
))}
)}
支付异常 ({incidents.length}) {incidents.length === 0 ? ( 暂无异常 ) : (
{incidents.map((inc) => ( ))}
ID 原因 时间 操作
{inc.id} {inc.reason ?? "—"} {inc.created_at?.slice(0, 19) ?? "—"}
)}
成功支付记录 ({payments.length}) {payments.length === 0 ? ( 暂无记录 ) : (
{payments.map((p) => ( ))}
ID 用户 金额 Tx Hash 时间
{p.id} {p.user_id?.slice(0, 10) ?? "—"}... {p.amount} {p.currency} {p.chain ?? "—"} {p.tx_hash ? ( {p.tx_hash.slice(0, 8)}...{p.tx_hash.slice(-6)} ) : "—"} {p.created_at?.slice(0, 19) ?? "—"}
)}
); }