Files
wzdj/app/admin/leveling/client.tsx

185 lines
8.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client"
import { useState, useEffect } from "react"
import { Search, RefreshCw, Package, FileText, Download } from "lucide-react"
import { Input } from "@/components/ui/input"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"
type Product = { id: string; game?: string; name?: string; rankFrom?: string; rankTo?: string; price?: number; status?: string }
type Order = { id: string; productId?: string; status?: string; amount?: number; createdAt?: string }
export default function AdminLevelingClient() {
const [products, setProducts] = useState<Product[]>([])
const [orders, setOrders] = useState<Order[]>([])
const [searchTerm, setSearchTerm] = useState("")
const [loading, setLoading] = useState(true)
const [syncing, setSyncing] = useState(false)
useEffect(() => {
loadData()
}, [])
const loadData = async () => {
setLoading(true)
try {
const [pRes, oRes] = await Promise.all([
fetch("/api/admin/leveling/products"),
fetch("/api/admin/leveling/orders"),
])
const pJson = await pRes.json()
const oJson = await oRes.json()
setProducts(pJson.success && Array.isArray(pJson.data) ? pJson.data : [])
setOrders(oJson.success && Array.isArray(oJson.data) ? oJson.data : [])
} catch (e) {
console.error(e)
setProducts([])
setOrders([])
}
setLoading(false)
}
const syncDailian = async () => {
setSyncing(true)
try {
await fetch("/api/admin/leveling/sync-dailian", { method: "POST" })
loadData()
} finally {
setSyncing(false)
}
}
const filtered = products.filter(
(p) =>
(p.game ?? "").toLowerCase().includes(searchTerm.toLowerCase()) ||
(p.name ?? "").toLowerCase().includes(searchTerm.toLowerCase()),
)
return (
<div className="p-4 lg:p-6 max-w-[1600px] mx-auto">
<h1 className="text-xl lg:text-2xl font-bold text-slate-800 mb-6"></h1>
<p className="text-slate-500 text-sm mb-4">MongoDB levelingProducts / levelingOrders</p>
<Card className="admin-glass rounded-2xl overflow-hidden mb-6 border border-slate-200/80 shadow-lg">
<CardContent className="p-4 flex flex-wrap gap-4">
<div className="relative flex-1 min-w-[200px]">
<Search className="w-4 h-4 absolute left-3 top-1/2 -translate-y-1/2 text-slate-500" />
<Input
placeholder="搜索游戏、套餐名..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="bg-white/90 border-slate-200 text-slate-800 pl-10"
/>
</div>
<Button variant="outline" onClick={loadData} className="border-slate-200 text-slate-600">
<RefreshCw className="w-4 h-4 mr-2" />
</Button>
<Button variant="outline" onClick={syncDailian} disabled={syncing} className="border-indigo-200 text-indigo-600">
<Download className="w-4 h-4 mr-2" /> {syncing ? "同步中..." : "同步代练通"}
</Button>
</CardContent>
</Card>
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4 mb-6">
<Card className="admin-glass rounded-2xl border border-slate-200/80 shadow-lg">
<CardContent className="p-4 text-center">
<p className="text-2xl font-bold text-slate-800">{products.length}</p>
<p className="text-slate-500 text-sm"></p>
</CardContent>
</Card>
<Card className="admin-glass rounded-2xl border border-slate-200/80 shadow-lg">
<CardContent className="p-4 text-center">
<p className="text-2xl font-bold text-indigo-600">{orders.length}</p>
<p className="text-slate-500 text-sm"></p>
</CardContent>
</Card>
<Card className="admin-glass rounded-2xl border border-slate-200/80 shadow-lg">
<CardContent className="p-4 text-center">
<p className="text-2xl font-bold text-emerald-600">{products.filter((p) => p.status === "active").length}</p>
<p className="text-slate-500 text-sm"></p>
</CardContent>
</Card>
</div>
<Card className="admin-glass rounded-2xl border border-slate-200/80 shadow-lg">
<CardHeader>
<CardTitle className="text-slate-800 flex items-center gap-2">
<Package className="w-5 h-5 text-indigo-500" />
({filtered.length})
</CardTitle>
</CardHeader>
<CardContent>
{loading ? (
<div className="text-center py-8 text-slate-500">...</div>
) : filtered.length === 0 ? (
<div className="text-center py-8 text-slate-500"></div>
) : (
<div className="overflow-x-auto">
<table className="w-full">
<thead>
<tr className="border-b border-slate-200">
<th className="text-left text-slate-600 text-sm font-medium py-3 px-4">/</th>
<th className="text-left text-slate-600 text-sm font-medium py-3 px-4"></th>
<th className="text-left text-slate-600 text-sm font-medium py-3 px-4"></th>
<th className="text-left text-slate-600 text-sm font-medium py-3 px-4"></th>
</tr>
</thead>
<tbody>
{filtered.map((p) => (
<tr key={p.id} className="border-b border-slate-100 hover:bg-slate-50/80">
<td className="py-3 px-4 text-slate-800 font-medium">{p.name}</td>
<td className="py-3 px-4 text-slate-600">{p.rankFrom ?? "—"} {p.rankTo ?? "—"}</td>
<td className="py-3 px-4 text-amber-600">¥{p.price ?? 0}</td>
<td className="py-3 px-4">
<Badge className={p.status === "active" ? "bg-emerald-100 text-emerald-700" : "bg-slate-100 text-slate-600"}>{p.status ?? "—"}</Badge>
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</CardContent>
</Card>
<Card className="admin-glass rounded-2xl border border-slate-200/80 shadow-lg mt-6">
<CardHeader>
<CardTitle className="text-slate-800 flex items-center gap-2">
<FileText className="w-5 h-5 text-indigo-500" />
({orders.length})
</CardTitle>
</CardHeader>
<CardContent>
{orders.length === 0 ? (
<p className="text-slate-500 text-sm"></p>
) : (
<div className="overflow-x-auto">
<table className="w-full">
<thead>
<tr className="border-b border-slate-200">
<th className="text-left text-slate-600 text-sm font-medium py-3 px-4">ID</th>
<th className="text-left text-slate-600 text-sm font-medium py-3 px-4"></th>
<th className="text-left text-slate-600 text-sm font-medium py-3 px-4"></th>
<th className="text-left text-slate-600 text-sm font-medium py-3 px-4"></th>
</tr>
</thead>
<tbody>
{orders.map((o) => (
<tr key={o.id} className="border-b border-slate-100">
<td className="py-3 px-4 text-slate-800 font-mono text-sm">{o.id.slice(0, 8)}...</td>
<td className="py-3 px-4"><Badge className="bg-slate-100 text-slate-600">{o.status ?? "—"}</Badge></td>
<td className="py-3 px-4 text-amber-600">{o.amount ?? 0}</td>
<td className="py-3 px-4 text-slate-500 text-sm">{o.createdAt ? new Date(o.createdAt).toLocaleString("zh-CN") : "—"}</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</CardContent>
</Card>
</div>
)
}