import * as React from "react" import { Button } from "@/components/ui/button" import { Skeleton } from "@/components/ui/skeleton" import { Card, CardContent, CardHeader, CardTitle, CardFooter } from "@/components/ui/card" import { Eye, EyeOff } from "lucide-react" import { ScrollArea } from "@/components/ui/scroll-area" import { DisputeService, TransactionService, DashboardService } from "@/lib/services" import type { DisputeWithOrder, Order, DailyStatsItem, TopCustomer } from "@/lib/services" import { RefundReviewDialog, CancelDisputeDialog } from "@/components/common/general/table-data" import { CountingNumber } from '@/components/animate-ui/primitives/texts/counting-number' import { useDisputeData } from "@/hooks/use-dispute" import { DisputeDialog } from "@/components/common/home/dispute-dialog" import { useRouter } from "next/navigation" import { Badge } from "@/components/ui/badge" import { cn } from "@/lib/utils" import { Progress } from "@/components/ui/progress" import { Spinner } from "@/components/ui/spinner" function AnimatedProgressBar({ value, className }: { value: number, className?: string }) { const [progress, setProgress] = React.useState(0) React.useEffect(() => { const timer = setTimeout(() => setProgress(value), 100) return () => clearTimeout(timer) }, [value]) return ( ) } interface OverviewCardProps { title: string titleExtra?: React.ReactNode headerExtra?: React.ReactNode action?: React.ReactNode loading?: boolean onRefresh?: () => void onViewAll?: () => void children: React.ReactNode className?: string } function OverviewCard({ title, titleExtra, headerExtra, action, loading, onRefresh, onViewAll, children, className }: OverviewCardProps) { return (
{title} {titleExtra &&
{titleExtra}
}
{action} {onRefresh && ( )}
{headerExtra &&
{headerExtra}
}
{children}
更新时间:{new Date().toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })} {onViewAll && ( )}
) } /** * 创建争议对象 * @param dispute 争议对象 * @param type 争议类型 * @returns 争议对象 */ const createDisputeOrder = (dispute: DisputeWithOrder, type: 'receive' | 'payment'): Order => ({ id: dispute.order_id, dispute_id: dispute.id, type, status: 'disputing' as const, order_no: '', order_name: dispute.order_name, merchant_order_no: '', payer_user_id: '0', payee_user_id: '0', payer_username: '', payee_username: dispute.payee_username, amount: dispute.amount, remark: '', client_id: '', trade_time: '', expires_at: '', created_at: '', updated_at: '', payment_type: '', payee_transfer_status: 'completed' as const, payee_transfer_at: '', }) /** * 争议列表骨架屏 * 用于显示争议列表的加载状态 * * @returns 争议列表骨架屏 */ const DisputeListSkeleton = () => (
{Array.from({ length: 7 }).map((_, index) => (
))}
) /** * 争议列表数据骨架屏 */ const PaymentListSkeleton = () => (
{Array.from({ length: 7 }).map((_, index) => (
))}
) /** * 获取活动状态显示文本 */ const getStatusText = (status: string) => { const statusMap: Record = { success: '成功', pending: '待处理', failed: '失败', expired: '已过期', disputing: '争议中', refund: '已退回', refused: '已拒绝' } return statusMap[status] || status } /** * 获取活动状态样式 */ const getStatusVariant = (status: string): "default" | "secondary" | "destructive" | "outline" => { switch (status) { case 'success': return 'default' case 'pending': return 'secondary' case 'failed': case 'refused': return 'destructive' default: return 'outline' } } /** * 活动卡片 * 用于显示活动列表数据 * * @returns 活动列表卡片 */ function PaymentCard({ onViewAll }: { onViewAll: () => void }) { const [isHidden, setIsHidden] = React.useState(false) const [payments, setPayments] = React.useState([]) const [loading, setLoading] = React.useState(true) const [total, setTotal] = React.useState(0) const fetchPayments = React.useCallback(async () => { try { setLoading(true) const response = await TransactionService.getTransactions({ page: 1, page_size: 10, types: ['payment'], statuses: ['success'] }) setPayments(response.orders) setTotal(response.total) } catch (error) { console.error('Failed to fetch payments:', error) } finally { setLoading(false) } }, []) React.useEffect(() => { fetchPayments() }, [fetchPayments]) return ( } loading={loading} onRefresh={fetchPayments} onViewAll={onViewAll} action={ } > {loading ? ( ) : payments.length > 0 ? (
{payments.map((payment) => (

{payment.order_name}

LDC {payment.amount}

{getStatusText(payment.status)}
))}
) : (

暂无积分活动记录

)}
{isHidden && (
)}
) } /** * 收入统计卡片 * 用于显示每日收入统计 * * @returns 收入统计卡片 */ function IncomeStatsCard() { const [stats, setStats] = React.useState([]) const [loading, setLoading] = React.useState(true) const [days] = React.useState(7) const fetchDailyStats = React.useCallback(async () => { try { setLoading(true) const data = await DashboardService.getDailyStats(days) //setStats(data) const newStats: DailyStatsItem[] = [] data.forEach((stat, i) => { let income = parseFloat(stat.income || "0"); if (income < 0) { income = 0; } newStats[i] = { date: stat.date, income: String(income), expense: stat.expense } }); setStats(newStats); } catch (error) { console.error('Failed to fetch daily stats:', error) } finally { setLoading(false) } }, [days]) React.useEffect(() => { fetchDailyStats() }, [fetchDailyStats]) // 计算总收入 const totalIncome = stats.reduce((sum, stat) => sum + parseFloat(stat.income || '0'), 0) // 找出最大收入值用于比例计算 const maxIncome = Math.max(...stats.map(s => parseFloat(s.income || '0'))) return ( {loading ? ( ) : ( <>LDC )} } loading={loading} onRefresh={fetchDailyStats} > {loading ? (
{Array.from({ length: 7 }).map((_, index) => (
))}
) : stats.length > 0 ? (
{stats.slice().reverse().map((stat, index) => { const income = parseFloat(stat.income || '0') const incomeWidth = maxIncome > 0 ? (income / maxIncome) * 100 : 0 return (
{new Date(stat.date).toLocaleDateString('zh-CN', { month: 'numeric', day: 'numeric' })} +{income.toFixed(2)}
) })}
) : (

暂无统计数据

)}
) } /** * 支出统计卡片 * 用于显示每日支出统计 * * @returns 支出统计卡片 */ function ExpenseStatsCard() { const [stats, setStats] = React.useState([]) const [loading, setLoading] = React.useState(true) const [days] = React.useState(7) const fetchDailyStats = React.useCallback(async () => { try { setLoading(true) const data = await DashboardService.getDailyStats(days) //setStats(data) const newStats: DailyStatsItem[] = [] data.forEach((stat, i) => { let income = parseFloat(stat.income || '0'); let expense = parseFloat(stat.expense || '0'); if (income < 0) { expense = expense - income; income = 0; } newStats[i] = { date: stat.date, income: String(income), expense: String(expense), } }) setStats(newStats); } catch (error) { console.error('Failed to fetch daily stats:', error) } finally { setLoading(false) } }, [days]) React.useEffect(() => { fetchDailyStats() }, [fetchDailyStats]) /* 计算总支出 */ const totalExpense = stats.reduce((sum, stat) => sum + parseFloat(stat.expense || '0'), 0) /* 找出最大支出值用于比例计算 */ const maxExpense = Math.max(...stats.map(s => parseFloat(s.expense || '0'))) return ( {loading ? ( ) : ( <>LDC )} } loading={loading} onRefresh={fetchDailyStats} > {loading ? (
{Array.from({ length: 7 }).map((_, index) => (
))}
) : stats.length > 0 ? (
{stats.slice().reverse().map((stat, index) => { const expense = parseFloat(stat.expense || '0') const expenseWidth = maxExpense > 0 ? (expense / maxExpense) * 100 : 0 return (
{new Date(stat.date).toLocaleDateString('zh-CN', { month: 'numeric', day: 'numeric' })} -{expense.toFixed(2)}
) })}
) : (

暂无统计数据

)}
) } /** * Top 客户卡片 * 用于显示向当前用户付款最多的客户排行 * * @returns Top 客户卡片 */ function TopCustomersCard() { const [customers, setCustomers] = React.useState([]) const [loading, setLoading] = React.useState(true) const [days] = React.useState(7) const [limit] = React.useState(5) const fetchTopCustomers = React.useCallback(async () => { try { setLoading(true) const data = await DashboardService.getTopCustomers(days, limit) setCustomers(data) } catch (error) { console.error('Failed to fetch top customers:', error) } finally { setLoading(false) } }, [days, limit]) React.useEffect(() => { fetchTopCustomers() }, [fetchTopCustomers]) /* 找出最大金额用于显示进度条 */ const maxAmount = customers && customers.length > 0 ? Math.max(...customers.map(c => parseFloat(c.total_amount || '0'))) : 0 return ( {loading ? (
{Array.from({ length: 5 }).map((_, index) => (
))}
) : customers && customers.length > 0 ? (
{customers.map((customer, index) => { const amount = parseFloat(customer.total_amount || '0') const percentage = maxAmount > 0 ? (amount / maxAmount) * 100 : 0 return (
#{index + 1} {customer.username}
{amount.toFixed(2)} LDC
{customer.order_count}单
) })}
) : (

暂无客户数据

)}
) } /** * 待处理的争议卡片 * 用于显示待处理的争议数据 * * @returns 待处理的争议卡片 */ function PendingDisputesCard({ onViewAll }: { onViewAll: () => void }) { const { disputes, loading, handleRefresh, refetchData } = useDisputeData({ fetchFn: (params) => DisputeService.listMerchantDisputes(params) }) return ( } loading={loading} onRefresh={handleRefresh} onViewAll={onViewAll} > {loading ? ( ) : disputes.list.length > 0 ? (
{disputes.list.map((dispute) => (

{dispute.order_name}

{dispute.payee_username}

))}
) : (

暂无待处理的积分活动争议

)}
) } /** * 我发起的争议卡片 * 用于显示我发起的争议数据 * * @returns 我发起的争议卡片 */ function MyDisputesCard({ onViewAll }: { onViewAll: () => void }) { const { disputes, loading, handleRefresh, refetchData } = useDisputeData({ fetchFn: (params) => DisputeService.listDisputes(params) }) return ( } loading={loading} onRefresh={handleRefresh} onViewAll={onViewAll} > {loading ? ( ) : disputes.list.length > 0 ? (
{disputes.list.map((dispute) => (

{dispute.order_name}

积分活动发起者正在处理争议

))}
) : (

暂无我发起的积分活动争议

)}
) } /** * 概览面板组件 * 用于显示概览面板 * * @returns 概览面板组件 */ export function OverviewPanel() { const router = useRouter() const [disputeDialogOpen, setDisputeDialogOpen] = React.useState(false) const [disputeDialogMode, setDisputeDialogMode] = React.useState<'pending' | 'my-disputes'>('pending') const handleViewAllPending = () => { setDisputeDialogMode('pending') setDisputeDialogOpen(true) } const handleViewAllMyDisputes = () => { setDisputeDialogMode('my-disputes') setDisputeDialogOpen(true) } const handleViewAllPayments = () => { router.push('/trade?type=payment') } return ( <>
) }