"use client"; import { useEffect, useState } from "react"; import { RefreshCcw } 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 { MembershipEntry } from "@/types/ops"; import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend, Area, AreaChart, } from "recharts"; type GrowthPoint = { date: string; trial: number; paid: number; total: number; cumulative: number }; export function MembershipsPageClient() { const [loading, setLoading] = useState(true); const [memberships, setMemberships] = useState([]); const [growth, setGrowth] = useState([]); const [filter, setFilter] = useState<"all" | "paid" | "trial">("all"); const load = async () => { setLoading(true); try { const [mData, gData] = await Promise.all([ opsApi.memberships(), fetch("/api/ops/memberships/growth?days=90", { cache: "no-store" }).then(r => r.ok ? r.json() : null), ]); setMemberships((mData as unknown as { memberships?: MembershipEntry[] }).memberships ?? []); setGrowth((gData as { daily?: GrowthPoint[] })?.daily ?? []); } catch { /* */ } setLoading(false); }; useEffect(() => { void load(); }, []); const paid = memberships.filter((m) => !m.is_trial); const trials = memberships.filter((m) => m.is_trial); const filtered = filter === "paid" ? paid : filter === "trial" ? trials : memberships; const planLabel = (code?: string) => { if (!code) return "—"; if (code.startsWith("signup_trial")) return "3天体验"; if (code === "pro_monthly") return "月付"; if (code === "pro_quarterly") return "季付"; if (code === "pro_yearly") return "年付"; return code; }; if (loading) return
加载中...
; return (

会员订阅 ({memberships.length}) 付费 {paid.length} · 体验 {trials.length}

{(["all", "paid", "trial"] as const).map((f) => ( ))}
{/* Growth chart */} {growth.length > 0 && ( 会员增长趋势 — 近 {growth.length} 天
{growth.reduce((s, d) => s + d.total, 0)}
总新增
{growth[growth.length - 1]?.cumulative ?? 0}
当前累计
{(growth.reduce((s, d) => s + d.total, 0) / Math.max(1, growth.filter(d => d.total > 0).length)).toFixed(1)}
日均新增
{Math.max(...growth.map(d => d.total), 0)}
单日最高
{/* Cumulative area chart */}

累计会员

{/* Daily bars */}

每日新增

)} {/* Table */}
{filtered.map((m, i) => ( ))} {filtered.length === 0 && ( )}
类型 邮箱 方案 起始 到期 排队天数
{m.is_trial ? ( 体验 ) : ( 付费 )} {m.email ?? "—"} {planLabel(m.plan_code)} {m.starts_at?.slice(0, 10) ?? "—"} {m.expires_at?.slice(0, 10) ?? "—"} {m.queued_days ?? 0}
暂无会员
); }