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
)
})}
) : (
)}
)
}
/**
* 待处理的争议卡片
* 用于显示待处理的争议数据
*
* @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 (
<>
>
)
}